Compare commits

..
17 Commits
72 changed files with 9712 additions and 6924 deletions
+2 -9
View File
@@ -16,20 +16,13 @@ start-local-testnet.sh
test-fullstack-main.sh test-fullstack-main.sh
start-fullstack-main.sh start-fullstack-main.sh
start-fullstack-test.sh start-fullstack-test.sh
start-fullstack-testnet.sh
start-free-main.sh start-free-main.sh
free-bch-api-main.sh
free-bch-api-test.sh
start-local-mainnet.sh.save start-local-mainnet.sh.save
start-local-testnet.sh.save start-local-testnet.sh.save
temp.sh temp.sh
test-fullstack-test.sh test-fullstack-test.sh
start-sweet-main.sh
test-sweet-main.sh
test-fullstack-abc.sh
test-fullstack-bchn.sh
start-fullstack-abc.sh
start-fullstack-bchn.sh
start-ss-main.sh
start-decatur-bchn.sh
coverage coverage
start-my-infra start-my-infra
+1 -1
View File
@@ -1,6 +1,6 @@
MIT License MIT License
Copyright (c) 2021 Permissionless Software Foundation Copyright (c) 2020 Chris Troutner
Permission is hereby granted, free of charge, to any person obtaining a copy Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal of this software and associated documentation files (the "Software"), to deal
+26 -25
View File
@@ -1,15 +1,19 @@
# bch-api # bch-api
[![License](https://img.shields.io/npm/l/@psf/bch-js)](https://github.com/Permissionless-Software-Foundation/bch-js/blob/master/LICENSE.md)
[![js-standard-style](https://img.shields.io/badge/javascript-standard%20code%20style-green.svg?style=flat-square)](https://github.com/feross/standard)
This is a REST API server, written in node.js JavaScript, using [Express.js](https://expressjs.com/) framework. 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. See [this article](https://psfoundation.cash/blog/cash-stack) to learn about the 'Cash Stack'. Visit [FullStack.cash](https://fullstack.cash), sign up for a free account, and use this REST API right away with the [bch-js](https://github.com/Permissionless-Software-Foundation/bch-js) JavaScript library. [![Build Status](https://travis-ci.org/christroutner/bch-api.svg?branch=master)](https://travis-ci.org/christroutner/bch-api)
This repository is intended to be paired with [bch-js](https://github.com/Permissionless-Software-Foundation/bch-js), an npm JavaScript library for building Bitcoin Cash apps. This is a fork and alternative implementation of
the [rest.bitcoin.com](https://github.com/Bitcoin-com/rest.bitcoin.com) repository.
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. See [this article](https://troutsblog.com/research/bitcoin-cash/how-to-bch-full-stack-developer) to learn about the 'Cash Stack'. Visit [FullStack.cash](https://fullstack.cash), sign up for a free account, and use this REST API right away with the [bch-js](https://github.com/christroutner/bch-js) JavaScript library.
![Cash Stack Network Diagram](./generic-network-diagram.png) 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).
Both bch-api and bch-js are part of the 'Cash Stack' [full stack of BCH software](https://troutsblog.com/research/bitcoin-cash/how-to-bch-full-stack-developer). 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://fullstack.cash/documentation) - [API Documentation](https://fullstack.cash/documentation)
- [Example Code](https://fullstack.cash/examples) - [Example Code](https://fullstack.cash/examples)
@@ -20,38 +24,41 @@ Have questions? Need help? Join our community support:
## Features ## Features
The following features set this repository apart from rest.bitcoin.com: The following features set this repository apart from rest.bitcoin.com:
- 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 - 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). Can be used to monetize access to the REST API. [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, 10 RPM for [free accounts](https://fullstack.cash/pricing), up to 100 RPM if a full-access JWT token is used.
- Typescript removed and ES8 JavaScript used instead. - Typescript removed and ES8 JavaScript used instead.
- `npm audit fix` frequently run to fix dependencies. - npm audit run on all dependencies.
- [Greenkeeper](https://greenkeeper.io/) implemented for automatic dependency management
and security updates.
## Live Demo ## Live Demo
You can test a live demo of the REST API by running the You can test a live demo of the REST API by running the
[bch-js examples](https://github.com/Permissionless-Software-Foundation/bch-js-examples). [bch-js examples](https://github.com/Permissionless-Software-Foundation/bch-js-examples).
Rate limits are 20 requests per minute, but you can increase them to 100 with a [paid account](https://fullstack.cash/pricing). Rate limits are 3 requests per minute, but you can increase them to 10 with a [free account](https://fullstack.cash/pricing).
You can bootstrap your own REST API server by downloading and installing the infrastructure listed on the [CashStrap](https://fullstack.cash/cashstrap) page. 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).
### Configure bch-js - Mainnet REST API server: https://api.fullstack.cash/v3/
The live servers can be used by [bch-js](https://github.com/Permissionless-Software-Foundation/bch-js) by settings the `restURL` config property to one of these servers: - Testnet REST API server: https://tapi.fullstack.cash/v3/
- BCHN Mainnet REST API server: https://bchn.fullstack.cash/v4/
- ABC Mainnet REST API server: https://abc.fullstack.cash/v4/
- Testnet3 REST API server: https://testnet3.fullstack.cash/v4/
- Check server status: https://metrics.fullstack.cash - Check server status: https://metrics.fullstack.cash
## Installation ## Installation
There are two installation paths, depending if you want a *development* or There are two installation paths, depending if you want a *development* or
*production* environment. You'll also need to set up the underlying infrastructure *production* environment. You'll also need to set up the underlying infrastructure
described on [this page](https://psfoundation.cash/blog/cash-stack). described [this page](https://troutsblog.com/research/bitcoin-cash/how-to-bch-full-stack-developer).
This code targets the Ubuntu 18.04 LTS Linux OS or higher. This code targets the Ubuntu 18.04 LTS Linux OS.
### Development ### Development
This is a standard node.js project. The installation is as follows: This is a standard node.js project. The installation is as follows:
- Clone this repository: - Clone this repository:
`git clone https://github.com/Permissionless-Software-Foundation/bch-api && cd bch-api` `git clone https://github.com/christroutner/bch-api && cd bch-api`
- Install dependencies: - Install dependencies:
@@ -86,11 +93,5 @@ installation.
Have questions? Need help? Join our community support Have questions? Need help? Join our community support
[Telegram channel](https://t.me/bch_js_toolkit) [Telegram channel](https://t.me/bch_js_toolkit)
## IPFS
Copies of this repository will occasionally be uploaded and hosted on [IPFS](https://ipfs.io):
- v1.25.1: [QmXFzFJHenymhReDc9oGAxbpmvK2fJYnisQ2pJn3gGyKKX](https://ipfs.io/ipfs/QmXFzFJHenymhReDc9oGAxbpmvK2fJYnisQ2pJn3gGyKKX)
## License ## License
[MIT](./LICENSE.md) [MIT](./LICENSE.md)
a
+1 -12
View File
@@ -1,22 +1,11 @@
/* /*
Common configuration settings. Common configuration settings.
Default settings in this file can be overridden by an environment variable.
*/ */
const config = { const config = {
// This is the same secret used by jwt-bch-api
apiTokenSecret: process.env.TOKENSECRET apiTokenSecret: process.env.TOKENSECRET
? process.env.TOKENSECRET ? process.env.TOKENSECRET
: 'secret-jwt-token', : 'secret-jwt-token'
// Rate Limits
anonRateLimit: process.env.ANON_RATE_LIMIT ? Number(process.env.ANON_RATE_LIMIT) : 50,
whitelistRateLimit: process.env.WHITELIST_RATE_LIMIT
? Number(process.env.WHITELIST_RATE_LIMIT)
: 10,
whitelistDomains: process.env.WHITELIST_DOMAINS
? process.env.WHITELIST_DOMAINS.split(',')
: ['fullstack.cash', 'psfoundation.cash']
} }
module.exports = config module.exports = config
+9 -19
View File
@@ -1,27 +1,17 @@
#!/bin/bash
# Triggers a webhook to update the staging server at staging.fullstack.cash. # Triggers a webhook to update the staging server at staging.fullstack.cash.
#!/bin/bash
#echo $DEPLOY_SECRET #echo $DEPLOY_SECRET
# This if statement will only deploy on Jenkins when its testing the master branch. echo "Deploy to production...."
echo "-->$GIT_BRANCH<--"
if [[ $GIT_BRANCH =~ "master" ]]
then
echo "Deploying to production...." export DATA="{\"ref\":\"$DEPLOY_SECRET\"}"
export DATA="{\"ref\":\"$DEPLOY_SECRET\"}" #echo $DATA
#echo $DATA curl -X POST http://fullstack.cash:9000/hooks/bch-api-mainnet -H "Content-Type: application/json" -d $DATA
curl -X POST http://fullstack.cash:9000/hooks/bch-api-testnet -H "Content-Type: application/json" -d $DATA
curl -X POST http://$FREE_SERVER:9000/hooks/bch-api-testnet -H "Content-Type: application/json" -d $DATA
curl -X POST http://$FREE_SERVER:9000/hooks/bch-api-mainnet -H "Content-Type: application/json" -d $DATA
curl -X POST http://fullstack.cash:9000/hooks/bch-api-mainnet -H "Content-Type: application/json" -d $DATA echo "...Finished deploying to production."
curl -X POST http://fullstack.cash:9000/hooks/bch-api-testnet -H "Content-Type: application/json" -d $DATA
#curl -X POST http://$FREE_SERVER:9000/hooks/bch-api-testnet -H "Content-Type: application/json" -d $DATA
#curl -X POST http://$FREE_SERVER:9000/hooks/bch-api-mainnet -H "Content-Type: application/json" -d $DATA
curl -X POST http://$BCHN_BCHAPI:9000/hooks/bch-api-bchn -H "Content-Type: application/json" -d $DATA
echo "...Finished deploying to production."
else
echo "Not master, so not deploying."
fi
+1
View File
@@ -18,6 +18,7 @@ USER safeuser
WORKDIR /home/safeuser WORKDIR /home/safeuser
RUN git clone https://github.com/Permissionless-Software-Foundation/bch-api RUN git clone https://github.com/Permissionless-Software-Foundation/bch-api
WORKDIR /home/safeuser/bch-api WORKDIR /home/safeuser/bch-api
RUN git checkout free
RUN npm install --silent RUN npm install --silent
# Generate documentation # Generate documentation
+9 -46
View File
@@ -1,64 +1,27 @@
#!/bin/bash #!/bin/bash
# Docker containers for the below infrastructure is described here: # test
# https://psfoundation.cash/blog/cash-stack
# Pre-synced databases can be downloaded on the CashStrap page:
# https://fullstack.cash/cashstrap
# Which network are you using?
export NETWORK=mainnet
#export NETWORK=testnet
# Full node # Full node
export RPC_IP=172.17.0.1:8332 export RPC_BASEURL=http://172.17.0.1:8332/
export RPC_BASEURL=http://$RPC_IP/
export RPC_USERNAME=bitcoin export RPC_USERNAME=bitcoin
export RPC_PASSWORD=password export RPC_PASSWORD=password
export NETWORK=mainnet
# SLPDB # SLPDB
export SLPDB_PASS_GP=somelongpassword
export SLPDB_URL=http://172.17.0.1:12300/ export SLPDB_URL=http://172.17.0.1:12300/
# Use the same address as SLPDB_URL if you don't have a separate whitelist server. export SLPDB_PASS=somelongpassword
export SLPDB_PASS_WL=somelongpassword
export SLPDB_WHITELIST_URL=http://172.17.0.1:12300/
# slp-api alternative SLP validator using slp-validate:
# https://github.com/Permissionless-Software-Foundation/slp-api
export SLP_API_URL=http://10.0.0.5:5001/
# Mainnet Fulcrum / ElectrumX # Blockbook Indexer
export FULCRUM_URL=172.17.0.1 export BLOCKBOOK_URL=https://172.17.0.1:9131/
export FULCRUM_PORT=50002 # Allow node.js to make network calls to https using self-signed certificate.
export NODE_TLS_REJECT_UNAUTHORIZED=0
# Redis DB - Used for rate limiting # Redis DB
export REDIS_PORT=6379 export REDIS_PORT=6379
export REDIS_HOST=172.17.0.1 export REDIS_HOST=172.17.0.1
# JWT Token Secret # JWT Token Secret
# This is used to verify JWT tokens generated with jwt-bch-api:
# https://github.com/Permissionless-Software-Foundation/jwt-bch-api
export TOKENSECRET=somelongsecretvalue export TOKENSECRET=somelongsecretvalue
# So that bch-api can call bch-js locally.
export LOCAL_RESTURL=http://127.0.0.1:3000/v4/
# Basic Authentication password
export PRO_PASS=somerandomepassword:someotherrandompassword:aThirdPassword
# Whitelisted domains. Automatically give pro-tier rate limit access to apps
# that originate froma domain on the whitelist.
export WHITELIST_DOMAINS=fullstack.cash,psfoundation.cash,torlist.cash
# Rate Limits. Numbers are divided into 1000. e.g. 1000 / 50 = 20 RPM for ANON.
# Requests use the ANON rate limit if they fail to pass in a JWT token.
# ANON = 20 requests per minute (RPM)
export ANON_RATE_LIMIT=50
# 10 = 100 RPM
export WHITELIST_RATE_LIMIT=10
# Set logging parameters
#1m means no more than 1 megabyte
export LOG_MAX_SIZE=1m
#5d means store no more than 5 days
export LOG_MAX_FILES=5d
npm start npm start
+2 -1
View File
@@ -15,12 +15,13 @@ USER safeuser
WORKDIR /home/safeuser WORKDIR /home/safeuser
RUN git clone https://github.com/Permissionless-Software-Foundation/bch-api RUN git clone https://github.com/Permissionless-Software-Foundation/bch-api
WORKDIR /home/safeuser/bch-api WORKDIR /home/safeuser/bch-api
RUN git checkout free
RUN npm install --silent RUN npm install --silent
# Generate documentation # Generate documentation
RUN npm run docs RUN npm run docs
EXPOSE 3000 EXPOSE 4000
COPY start-local-testnet.sh start-local-testnet.sh COPY start-local-testnet.sh start-local-testnet.sh
CMD ["./start-local-testnet.sh"] CMD ["./start-local-testnet.sh"]
+2 -2
View File
@@ -6,9 +6,9 @@ bch-api-test:
links: links:
- redis-test - redis-test
ports: ports:
- "4000:3000" - "4000:4000"
volumes: volumes:
- ../../logs:/home/safeuser/bch-api/logs - ../../logs:/home/safeuser/bch-api/logs
restart: always restart: always
redis-test: redis-test:
+6 -37
View File
@@ -1,32 +1,18 @@
#!/bin/bash #!/bin/bash
# Docker containers for the below infrastructure is described here:
# https://psfoundation.cash/blog/cash-stack
# Pre-synced databases can be downloaded on the CashStrap page:
# https://fullstack.cash/cashstrap
# Which network are you using?
#export NETWORK=mainnet
export NETWORK=testnet
# Full node # Full node
export RPC_IP=172.17.0.1:18332 export RPC_BASEURL=http://172.17.0.1:18332/
export RPC_BASEURL=http://$RPC_IP/
export RPC_USERNAME=bitcoin export RPC_USERNAME=bitcoin
export RPC_PASSWORD=password export RPC_PASSWORD=password
export NETWORK=testnet
# SLPDB # SLPDB
export SLPDB_URL=http://172.17.0.1:13300/ export SLPDB_URL=http://172.17.0.1:13300/
export SLPDB_PASS=somelongpassword
# Use the same address as SLPDB_URL if you don't have a separate whitelist server.
export SLPDB_WHITELIST_URL=http://172.17.0.1:13300/
# slp-api alternative SLP validator using slp-validate:
# https://github.com/Permissionless-Software-Foundation/slp-api
export SLP_API_URL=http://10.0.0.5:5001/
# Mainnet Fulcrum / ElectrumX # Blockbook Indexer
export FULCRUM_URL=172.17.0.1 export BLOCKBOOK_URL=https://172.17.0.1:19131/
export FULCRUM_PORT=60002 # Allow node.js to make network calls to https using self-signed certificate.
export NODE_TLS_REJECT_UNAUTHORIZED=0
# Redis DB # Redis DB
export REDIS_PORT=6380 export REDIS_PORT=6380
@@ -35,21 +21,4 @@ export REDIS_HOST=172.17.0.1
# JWT Token Secret # JWT Token Secret
export TOKENSECRET=somelongsecretvalue export TOKENSECRET=somelongsecretvalue
# So that bch-api can call bch-js locally.
export LOCAL_RESTURL=http://127.0.0.1:3000/v4/
# Basic Authentication password (optional)
export PRO_PASS=somerandomepassword:someotherrandompassword:aThirdPassword
# Whitelisted domains. Automatically give pro-tier rate limit access to apps
# that originate froma domain on the whitelist.
export WHITELIST_DOMAINS=fullstack.cash,psfoundation.cash,torlist.cash
# Rate Limits. Numbers are divided into 1000. e.g. 1000 / 50 = 20 RPM for ANON.
# Requests use the ANON rate limit if they fail to pass in a JWT token.
# ANON = 20 requests per minute (RPM)
export ANON_RATE_LIMIT=50
# 10 = 100 RPM
export WHITELIST_RATE_LIMIT=10
npm start npm start
Binary file not shown.

Before

Width:  |  Height:  |  Size: 40 KiB

+7675 -3685
View File
File diff suppressed because it is too large Load Diff
+23 -16
View File
@@ -14,25 +14,24 @@
"scripts": { "scripts": {
"start": "node ./src/app.js", "start": "node ./src/app.js",
"dev": "nodemon ./dist/app.js", "dev": "nodemon ./dist/app.js",
"test": "npm run lint && npm run test-v4", "test": "npm run lint && npm run test-v3",
"lint": "standard --env mocha --fix", "lint": "standard --env mocha --fix",
"test-v4": "export NETWORK=mainnet && nyc --reporter=text mocha --timeout 60000 test/v4/", "test-v3": "export NETWORK=mainnet && nyc --reporter=text mocha --timeout 60000 test/v3/",
"test:integration": "mocha test/v4/integration", "test:temp": "export NETWORK=mainnet && mocha --timeout 25000 test/v3/slp.js",
"test:integration:slpdb": "mocha --timeout 25000 -g '#validate2Single' test/v4/integration/slp*.js", "test:integration": "mocha test/v3/integration",
"test:integration:slpdb": "mocha --timeout 25000 -g '#validate2Single' test/v3/integration/slp*.js",
"coverage": "nyc report --reporter=text-lcov | coveralls", "coverage": "nyc report --reporter=text-lcov | coveralls",
"coverage:report": "export NETWORK=mainnet && nyc --reporter=html mocha --timeout 25000 test/v4/", "coverage:report": "export NETWORK=mainnet && nyc --reporter=html mocha --timeout 25000 test/v3/",
"docs": "./node_modules/.bin/apidoc -i src/routes/v4 -o docs", "docs": "./node_modules/.bin/apidoc -i src/routes/v3 -o docs"
"test:temp1": "export NETWORK=mainnet && export TEST=integration && mocha --exit --timeout 25000 -g '#hydrateUtxosWL' test/v4/integration/",
"test:temp2": "mocha test/v4/rate-limits.js"
}, },
"engines": { "engines": {
"node": ">=10.15.1" "node": ">=10.15.1"
}, },
"dependencies": { "dependencies": {
"@psf/bch-js": "^4.15.21", "@psf/bch-js": "^3.7.1",
"apidoc": "^0.26.0", "apidoc": "^0.23.0",
"axios": "^0.21.1", "axios": "^0.19.0",
"bitcore-lib-cash": "^8.23.1", "bitcore-lib-cash": "^8.20.3",
"body-parser": "^1.18.3", "body-parser": "^1.18.3",
"cookie-parser": "~1.4.3", "cookie-parser": "~1.4.3",
"cors": "^2.8.3", "cors": "^2.8.3",
@@ -45,6 +44,7 @@
"helmet": "^3.21.2", "helmet": "^3.21.2",
"ioredis": "^4.14.1", "ioredis": "^4.14.1",
"jsonwebtoken": "^8.5.1", "jsonwebtoken": "^8.5.1",
"level": "^6.0.0",
"mkdirp": "^1.0.0", "mkdirp": "^1.0.0",
"mocha": "^7.1.1", "mocha": "^7.1.1",
"morgan": "^1.9.1", "morgan": "^1.9.1",
@@ -52,6 +52,8 @@
"passport": "^0.4.0", "passport": "^0.4.0",
"passport-anonymous": "^1.0.1", "passport-anonymous": "^1.0.1",
"passport-http": "^0.3.0", "passport-http": "^0.3.0",
"pg": "^8.0.0",
"pg-hstore": "^2.3.2",
"rate-limiter-flexible": "^2.0.0", "rate-limiter-flexible": "^2.0.0",
"winston": "^3.2.1", "winston": "^3.2.1",
"winston-daily-rotate-file": "^4.0.0" "winston-daily-rotate-file": "^4.0.0"
@@ -66,12 +68,17 @@
"eslint-plugin-prettier": "^3.1.0", "eslint-plugin-prettier": "^3.1.0",
"eslint-plugin-standard": "^4.0.0", "eslint-plugin-standard": "^4.0.0",
"fs-extra": "^9.0.0", "fs-extra": "^9.0.0",
"nock": "^13.0.5", "nock": "^12.0.0",
"node-mocks-http": "^1.7.0",
"nyc": "^15.0.0", "nyc": "^15.0.0",
"prettier": "^2.0.0", "prettier": "^2.0.0",
"semantic-release": "^17.3.9", "proxyquire": "^2.1.0",
"request": "^2.88.0",
"request-promise": "^4.2.2",
"semantic-release": "^17.0.0",
"sinon": "^9.0.0", "sinon": "^9.0.0",
"standard": "^14.3.1" "standard": "^14.3.1",
"typescript": "^3.1.4"
}, },
"release": { "release": {
"publish": [ "publish": [
@@ -83,6 +90,6 @@
}, },
"apidoc": { "apidoc": {
"title": "bch-api", "title": "bch-api",
"url": "https://api.fullstack.cash/v4" "url": "https://api.fullstack.cash/v3"
} }
} }
+49 -46
View File
@@ -25,34 +25,35 @@ const jwtAuth = require('./middleware/jwt-auth')
// Logging for API requests. // Logging for API requests.
const logReqInfo = require('./middleware/req-logging') const logReqInfo = require('./middleware/req-logging')
// v4 // v3
const healthCheckV4 = require('./routes/v4/health-check') const healthCheckV3 = require('./routes/v3/health-check')
const BlockchainV4 = require('./routes/v4/full-node/blockchain') const BlockchainV3 = require('./routes/v3/full-node/blockchain')
const ControlV4 = require('./routes/v4/full-node/control') const ControlV3 = require('./routes/v3/full-node/control')
const MiningV4 = require('./routes/v4/full-node/mining') const MiningV3 = require('./routes/v3/full-node/mining')
const networkV4 = require('./routes/v4/full-node/network') const networkV3 = require('./routes/v3/full-node/network')
const RawtransactionsV4 = require('./routes/v4/full-node/rawtransactions') const RawtransactionsV3 = require('./routes/v3/full-node/rawtransactions')
const UtilV4 = require('./routes/v4/util') const utilV3 = require('./routes/v3/util')
const SlpV4 = require('./routes/v4/slp') const SlpV3 = require('./routes/v3/slp')
const xpubV4 = require('./routes/v4/xpub') const xpubV3 = require('./routes/v3/xpub')
const ElectrumXV4 = require('./routes/v4/electrumx') const BlockbookV3 = require('./routes/v3/blockbook')
const EncryptionV4 = require('./routes/v4/encryption') const Ninsight = require('./routes/v3/ninsight')
const PriceV4 = require('./routes/v4/price') const ElectrumXV3 = require('./routes/v3/electrumx')
const Ninsight = require('./routes/v4/ninsight') const EncryptionV3 = require('./routes/v3/encryption')
const PriceV3 = require('./routes/v3/price')
require('dotenv').config() require('dotenv').config()
// Instantiate v4 route libraries. // Instantiate route libraries.
const blockchainV4 = new BlockchainV4() const blockchainV3 = new BlockchainV3()
const controlV4 = new ControlV4() const controlV3 = new ControlV3()
const miningV4 = new MiningV4() const miningV3 = new MiningV3()
const rawtransactionsV4 = new RawtransactionsV4() const rawtransactionsV3 = new RawtransactionsV3()
const slpV4 = new SlpV4() const slpV3 = new SlpV3()
const electrumxv4 = new ElectrumXV4() const blockbookV3 = new BlockbookV3()
electrumxv4.connect() const electrumxv3 = new ElectrumXV3()
const encryptionv4 = new EncryptionV4() electrumxv3.connect()
const pricev4 = new PriceV4() const encryptionv3 = new EncryptionV3()
const utilV4 = new UtilV4({ electrumx: electrumxv4 }) const pricev3 = new PriceV3()
const app = express() const app = express()
@@ -94,36 +95,38 @@ app.use(express.static(path.join(__dirname, 'public')))
// Log requests for later analysis. // Log requests for later analysis.
app.use('/', logReqInfo) app.use('/', logReqInfo)
const v4prefix = 'v4' // const v2prefix = "v2"
const v3prefix = 'v3'
// Inspect the header for a JWT token. // Inspect the header for a JWT token.
app.use(`/${v4prefix}/`, jwtAuth.getTokenFromHeaders) app.use(`/${v3prefix}/`, jwtAuth.getTokenFromHeaders)
// Instantiate the authorization middleware, used to implement pro-tier rate limiting. // Instantiate the authorization middleware, used to implement pro-tier rate limiting.
// Handles Anonymous and Basic Authorization schemes used by passport.js // Handles Anonymous and Basic Authorization schemes used by passport.js
const auth = new AuthMW() const auth = new AuthMW()
app.use(`/${v4prefix}/`, auth.mw()) app.use(`/${v3prefix}/`, auth.mw())
// Rate limit on all v4 routes // Rate limit on all v3 routes
// Establish and enforce rate limits. // Establish and enforce rate limits.
app.use(`/${v4prefix}/`, rateLimits.rateLimitByResource) // app.use(`/${v3prefix}/`, rateLimits.routeRateLimit)
app.use(`/${v3prefix}/`, rateLimits.rateLimitByResource)
// Connect v4 routes app.use(`/${v3prefix}/` + 'health-check', healthCheckV3)
app.use(`/${v4prefix}/` + 'health-check', healthCheckV4) app.use(`/${v3prefix}/` + 'blockchain', blockchainV3.router)
app.use(`/${v4prefix}/` + 'blockchain', blockchainV4.router) app.use(`/${v3prefix}/` + 'control', controlV3.router)
app.use(`/${v4prefix}/` + 'control', controlV4.router) app.use(`/${v3prefix}/` + 'mining', miningV3.router)
app.use(`/${v4prefix}/` + 'mining', miningV4.router) app.use(`/${v3prefix}/` + 'network', networkV3)
app.use(`/${v4prefix}/` + 'network', networkV4) app.use(`/${v3prefix}/` + 'rawtransactions', rawtransactionsV3.router)
app.use(`/${v4prefix}/` + 'rawtransactions', rawtransactionsV4.router) app.use(`/${v3prefix}/` + 'util', utilV3.router)
app.use(`/${v4prefix}/` + 'slp', slpV4.router) app.use(`/${v3prefix}/` + 'slp', slpV3.router)
app.use(`/${v4prefix}/` + 'xpub', xpubV4.router) app.use(`/${v3prefix}/` + 'xpub', xpubV3.router)
app.use(`/${v4prefix}/` + 'electrumx', electrumxv4.router) app.use(`/${v3prefix}/` + 'blockbook', blockbookV3.router)
app.use(`/${v4prefix}/` + 'encryption', encryptionv4.router) app.use(`/${v3prefix}/` + 'electrumx', electrumxv3.router)
app.use(`/${v4prefix}/` + 'price', pricev4.router) app.use(`/${v3prefix}/` + 'encryption', encryptionv3.router)
app.use(`/${v4prefix}/` + 'util', utilV4.router) app.use(`/${v3prefix}/` + 'price', pricev3.router)
const ninsight = new Ninsight() const ninsight = new Ninsight()
app.use(`/${v4prefix}/` + 'ninsight', ninsight.router) app.use(`/${v3prefix}/` + 'ninsight', ninsight.router)
// catch 404 and forward to error handler // catch 404 and forward to error handler
app.use((req, res, next) => { app.use((req, res, next) => {
@@ -208,11 +211,11 @@ function onError (error) {
case 'EACCES': case 'EACCES':
console.error(`${bind} requires elevated privileges`) console.error(`${bind} requires elevated privileges`)
process.exit(1) process.exit(1)
// break break
case 'EADDRINUSE': case 'EADDRINUSE':
console.error(`${bind} is already in use`) console.error(`${bind} is already in use`)
process.exit(1) process.exit(1)
// break break
default: default:
throw error throw error
} }
+49 -55
View File
@@ -22,9 +22,9 @@
'use strict' 'use strict'
const passport = require('passport') const passport = require('passport')
const BasicStrategy = require('passport-http').BasicStrategy // const BasicStrategy = require('passport-http').BasicStrategy
const AnonymousStrategy = require('passport-anonymous') const AnonymousStrategy = require('passport-anonymous')
const wlogger = require('../util/winston-logging') // const wlogger = require('../util/winston-logging')
// Used for debugging and iterrogating JS objects. // Used for debugging and iterrogating JS objects.
const util = require('util') const util = require('util')
@@ -33,11 +33,9 @@ util.inspect.defaultOptions = { depth: 1 }
// let _this // let _this
// Set default rate limit value for testing // Set default rate limit value for testing
const PRO_PASSES = process.env.PRO_PASS // const PRO_PASSES = process.env.PRO_PASS ? process.env.PRO_PASS : 'BITBOX'
? process.env.PRO_PASS
: 'testpassword'
// Convert the pro-tier password string into an array split by ':'. // Convert the pro-tier password string into an array split by ':'.
const PRO_PASS = PRO_PASSES.split(':') // const PRO_PASS = PRO_PASSES.split(':')
// wlogger.verbose(`PRO_PASS set to: ${PRO_PASS}`) // wlogger.verbose(`PRO_PASS set to: ${PRO_PASS}`)
@@ -49,60 +47,56 @@ class AuthMW {
// Initialize passport for 'anonymous' authentication. // Initialize passport for 'anonymous' authentication.
passport.use(new AnonymousStrategy()) passport.use(new AnonymousStrategy())
console.log(`PRO_PASS: ${JSON.stringify(PRO_PASS, null, 2)}`)
// Initialize passport for 'basic' authentication. // Initialize passport for 'basic' authentication.
passport.use( // passport.use(
new BasicStrategy({ passReqToCallback: true }, function ( // new BasicStrategy({ passReqToCallback: true }, function (
req, // req,
username, // username,
password, // password,
done // done
) { // ) {
// console.log(`req: ${util.inspect(req)}`) // // console.log(`req: ${util.inspect(req)}`)
// console.log(`username: ${username}`) // // console.log(`username: ${username}`)
// console.log(`password: ${password}`) // // console.log(`password: ${password}`)
//
// Create the req.locals property if it does not yet exist. // // Create the req.locals property if it does not yet exist.
if (!req.locals) { // if (!req.locals) {
req.locals = { // req.locals = {
// default values // // default values
proLimit: false, // proLimit: false,
apiLevel: 0 // apiLevel: 0
} // }
} // }
//
// Set pro-tier rate limit to flag to false by default. // // Set pro-tier rate limit to flag to false by default.
req.locals.proLimit = false // req.locals.proLimit = false
//
// Evaluate the username and password and set the rate limit accordingly. // // Evaluate the username and password and set the rate limit accordingly.
// if (username === "BITBOX" && password === PRO_PASS) { // // if (username === "BITBOX" && password === PRO_PASS) {
if (username === 'fullstackcash') { // if (username === 'BITBOX') {
for (let i = 0; i < PRO_PASS.length; i++) { // for (let i = 0; i < PRO_PASS.length; i++) {
const thisPass = PRO_PASS[i] // const thisPass = PRO_PASS[i]
//
if (password === thisPass) { // if (password === thisPass) {
wlogger.verbose(`${req.url} called by ${password.slice(0, 6)}`) // wlogger.verbose(`${req.url} called by ${password.slice(0, 6)}`)
//
// Success // // Success
req.locals.proLimit = true // req.locals.proLimit = true
console.log(`User ${req.ip} authenticated using Basic Auth`) // break
break // }
} // }
} // }
} //
// // console.log(`req.locals: ${util.inspect(req.locals)}`)
// console.log(`req.locals: ${util.inspect(req.locals)}`) //
// return done(null, true)
return done(null, true) // })
}) // )
)
} }
// Middleware called by the route. // Middleware called by the route.
mw () { mw () {
console.log('Initializing passport') return passport.authenticate(['anonymous'], {
return passport.authenticate(['basic', 'anonymous'], {
session: false session: false
}) })
} }
+69 -144
View File
@@ -1,28 +1,9 @@
/*
Sets the rate limits for the anonymous and paid tiers. Current rate limits:
- 1000 points in 60 seconds
- 10 points per call for paid tier (100 RPM)
- 50 points per call for anonymous tier (20 RPM)
Background:
The rate limits below were originially coded with the idea of charging on a
per-resource basis. However, that was confusing to end users trying to purchase
a subscription. So everything was simplied to two tiers: paid and anonymous
*/
'use strict' 'use strict'
// Public npm libraries.
const jwt = require('jsonwebtoken')
// local libraries.
const wlogger = require('../util/winston-logging') const wlogger = require('../util/winston-logging')
const config = require('../../config') const config = require('../../config')
const ANON_LIMITS = config.anonRateLimit const jwt = require('jsonwebtoken')
const WHITELIST_RATE_LIMIT = config.whitelistRateLimit
const WHITELIST_DOMAINS = config.whitelistDomains
const INTERNAL_RATE_LIMIT = 1
// Redis // Redis
const redisOptions = { const redisOptions = {
@@ -113,107 +94,78 @@ class RateLimits {
err err
) )
} }
//
} else if (req.body && req.body.usrObj) {
// Same as above, but this code path is activated from internal calls to
// bch-js, like hydrateUtxo(), which passes the user object from the
// original API call.
try {
decoded = _this.jwt.verify(
req.body.usrObj.jwtToken,
_this.config.apiTokenSecret
)
// console.log(`decoded: ${JSON.stringify(decoded, null, 2)}`)
userId = decoded.id
} catch (err) {
// This handler will be triggered if the JWT token does not match the
// token secret.
wlogger.error(
'Error in route-ratelimit.js trying to decode JWT token in usrObj'
)
}
} else { } else {
wlogger.debug('No JWT token found!') wlogger.debug('No JWT token found!')
} }
// Default value is 50 points per request = 20 RPM // Used for displaying error message. Default value is 30.
let rateLimit = ANON_LIMITS let rateLimit = 30
// Only evaluate the JWT token if the user is not using Basic Authentication. // Code here for the rate limiter is adapted from this example:
if (!req.locals.proLimit) { // https://github.com/animir/node-rate-limiter-flexible/wiki/Overall-example#authorized-and-not-authorized-users
// Code here for the rate limiter is adapted from this example: try {
// https://github.com/animir/node-rate-limiter-flexible/wiki/Overall-example#authorized-and-not-authorized-users // The resource being consumed: full node, indexer, SLPDB, etc.
try { const resource = _this.getResource(req.url)
// The resource being consumed: full node, indexer, SLPDB, etc. wlogger.debug(`resource: ${resource}`)
const resource = _this.getResource(req.url)
wlogger.debug(`resource: ${resource}`)
// Key will be the JWT ID if it exists, otherwise the IP address of the caller. let key = userId || req.ip
let key = userId || req.ip res.locals.key = key // Feedback for tests.
res.locals.key = key // Feedback for tests.
// console.log(`key: ${key}`)
// const pointsToConsume = userId ? 1 : 30 // const pointsToConsume = userId ? 1 : 30
decoded.resource = resource decoded.resource = resource
let pointsToConsume = _this.calcPoints(decoded) let pointsToConsume = _this.calcPoints(decoded)
res.locals.pointsToConsume = pointsToConsume // Feedback for tests. res.locals.pointsToConsume = pointsToConsume // Feedback for tests.
// Retrieve the origin. // Retrieve the origin.
let origin = req.get('origin') let origin = req.get('origin')
if (origin === undefined && key.indexOf('10.0.0.5') > -1) {
// Handle calls coming from the intranet. origin = 'slp-api'
if (origin === undefined && key.indexOf('10.0.0.5') > -1) {
origin = 'slp-api'
}
wlogger.info(`origin: ${origin}`)
// If the request originates from one of the approved wallet apps, then
// apply paid-access rate limits.
// console.log(`origin: ${JSON.stringify(origin, null, 2)}`)
// console.log(`whitelist: ${JSON.stringify(WHITELIST_DOMAINS, null, 2)}`)
const isInWhitelist = _this.isInWhitelist(origin)
if (isInWhitelist) {
pointsToConsume = WHITELIST_RATE_LIMIT
res.locals.pointsToConsume = pointsToConsume // Feedback for tests.
}
// For internal calls, increase rate limits to as fast as possible.
if (
// Comment out the line below when running bch-js e2e rate limit tests.
key.toString().indexOf('::ffff:127.0.0.1') > -1 ||
// Do not comment out this line.
key.toString().indexOf('172.17.') > -1
) {
pointsToConsume = INTERNAL_RATE_LIMIT
res.locals.pointsToConsume = pointsToConsume // Feedback for tests.
}
wlogger.info(
`User ${key} consuming ${pointsToConsume} point for resource ${resource}.`
)
rateLimit = Math.floor(1000 / pointsToConsume)
// Update the key so that rate limits track both the user and the resource.
key = `${key}-${resource}`
await _this.rateLimiter.consume(key, pointsToConsume)
} catch (err) {
// console.log('err: ', err)
// Used for returning data for tests.
res.locals.rateLimitTriggered = true
// console.log('res.locals: ', res.locals)
// Rate limited was triggered
res.status(429) // https://github.com/Bitcoin-com/rest.bitcoin.com/issues/330
return res.json({
error: `Too many requests. Your limits are currently ${rateLimit} requests per minute. Increase rate limits at https://fullstack.cash`
})
} }
wlogger.info(`origin: ${origin}`)
// If the request originates from one of the approved wallet apps, then
// apply paid-access rate limits.
if (
origin &&
(origin.toString().indexOf('wallet.fullstack.cash') > -1 ||
origin.toString().indexOf('sandbox.fullstack.cash') > -1 ||
origin === 'slp-api')
) {
pointsToConsume = 10
res.locals.pointsToConsume = pointsToConsume // Feedback for tests.
}
// Apply paid-access rate limits based on key/IP
if (
key.toString().indexOf('172.17.') > -1 ||
key.toString().indexOf('::ffff:127.0.0.1') > -1
) {
pointsToConsume = 1
res.locals.pointsToConsume = pointsToConsume // Feedback for tests.
}
wlogger.info(
`User ${key} consuming ${pointsToConsume} point for resource ${resource}.`
)
rateLimit = Math.floor(1000 / pointsToConsume)
// Update the key so that rate limits track both the user and the resource.
key = `${key}-${resource}`
await _this.rateLimiter.consume(key, pointsToConsume)
} catch (err) {
// console.log('err: ', err)
// Used for returning data for tests.
res.locals.rateLimitTriggered = true
// console.log('res.locals: ', res.locals)
// Rate limited was triggered
res.status(429) // https://github.com/Bitcoin-com/rest.bitcoin.com/issues/330
return res.json({
error: `Too many requests. Your limits are currently ${rateLimit} requests per minute. Increase rate limits at https://fullstack.cash`
})
} }
} catch (err) { } catch (err) {
wlogger.error('Error in route-ratelimit.js/newRateLimit(): ', err) wlogger.error('Error in route-ratelimit.js/newRateLimit(): ', err)
@@ -226,7 +178,7 @@ class RateLimits {
// Calculates the points consumed, based on the jwt information and the route // Calculates the points consumed, based on the jwt information and the route
// requested. // requested.
calcPoints (jwtInfo) { calcPoints (jwtInfo) {
let retVal = ANON_LIMITS // By default, use anonymous tier. let retVal = 33 // By default, use anonymous tier.
try { try {
// console.log(`jwtInfo: ${JSON.stringify(jwtInfo, null, 2)}`) // console.log(`jwtInfo: ${JSON.stringify(jwtInfo, null, 2)}`)
@@ -245,12 +197,12 @@ class RateLimits {
if (level40Routes.includes(resource)) { if (level40Routes.includes(resource)) {
if (apiLevel >= 40) retVal = 10 if (apiLevel >= 40) retVal = 10
// else if (apiLevel >= 10) retVal = 10 // else if (apiLevel >= 10) retVal = 10
else retVal = ANON_LIMITS else retVal = 33
// Normal indexer routes // Normal indexer routes
} else if (level30Routes.includes(resource)) { } else if (level30Routes.includes(resource)) {
if (apiLevel >= 30) retVal = 10 if (apiLevel >= 30) retVal = 10
else retVal = ANON_LIMITS else retVal = 33
// Full node tier // Full node tier
} else if (apiLevel >= 20) { } else if (apiLevel >= 20) {
@@ -258,7 +210,7 @@ class RateLimits {
// Free tier, full node only. // Free tier, full node only.
} else { } else {
retVal = ANON_LIMITS retVal = 33
} }
} }
@@ -266,7 +218,7 @@ class RateLimits {
} catch (err) { } catch (err) {
wlogger.error('Error in route-ratelimit.js/calcPoints()') wlogger.error('Error in route-ratelimit.js/calcPoints()')
// throw err // throw err
retVal = ANON_LIMITS retVal = 33
} }
return retVal return retVal
@@ -289,33 +241,6 @@ class RateLimits {
throw err throw err
} }
} }
// Returns a boolean if the origin of the request matches a domain in the
// whitelist.
isInWhitelist (origin) {
try {
const retVal = false // Default value.
if (!origin) return false
// console.log(`WHITELIST_DOMAINS: ${JSON.stringify(WHITELIST_DOMAINS, null, 2)}`)
for (let i = 0; i < WHITELIST_DOMAINS.length; i++) {
const thisDomain = WHITELIST_DOMAINS[i]
if (origin.toString().indexOf(thisDomain) > -1) {
return true
}
}
return retVal
} catch (err) {
wlogger.error(
'Error in route-ratelimit.js/isInWhitelist(). Returning false by default.'
)
return false
}
}
} }
module.exports = RateLimits module.exports = RateLimits
@@ -96,6 +96,18 @@ class Blockbook {
} }
} }
/**
* @api {get} /blockbook/balance/{addr} Get balance for a single address.
* @apiName Balance for a single address
* @apiGroup Blockbook
* @apiDescription Returns an object with balance and details about an address.
*
*
* @apiExample Example usage:
* curl -X GET "https://api.fullstack.cash/v3/blockbook/balance/bitcoincash:qrdka2205f4hyukutc2g0s6lykperc8nsu5u2ddpqf" -H "accept: application/json"
*
*/
// GET handler for single balance
async balanceSingle (req, res, next) { async balanceSingle (req, res, next) {
try { try {
const address = req.params.address const address = req.params.address
@@ -153,6 +165,18 @@ class Blockbook {
} }
} }
/**
* @api {post} /blockbook/balance Get balance for an array of addresses.
* @apiName Balance for an array of addresses
* @apiGroup Blockbook
* @apiDescription Return balances and details for an array of addresses.
* Limited to 20 items per request.
*
* @apiExample Example usage:
* curl -X POST "https://api.fullstack.cash/v3/blockbook/balance" -H "accept: application/json" -H "Content-Type: application/json" -d '{"addresses":["bitcoincash:qrdka2205f4hyukutc2g0s6lykperc8nsu5u2ddpqf","bitcoincash:qrdka2205f4hyukutc2g0s6lykperc8nsu5u2ddpqf"]}'
*
*
*/
// POST handler for bulk queries on address details // POST handler for bulk queries on address details
async balanceBulk (req, res, next) { async balanceBulk (req, res, next) {
try { try {
@@ -260,6 +284,17 @@ class Blockbook {
} }
} }
/**
* @api {get} /blockbook/utxos/{addr} Get utxos for a single address.
* @apiName UTXOs for a single address
* @apiGroup Blockbook
* @apiDescription Returns an object with UTXOs associated with an address.
*
*
* @apiExample Example usage:
* curl -X GET "https://api.fullstack.cash/v3/blockbook/utxos/bitcoincash:qr69kyzha07dcecrsvjwsj4s6slnlq4r8c30lxnur3" -H "accept: application/json"
*
*/
// GET handler for single balance // GET handler for single balance
async utxosSingle (req, res, next) { async utxosSingle (req, res, next) {
try { try {
@@ -317,6 +352,18 @@ class Blockbook {
} }
} }
/**
* @api {post} /blockbook/utxos Get UTXOs for an array of addresses.
* @apiName UTXOs for an array of addresses
* @apiGroup Blockbook
* @apiDescription Return UTXOs associate with an array of addresses.
* Limited to 20 items per request.
*
* @apiExample Example usage:
* curl -X POST "https://api.fullstack.cash/v3/blockbook/utxos" -H "accept: application/json" -H "Content-Type: application/json" -d '{"addresses":["bitcoincash:qr69kyzha07dcecrsvjwsj4s6slnlq4r8c30lxnur3","bitcoincash:qzy8wnj0dz927eu6kvh8v2pqsr5w8jh33ys757tdtq"]}'
*
*
*/
// POST handler for bulk queries on address utxos // POST handler for bulk queries on address utxos
async utxosBulk (req, res, next) { async utxosBulk (req, res, next) {
try { try {
@@ -414,6 +461,17 @@ class Blockbook {
} }
} }
/**
* @api {get} /blockbook/tx/{txid} Get details for a single transaction.
* @apiName Details for a single transaction
* @apiGroup Blockbook
* @apiDescription Returns an object with details for a single transaction
*
*
* @apiExample Example usage:
* curl -X GET "https://api.fullstack.cash/v3/blockbook/tx/6181c669614fa18039a19b23eb06806bfece1f7514ab457c3bb82a40fe171a6d" -H "accept: application/json"
*
*/
// GET handler for single transaction details. // GET handler for single transaction details.
async txSingle (req, res, next) { async txSingle (req, res, next) {
try { try {
@@ -456,6 +514,18 @@ class Blockbook {
} }
} }
/**
* @api {post} /blockbook/tx Get details for an array of transactions.
* @apiName Details for an array of transactions
* @apiGroup Blockbook
* @apiDescription Return details for an array of transactions.
* Limited to 20 items per request.
*
* @apiExample Example usage:
* curl -X POST "https://api.fullstack.cash/v3/blockbook/tx" -H "accept: application/json" -H "Content-Type: application/json" -d '{"txids":["6181c669614fa18039a19b23eb06806bfece1f7514ab457c3bb82a40fe171a6d","6181c669614fa18039a19b23eb06806bfece1f7514ab457c3bb82a40fe171a6d"]}'
*
*
*/
// POST handler for bulk queries on tx details // POST handler for bulk queries on tx details
async txBulk (req, res, next) { async txBulk (req, res, next) {
try { try {
@@ -79,21 +79,6 @@ class Electrum {
// Set the connection flag. // Set the connection flag.
_this.isReady = true _this.isReady = true
// Periodically check the connection. If it's not connected, attempt to
// reconnect.
_this.reconnectIntervalHandle = setInterval(async function () {
const status = _this.electrumx.connection.status
// console.log(`Electrumx status: ${status}`)
// 1 = connected. If we're not connected, attemp to reconnect.
if (status !== 1) {
wlogger.info(`Electrumx not connectes. Status: ${status}`)
wlogger.info('Attempting to reconnect...')
await _this.electrumx.connect()
wlogger.info('...reconnected.')
}
}, 30000)
console.log('...Successfully connected to ElectrumX server.') console.log('...Successfully connected to ElectrumX server.')
// console.log(`_this.isReady: ${_this.isReady}`) // console.log(`_this.isReady: ${_this.isReady}`)
@@ -111,9 +96,6 @@ class Electrum {
// Return immediately if the isReady flag is false. // Return immediately if the isReady flag is false.
if (!_this.isReady) return true if (!_this.isReady) return true
// Disable the reconnect timer.
clearInterval(_this.reconnectIntervalHandle)
// Disconnect from the server. // Disconnect from the server.
await _this.electrumx.disconnect() await _this.electrumx.disconnect()
@@ -135,7 +117,7 @@ class Electrum {
const { msg, status } = _this.routeUtils.decodeError(err) const { msg, status } = _this.routeUtils.decodeError(err)
if (msg) { if (msg) {
res.status(status) res.status(status)
return res.json({ success: false, error: msg }) return res.json({ error: msg })
} }
// Handle error patterns specific to this route. // Handle error patterns specific to this route.
@@ -195,7 +177,7 @@ class Electrum {
* *
* *
* @apiExample Example usage: * @apiExample Example usage:
* curl -X GET "https://api.fullstack.cash/v4/electrumx/utxos/bitcoincash:qr69kyzha07dcecrsvjwsj4s6slnlq4r8c30lxnur3" -H "accept: application/json" * curl -X GET "https://api.fullstack.cash/v3/electrumx/utxos/bitcoincash:qr69kyzha07dcecrsvjwsj4s6slnlq4r8c30lxnur3" -H "accept: application/json"
* *
*/ */
// GET handler for single balance // GET handler for single balance
@@ -264,7 +246,7 @@ class Electrum {
* Limited to 20 items per request. * Limited to 20 items per request.
* *
* @apiExample Example usage: * @apiExample Example usage:
* curl -X POST "https://api.fullstack.cash/v4/electrumx/utxos" -H "accept: application/json" -H "Content-Type: application/json" -d '{"addresses":["bitcoincash:qrdka2205f4hyukutc2g0s6lykperc8nsu5u2ddpqf","bitcoincash:qrdka2205f4hyukutc2g0s6lykperc8nsu5u2ddpqf"]}' * curl -X POST "https://api.fullstack.cash/v3/electrumx/utxos" -H "accept: application/json" -H "Content-Type: application/json" -d '{"addresses":["bitcoincash:qrdka2205f4hyukutc2g0s6lykperc8nsu5u2ddpqf","bitcoincash:qrdka2205f4hyukutc2g0s6lykperc8nsu5u2ddpqf"]}'
* *
* *
*/ */
@@ -389,7 +371,7 @@ class Electrum {
* *
* *
* @apiExample Example usage: * @apiExample Example usage:
* curl -X GET "https://api.fullstack.cash/v4/electrumx/tx/data/a1075db55d416d3ca199f55b6084e2115b9345e16c5cf302fc80e9d5fbf5d48d" -H "accept: application/json" * curl -X GET "https://api.fullstack.cash/v3/electrumx/tx/data/a1075db55d416d3ca199f55b6084e2115b9345e16c5cf302fc80e9d5fbf5d48d" -H "accept: application/json"
* *
*/ */
// GET handler for single transaction // GET handler for single transaction
@@ -449,7 +431,7 @@ class Electrum {
* Limited to 20 items per request. * Limited to 20 items per request.
* *
* @apiExample Example usage: * @apiExample Example usage:
* curl -X POST "https://api.fullstack.cash/v4/electrumx/tx/data" -H "accept: application/json" -H "Content-Type: application/json" -d '{"txids":["a1075db55d416d3ca199f55b6084e2115b9345e16c5cf302fc80e9d5fbf5d48d","a1075db55d416d3ca199f55b6084e2115b9345e16c5cf302fc80e9d5fbf5d48d"], "verbose":false}' * curl -X POST "https://api.fullstack.cash/v3/electrumx/tx/data" -H "accept: application/json" -H "Content-Type: application/json" -d '{"txids":["a1075db55d416d3ca199f55b6084e2115b9345e16c5cf302fc80e9d5fbf5d48d","a1075db55d416d3ca199f55b6084e2115b9345e16c5cf302fc80e9d5fbf5d48d"], "verbose":false}'
* *
* *
*/ */
@@ -550,7 +532,7 @@ class Electrum {
* @apiDescription Broadcast a raw transaction and return the transaction ID on success or error on failure. * @apiDescription Broadcast a raw transaction and return the transaction ID on success or error on failure.
* *
* @apiExample Example usage: * @apiExample Example usage:
* curl -X POST "https://api.fullstack.cash/v4/electrumx/tx/broadcast" -H "accept: application/json" -H "Content-Type: application/json" -d '{"txHex":"020000000265d13ef402840c8a51f39779afb7ae4d49e4b0a3c24a3d0e7742038f2c679667010000006441dd1dd72770cadede1a7fd0363574846c48468a398ddfa41a9677c74cac8d2652b682743725a3b08c6c2021a629011e11a264d9036e9d5311e35b5f4937ca7b4e4121020797d8fd4d2fa6fd7cdeabe2526bfea2b90525d6e8ad506ec4ee3c53885aa309ffffffff65d13ef402840c8a51f39779afb7ae4d49e4b0a3c24a3d0e7742038f2c679667000000006441347d7f218c11c04487c1ad8baac28928fb10e5054cd4494b94d078cfa04ccf68e064fb188127ff656c0b98e9ce87f036d183925d0d0860605877d61e90375f774121028a53f95eb631b460854fc836b2e5d31cad16364b4dc3d970babfbdcc3f2e4954ffffffff035ac355000000000017a914189ce02e332548f4804bac65cba68202c9dbf822878dfd0800000000001976a914285bb350881b21ac89724c6fb6dc914d096cd53b88acf9ef3100000000001976a91445f1f1c4a9b9419a5088a3e9c24a293d7a150e6488ac00000000"}' * curl -X POST "https://api.fullstack.cash/v3/electrumx/tx/broadcast" -H "accept: application/json" -H "Content-Type: application/json" -d '{"txHex":"020000000265d13ef402840c8a51f39779afb7ae4d49e4b0a3c24a3d0e7742038f2c679667010000006441dd1dd72770cadede1a7fd0363574846c48468a398ddfa41a9677c74cac8d2652b682743725a3b08c6c2021a629011e11a264d9036e9d5311e35b5f4937ca7b4e4121020797d8fd4d2fa6fd7cdeabe2526bfea2b90525d6e8ad506ec4ee3c53885aa309ffffffff65d13ef402840c8a51f39779afb7ae4d49e4b0a3c24a3d0e7742038f2c679667000000006441347d7f218c11c04487c1ad8baac28928fb10e5054cd4494b94d078cfa04ccf68e064fb188127ff656c0b98e9ce87f036d183925d0d0860605877d61e90375f774121028a53f95eb631b460854fc836b2e5d31cad16364b4dc3d970babfbdcc3f2e4954ffffffff035ac355000000000017a914189ce02e332548f4804bac65cba68202c9dbf822878dfd0800000000001976a914285bb350881b21ac89724c6fb6dc914d096cd53b88acf9ef3100000000001976a91445f1f1c4a9b9419a5088a3e9c24a293d7a150e6488ac00000000"}'
* *
*/ */
// POST handler for broadcasting a single transaction // POST handler for broadcasting a single transaction
@@ -641,7 +623,7 @@ class Electrum {
* *
* *
* @apiExample Example usage: * @apiExample Example usage:
* curl -X GET "https://api.fullstack.cash/v4/electrumx/block/header/42?count=2" -H "accept: application/json" * curl -X GET "https://api.fullstack.cash/v3/electrumx/block/header/42?count=2" -H "accept: application/json"
* *
*/ */
// GET handler for single block headers // GET handler for single block headers
@@ -707,7 +689,7 @@ class Electrum {
* Limited to 20 items per request. * Limited to 20 items per request.
* *
* @apiExample Example usage: * @apiExample Example usage:
* curl -X POST "https://api.fullstack.cash/v4/electrumx/block/headers" -H "accept: application/json" -H "Content-Type: application/json" -d '{"heights":[{ "height": 42, count: 2 }, { "height": 100, count: 5 }]}' * curl -X POST "https://api.fullstack.cash/v3/electrumx/block/headers" -H "accept: application/json" -H "Content-Type: application/json" -d '{"heights":[{ "height": 42, count: 2 }, { "height": 100, count: 5 }]}'
* *
*/ */
// POST handler for bulk queries on block headers // POST handler for bulk queries on block headers
@@ -806,7 +788,7 @@ class Electrum {
* *
* *
* @apiExample Example usage: * @apiExample Example usage:
* curl -X GET "https://api.fullstack.cash/v4/electrumx/balance/bitcoincash:qr69kyzha07dcecrsvjwsj4s6slnlq4r8c30lxnur3" -H "accept: application/json" * curl -X GET "https://api.fullstack.cash/v3/electrumx/balance/bitcoincash:qr69kyzha07dcecrsvjwsj4s6slnlq4r8c30lxnur3" -H "accept: application/json"
* *
*/ */
// GET handler for single balance // GET handler for single balance
@@ -876,7 +858,7 @@ class Electrum {
* Limited to 20 items per request. * Limited to 20 items per request.
* *
* @apiExample Example usage: * @apiExample Example usage:
* curl -X POST "https://api.fullstack.cash/v4/electrumx/balance" -H "accept: application/json" -H "Content-Type: application/json" -d '{"addresses":["bitcoincash:qrdka2205f4hyukutc2g0s6lykperc8nsu5u2ddpqf","bitcoincash:qrdka2205f4hyukutc2g0s6lykperc8nsu5u2ddpqf"]}' * curl -X POST "https://api.fullstack.cash/v3/electrumx/balance" -H "accept: application/json" -H "Content-Type: application/json" -d '{"addresses":["bitcoincash:qrdka2205f4hyukutc2g0s6lykperc8nsu5u2ddpqf","bitcoincash:qrdka2205f4hyukutc2g0s6lykperc8nsu5u2ddpqf"]}'
* *
* *
*/ */
@@ -999,7 +981,7 @@ class Electrum {
* *
* *
* @apiExample Example usage: * @apiExample Example usage:
* curl -X GET "https://api.fullstack.cash/v4/electrumx/transactions/bitcoincash:qr69kyzha07dcecrsvjwsj4s6slnlq4r8c30lxnur3" -H "accept: application/json" * curl -X GET "https://api.fullstack.cash/v3/electrumx/transactions/bitcoincash:qr69kyzha07dcecrsvjwsj4s6slnlq4r8c30lxnur3" -H "accept: application/json"
* *
*/ */
// GET handler for single balance // GET handler for single balance
@@ -1069,7 +1051,7 @@ class Electrum {
* Limited to 20 items per request. * Limited to 20 items per request.
* *
* @apiExample Example usage: * @apiExample Example usage:
* curl -X POST "https://api.fullstack.cash/v4/electrumx/transactions" -H "accept: application/json" -H "Content-Type: application/json" -d '{"addresses":["bitcoincash:qrdka2205f4hyukutc2g0s6lykperc8nsu5u2ddpqf","bitcoincash:qrdka2205f4hyukutc2g0s6lykperc8nsu5u2ddpqf"]}' * curl -X POST "https://api.fullstack.cash/v3/electrumx/transactions" -H "accept: application/json" -H "Content-Type: application/json" -d '{"addresses":["bitcoincash:qrdka2205f4hyukutc2g0s6lykperc8nsu5u2ddpqf","bitcoincash:qrdka2205f4hyukutc2g0s6lykperc8nsu5u2ddpqf"]}'
* *
* *
*/ */
@@ -1192,7 +1174,7 @@ class Electrum {
* *
* *
* @apiExample Example usage: * @apiExample Example usage:
* curl -X GET "https://api.fullstack.cash/v4/electrumx/unconfirmed/bitcoincash:qr69kyzha07dcecrsvjwsj4s6slnlq4r8c30lxnur3" -H "accept: application/json" * curl -X GET "https://api.fullstack.cash/v3/electrumx/unconfirmed/bitcoincash:qr69kyzha07dcecrsvjwsj4s6slnlq4r8c30lxnur3" -H "accept: application/json"
* *
*/ */
// GET handler for single balance // GET handler for single balance
@@ -1262,7 +1244,7 @@ class Electrum {
* Limited to 20 items per request. * Limited to 20 items per request.
* *
* @apiExample Example usage: * @apiExample Example usage:
* curl -X POST "https://api.fullstack.cash/v4/electrumx/unconfirmed" -H "accept: application/json" -H "Content-Type: application/json" -d '{"addresses":["bitcoincash:qrdka2205f4hyukutc2g0s6lykperc8nsu5u2ddpqf","bitcoincash:qrdka2205f4hyukutc2g0s6lykperc8nsu5u2ddpqf"]}' * curl -X POST "https://api.fullstack.cash/v3/electrumx/unconfirmed" -H "accept: application/json" -H "Content-Type: application/json" -d '{"addresses":["bitcoincash:qrdka2205f4hyukutc2g0s6lykperc8nsu5u2ddpqf","bitcoincash:qrdka2205f4hyukutc2g0s6lykperc8nsu5u2ddpqf"]}'
* *
* *
*/ */
@@ -16,10 +16,15 @@ const RouteUtils = require('../../util/route-utils')
const routeUtils = new RouteUtils() const routeUtils = new RouteUtils()
const BCHJS = require('@psf/bch-js') const BCHJS = require('@psf/bch-js')
const restURL = process.env.LOCAL_RESTURL const bchjs = new BCHJS()
? process.env.LOCAL_RESTURL
: 'https://api.fullstack.cash/v4/' // Use Blockbook for getting indexer data.
const bchjs = new BCHJS({ restURL }) const Blockbook = require('./blockbook')
const blockbook = new Blockbook()
// Use RawTransactions library for getting raw blockchain data.
const RawTransactions = require('./full-node/rawtransactions')
const rawTransactions = new RawTransactions()
let _this let _this
@@ -31,8 +36,8 @@ class Encryption {
_this.axios = axios _this.axios = axios
_this.routeUtils = routeUtils _this.routeUtils = routeUtils
_this.bchjs = bchjs _this.bchjs = bchjs
// _this.blockbook = blockbook _this.blockbook = blockbook
// _this.rawTransactions = rawTransactions _this.rawTransactions = rawTransactions
_this.router = router _this.router = router
_this.router.get('/', _this.root) _this.router.get('/', _this.root)
@@ -45,7 +50,7 @@ class Encryption {
const { msg, status } = _this.routeUtils.decodeError(err) const { msg, status } = _this.routeUtils.decodeError(err)
if (msg) { if (msg) {
res.status(status) res.status(status)
return res.json({ success: false, error: msg }) return res.json({ error: msg })
} }
// Handle error patterns specific to this route. // Handle error patterns specific to this route.
@@ -76,7 +81,7 @@ class Encryption {
* *
* *
* @apiExample Example usage: * @apiExample Example usage:
* curl -X GET "https://api.fullstack.cash/v4/encryption/publickey/bitcoincash:qrdka2205f4hyukutc2g0s6lykperc8nsu5u2ddpqf" -H "accept: application/json" * curl -X GET "https://api.fullstack.cash/v3/encryption/publickey/bitcoincash:qrdka2205f4hyukutc2g0s6lykperc8nsu5u2ddpqf" -H "accept: application/json"
* *
*/ */
async getPublicKey (req, res, next) { async getPublicKey (req, res, next) {
@@ -92,15 +97,6 @@ class Encryption {
}) })
} }
// Generate a user object that can be passed along with internal calls
// from bch-js.
const usrObj = {
ip: req._remoteAddress,
jwtToken: req.locals.jwtToken,
proLimit: req.locals.proLimit,
apiLevel: req.locals.apiLevel
}
const cashAddr = _this.bchjs.Address.toCashAddress(address) const cashAddr = _this.bchjs.Address.toCashAddress(address)
// Prevent a common user error. Ensure they are using the correct network address. // Prevent a common user error. Ensure they are using the correct network address.
@@ -119,28 +115,25 @@ class Encryption {
cashAddr cashAddr
) )
const rawTxData = await _this.bchjs.Electrumx.transactions(cashAddr, usrObj) // Retrieve the transaction history for this address.
// console.log(`rawTxData: ${JSON.stringify(rawTxData, null, 2)}`) const balance = await _this.blockbook.balanceFromBlockbook(cashAddr)
// console.log(`balance: ${JSON.stringify(balance, null, 2)}`)
// Extract just the TXIDs const txHistory = balance.txids
const txids = rawTxData.transactions.map((elem) => elem.tx_hash) // console.log(`txHistory: ${JSON.stringify(txHistory, null, 2)}`)
// console.log(`txids: ${JSON.stringify(txids, null, 2)}`)
// throw error if there is no transaction history. // throw error if there is no transaction history.
if (!txids || txids.length === 0) { if (!txHistory || txHistory.length === 0) {
throw new Error('No transaction history.') throw new Error('No transaction history.')
} }
// Loop through the transaction history and search for the public key. // Loop through the transaction history and search for the public key.
for (let i = 0; i < txids.length; i++) { for (let i = 0; i < txHistory.length; i++) {
const thisTx = txids[i] const thisTx = txHistory[i]
// CT 2/24/21: I might want to convert this to the POST call, to take const txDetails = await _this.rawTransactions.getRawTransactionsFromNode(
// advantage of the usrObj. It does not get passed in a GET call.
const txDetails = await _this.bchjs.RawTransactions.getRawTransaction(
thisTx, thisTx,
true, true
usrObj
) )
// console.log(`txDetails: ${JSON.stringify(txDetails, null, 2)}`) // console.log(`txDetails: ${JSON.stringify(txDetails, null, 2)}`)
@@ -80,7 +80,7 @@ class Blockchain {
* block chain. * block chain.
* *
* @apiExample Example usage: * @apiExample Example usage:
* curl -X GET "https://api.fullstack.cash/v4/blockchain/getBestBlockHash" -H "accept: application/json" * curl -X GET "https://api.fullstack.cash/v3/blockchain/getBestBlockHash" -H "accept: application/json"
* *
* @apiSuccess {String} bestBlockHash 000000000000000002bc884334336d99c9a9c616670a9244c6a8c1fc35aa91a1 * @apiSuccess {String} bestBlockHash 000000000000000002bc884334336d99c9a9c616670a9244c6a8c1fc35aa91a1
*/ */
@@ -111,7 +111,7 @@ class Blockchain {
* @apiDescription Returns an object containing various state info regarding blockchain processing. * @apiDescription Returns an object containing various state info regarding blockchain processing.
* *
* @apiExample Example usage: * @apiExample Example usage:
* curl -X GET "https://api.fullstack.cash/v4/blockchain/getBlockchainInfo" -H "accept: application/json" * curl -X GET "https://api.fullstack.cash/v3/blockchain/getBlockchainInfo" -H "accept: application/json"
* *
* @apiSuccess {Object} object Object containing data * @apiSuccess {Object} object Object containing data
* @apiSuccess {String} object.chain "main" * @apiSuccess {String} object.chain "main"
@@ -155,7 +155,7 @@ class Blockchain {
* @apiDescription Returns the number of blocks in the longest blockchain. * @apiDescription Returns the number of blocks in the longest blockchain.
* *
* @apiExample Example usage: * @apiExample Example usage:
* curl -X GET "https://api.fullstack.cash/v4/blockchain/getBlockCount" -H "accept: application/json" * curl -X GET "https://api.fullstack.cash/v3/blockchain/getBlockCount" -H "accept: application/json"
* *
* @apiSuccess {Number} bestBlockCount 587665 * @apiSuccess {Number} bestBlockCount 587665
*/ */
@@ -188,7 +188,7 @@ class Blockchain {
* returns an Object with information about blockheader hash. * returns an Object with information about blockheader hash.
* *
* @apiExample Example usage: * @apiExample Example usage:
* curl -X GET "https://api.fullstack.cash/v4/blockchain/getBlockHeader/000000000000000005e14d3f9fdfb70745308706615cfa9edca4f4558332b201?verbose=true" -H "accept: application/json" * curl -X GET "https://api.fullstack.cash/v3/blockchain/getBlockHeader/000000000000000005e14d3f9fdfb70745308706615cfa9edca4f4558332b201?verbose=true" -H "accept: application/json"
* *
* @apiParam {String} hash block hash * @apiParam {String} hash block hash
* @apiParam {Boolean} verbose Return verbose data * @apiParam {Boolean} verbose Return verbose data
@@ -249,7 +249,7 @@ class Blockchain {
* returns an Object with information about blockheader hash. * returns an Object with information about blockheader hash.
* *
* @apiExample Example usage: * @apiExample Example usage:
* curl -X POST "https://api.fullstack.cash/v4/blockchain/getBlockHeader" -H "accept: application/json" -H "Content-Type: application/json" -d "{\"hashes\":[\"000000000000000005e14d3f9fdfb70745308706615cfa9edca4f4558332b201\",\"00000000000000000568f0a96bf4348847bc84e455cbfec389f27311037a20f3\"],\"verbose\":true}" * curl -X POST "https://api.fullstack.cash/v3/blockchain/getBlockHeader" -H "accept: application/json" -H "Content-Type: application/json" -d "{\"hashes\":[\"000000000000000005e14d3f9fdfb70745308706615cfa9edca4f4558332b201\",\"00000000000000000568f0a96bf4348847bc84e455cbfec389f27311037a20f3\"],\"verbose\":true}"
* *
* @apiParam {String} hash block hash * @apiParam {String} hash block hash
* @apiParam {Boolean} verbose Return verbose data * @apiParam {Boolean} verbose Return verbose data
@@ -341,7 +341,7 @@ class Blockchain {
* including the main chain as well as orphaned branches. * including the main chain as well as orphaned branches.
* *
* @apiExample Example usage: * @apiExample Example usage:
* curl -X GET "https://api.fullstack.cash/v4/blockchain/getChainTips" -H "accept: application/json" * curl -X GET "https://api.fullstack.cash/v3/blockchain/getChainTips" -H "accept: application/json"
* *
*/ */
async getChainTips (req, res, next) { async getChainTips (req, res, next) {
@@ -372,7 +372,7 @@ class Blockchain {
* power on the network. * power on the network.
* *
* @apiExample Example usage: * @apiExample Example usage:
* curl -X GET "https://api.fullstack.cash/v4/blockchain/getDifficulty" -H "accept: application/json" * curl -X GET "https://api.fullstack.cash/v3/blockchain/getDifficulty" -H "accept: application/json"
* *
*/ */
async getDifficulty (req, res, next) { async getDifficulty (req, res, next) {
@@ -404,7 +404,7 @@ class Blockchain {
* mempool (unconfirmed) * mempool (unconfirmed)
* *
* @apiExample Example usage: * @apiExample Example usage:
* curl -X GET "https://api.fullstack.cash/v4/blockchain/getMempoolEntry/fe28050b93faea61fa88c4c630f0e1f0a1c24d0082dd0e10d369e13212128f33" -H "accept: application/json" * curl -X GET "https://api.fullstack.cash/v3/blockchain/getMempoolEntry/fe28050b93faea61fa88c4c630f0e1f0a1c24d0082dd0e10d369e13212128f33" -H "accept: application/json"
* *
*/ */
async getMempoolEntrySingle (req, res, next) { async getMempoolEntrySingle (req, res, next) {
@@ -442,7 +442,7 @@ class Blockchain {
* @apiDescription Returns mempool data for multiple transactions * @apiDescription Returns mempool data for multiple transactions
* *
* @apiExample Example usage: * @apiExample Example usage:
* curl -X POST https://api.fullstack.cash/v4/blockchain/getMempoolEntry -H "Content-Type: application/json" -d "{\"txids\":[\"a5f972572ee1753e2fd2457dd61ce5f40fa2f8a30173d417e49feef7542c96a1\",\"5165dc531aad05d1149bb0f0d9b7bda99c73e2f05e314bcfb5b4bb9ca5e1af5e\"]}" * curl -X POST https://api.fullstack.cash/v3/blockchain/getMempoolEntry -H "Content-Type: application/json" -d "{\"txids\":[\"a5f972572ee1753e2fd2457dd61ce5f40fa2f8a30173d417e49feef7542c96a1\",\"5165dc531aad05d1149bb0f0d9b7bda99c73e2f05e314bcfb5b4bb9ca5e1af5e\"]}"
*/ */
async getMempoolEntryBulk (req, res, next) { async getMempoolEntryBulk (req, res, next) {
try { try {
@@ -515,7 +515,7 @@ class Blockchain {
* against the 25 ancestor chain-limit. * against the 25 ancestor chain-limit.
* *
* @apiExample Example usage: * @apiExample Example usage:
* curl -X GET "https://api.fullstack.cash/v4/blockchain/getMempoolAncestors/fe28050b93faea61fa88c4c630f0e1f0a1c24d0082dd0e10d369e13212128f33" -H "accept: application/json" * curl -X GET "https://api.fullstack.cash/v3/blockchain/getMempoolAncestors/fe28050b93faea61fa88c4c630f0e1f0a1c24d0082dd0e10d369e13212128f33" -H "accept: application/json"
* *
*/ */
async getMempoolAncestorsSingle (req, res, next) { async getMempoolAncestorsSingle (req, res, next) {
@@ -557,7 +557,7 @@ class Blockchain {
* @apiDescription Returns details on the active state of the TX memory pool. * @apiDescription Returns details on the active state of the TX memory pool.
* *
* @apiExample Example usage: * @apiExample Example usage:
* curl -X GET https://api.fullstack.cash/v4/getMempoolInfo -H "accept: application/json" * curl -X GET https://api.fullstack.cash/v3/getMempoolInfo -H "accept: application/json"
* *
*/ */
async getMempoolInfo (req, res, next) { async getMempoolInfo (req, res, next) {
@@ -587,7 +587,7 @@ class Blockchain {
* @apiDescription Returns details on the active state of the TX memory pool. * @apiDescription Returns details on the active state of the TX memory pool.
* *
* @apiExample Example usage: * @apiExample Example usage:
* curl -X GET https://api.fullstack.cash/v4/getMempoolInfo -H "accept: application/json" * curl -X GET https://api.fullstack.cash/v3/getMempoolInfo -H "accept: application/json"
* *
*/ */
@@ -599,7 +599,7 @@ class Blockchain {
* of string transaction ids. * of string transaction ids.
* *
* @apiExample Example usage: * @apiExample Example usage:
* curl -X GET "https://api.fullstack.cash/v4/getRawMempool/?verbose=true" -H "accept: application/json" * curl -X GET "https://api.fullstack.cash/v3/getRawMempool/?verbose=true" -H "accept: application/json"
* *
* @apiParam {Boolean} verbose Return verbose data * @apiParam {Boolean} verbose Return verbose data
* *
@@ -635,7 +635,7 @@ class Blockchain {
* @apiDescription Returns details about an unspent transaction output. * @apiDescription Returns details about an unspent transaction output.
* *
* @apiExample Example usage: * @apiExample Example usage:
* curl -X GET "https://api.fullstack.cash/v4/blockchain/getTxOut/fe28050b93faea61fa88c4c630f0e1f0a1c24d0082dd0e10d369e13212128f33/0?mempool=false" -H "accept: application/json" * curl -X GET "https://api.fullstack.cash/v3/blockchain/getTxOut/fe28050b93faea61fa88c4c630f0e1f0a1c24d0082dd0e10d369e13212128f33/0?mempool=false" -H "accept: application/json"
* *
* @apiParam {String} txid Transaction id (required) * @apiParam {String} txid Transaction id (required)
* @apiParam {Number} n Output number (required) * @apiParam {Number} n Output number (required)
@@ -692,7 +692,7 @@ class Blockchain {
* @apiDescription Returns details about an unspent transaction output (UTXO). * @apiDescription Returns details about an unspent transaction output (UTXO).
* *
* @apiExample Example usage: * @apiExample Example usage:
* curl "https://api.fullstack.cash/v4/blockchain/getTxOut/" -X POST -H "Content-Type: application/json" --data-binary '{"txid":"d5228d2cdc77fbe5a9aa79f19b0933b6802f9f0067f42847fc4fe343664723e5","vout":0,"mempool":true}' * curl "https://api.fullstack.cash/v3/blockchain/getTxOut/" -X POST -H "Content-Type: application/json" --data-binary '{"txid":"d5228d2cdc77fbe5a9aa79f19b0933b6802f9f0067f42847fc4fe343664723e5","vout":0,"mempool":true}'
* *
* @apiParam {String} txid Transaction id (required) * @apiParam {String} txid Transaction id (required)
* @apiParam {Number} vout of transaction (required) * @apiParam {Number} vout of transaction (required)
@@ -746,7 +746,7 @@ class Blockchain {
* @apiDescription Returns a hex-encoded proof that 'txid' was included in a block. * @apiDescription Returns a hex-encoded proof that 'txid' was included in a block.
* *
* @apiExample Example usage: * @apiExample Example usage:
* curl -X GET "https://api.fullstack.cash/v4/blockchain/getTxOutProofSingle/fe28050b93faea61fa88c4c630f0e1f0a1c24d0082dd0e10d369e13212128f33" -H "accept: application/json" * curl -X GET "https://api.fullstack.cash/v3/blockchain/getTxOutProofSingle/fe28050b93faea61fa88c4c630f0e1f0a1c24d0082dd0e10d369e13212128f33" -H "accept: application/json"
* *
* @apiParam {String} txid Transaction id (required) * @apiParam {String} txid Transaction id (required)
* *
@@ -53,7 +53,7 @@ class Control {
* @apiDescription RPC call which gets basic full node information. * @apiDescription RPC call which gets basic full node information.
* *
* @apiExample Example usage: * @apiExample Example usage:
* curl -X GET "https://api.fullstack.cash/v4/control/getnetworkinfo" -H "accept: application/json" * curl -X GET "https://api.fullstack.cash/v3/control/getnetworkinfo" -H "accept: application/json"
* *
*/ */
async getNetworkInfo (req, res, next) { async getNetworkInfo (req, res, next) {
@@ -77,7 +77,7 @@ class Mining {
* *
* *
* @apiExample Example usage: * @apiExample Example usage:
* curl -X GET "https://api.fullstack.cash/v4/mining/getMiningInfo" -H "accept: application/json" * curl -X GET "https://api.fullstack.cash/v3/mining/getMiningInfo" -H "accept: application/json"
* *
* *
*/ */
@@ -107,7 +107,7 @@ class Mining {
* *
* *
* @apiExample Example usage: * @apiExample Example usage:
* curl -X GET "https://api.fullstack.cash/v4/mining/getNetworkHashps?nblocks=120&height=-1" -H "accept: application/json" * curl -X GET "https://api.fullstack.cash/v3/mining/getNetworkHashps?nblocks=120&height=-1" -H "accept: application/json"
* *
* *
*/ */
@@ -65,7 +65,7 @@ class RawTransactions {
* *
* *
* @apiExample Example usage: * @apiExample Example usage:
* curl -X GET "https://api.fullstack.cash/v4/rawtransactions/decodeRawTransaction/02000000010e991f7ccec410f27d333f737f149b5d3be6728687da81072e638aed0063a176010000006b483045022100cd20443b0af090053450bc4ab00d563d4ac5955bb36e0135b00b8a96a19f233302205047f2c70a08c6ef4b76f2d198b33a31d17edfaa7e1e9e865894da0d396009354121024d4e7f522f67105b7bf5f9dbe557e7b2244613fdfcd6fe09304f93877328f6beffffffff02a0860100000000001976a9140ee020c07f39526ac5505c54fa1ab98490979b8388acb5f0f70b000000001976a9143a9b2b0c12fe722fcf653b6ef5dcc38732d6ff5188ac00000000" -H "accept: application/json" * curl -X GET "https://api.fullstack.cash/v3/rawtransactions/decodeRawTransaction/02000000010e991f7ccec410f27d333f737f149b5d3be6728687da81072e638aed0063a176010000006b483045022100cd20443b0af090053450bc4ab00d563d4ac5955bb36e0135b00b8a96a19f233302205047f2c70a08c6ef4b76f2d198b33a31d17edfaa7e1e9e865894da0d396009354121024d4e7f522f67105b7bf5f9dbe557e7b2244613fdfcd6fe09304f93877328f6beffffffff02a0860100000000001976a9140ee020c07f39526ac5505c54fa1ab98490979b8388acb5f0f70b000000001976a9143a9b2b0c12fe722fcf653b6ef5dcc38732d6ff5188ac00000000" -H "accept: application/json"
*/ */
async decodeRawTransactionSingle (req, res, next) { async decodeRawTransactionSingle (req, res, next) {
try { try {
@@ -103,7 +103,7 @@ class RawTransactions {
* *
* *
* @apiExample Example usage: * @apiExample Example usage:
* curl -X POST "https://api.fullstack.cash/v4/rawtransactions/decodeRawTransaction" -H "accept: application/json" -H "Content-Type: application/json" -d '{"hexes":["01000000013ba3edfd7a7b12b27ac72c3e67768f617fc81bc3888a51323a9fb8aa4b1e5e4a000000006a4730440220540986d1c58d6e76f8f05501c520c38ce55393d0ed7ed3c3a82c69af04221232022058ea43ed6c05fec0eccce749a63332ed4525460105346f11108b9c26df93cd72012103083dfc5a0254613941ddc91af39ff90cd711cdcde03a87b144b883b524660c39ffffffff01807c814a000000001976a914d7e7c4e0b70eaa67ceff9d2823d1bbb9f6df9a5188ac00000000","01000000013ba3edfd7a7b12b27ac72c3e67768f617fc81bc3888a51323a9fb8aa4b1e5e4a000000006a4730440220540986d1c58d6e76f8f05501c520c38ce55393d0ed7ed3c3a82c69af04221232022058ea43ed6c05fec0eccce749a63332ed4525460105346f11108b9c26df93cd72012103083dfc5a0254613941ddc91af39ff90cd711cdcde03a87b144b883b524660c39ffffffff01807c814a000000001976a914d7e7c4e0b70eaa67ceff9d2823d1bbb9f6df9a5188ac00000000"]}' * curl -X POST "https://api.fullstack.cash/v3/rawtransactions/decodeRawTransaction" -H "accept: application/json" -H "Content-Type: application/json" -d '{"hexes":["01000000013ba3edfd7a7b12b27ac72c3e67768f617fc81bc3888a51323a9fb8aa4b1e5e4a000000006a4730440220540986d1c58d6e76f8f05501c520c38ce55393d0ed7ed3c3a82c69af04221232022058ea43ed6c05fec0eccce749a63332ed4525460105346f11108b9c26df93cd72012103083dfc5a0254613941ddc91af39ff90cd711cdcde03a87b144b883b524660c39ffffffff01807c814a000000001976a914d7e7c4e0b70eaa67ceff9d2823d1bbb9f6df9a5188ac00000000","01000000013ba3edfd7a7b12b27ac72c3e67768f617fc81bc3888a51323a9fb8aa4b1e5e4a000000006a4730440220540986d1c58d6e76f8f05501c520c38ce55393d0ed7ed3c3a82c69af04221232022058ea43ed6c05fec0eccce749a63332ed4525460105346f11108b9c26df93cd72012103083dfc5a0254613941ddc91af39ff90cd711cdcde03a87b144b883b524660c39ffffffff01807c814a000000001976a914d7e7c4e0b70eaa67ceff9d2823d1bbb9f6df9a5188ac00000000"]}'
* *
* *
*/ */
@@ -177,7 +177,7 @@ class RawTransactions {
* *
* *
* @apiExample Example usage: * @apiExample Example usage:
* curl -X GET "https://api.fullstack.cash/v4/rawtransactions/decodeScript/4830450221009a51e00ec3524a7389592bc27bea4af5104a59510f5f0cfafa64bbd5c164ca2e02206c2a8bbb47eabdeed52f17d7df668d521600286406930426e3a9415fe10ed592012102e6e1423f7abde8b70bca3e78a7d030e5efabd3eb35c19302542b5fe7879c1a16" -H "accept: application/json" * curl -X GET "https://api.fullstack.cash/v3/rawtransactions/decodeScript/4830450221009a51e00ec3524a7389592bc27bea4af5104a59510f5f0cfafa64bbd5c164ca2e02206c2a8bbb47eabdeed52f17d7df668d521600286406930426e3a9415fe10ed592012102e6e1423f7abde8b70bca3e78a7d030e5efabd3eb35c19302542b5fe7879c1a16" -H "accept: application/json"
* *
* *
*/ */
@@ -218,7 +218,7 @@ class RawTransactions {
* *
* *
* @apiExample Example usage: * @apiExample Example usage:
*curl -X POST "https://api.fullstack.cash/v4/rawtransactions/decodeScript" -H "accept:" -H "Content-Type: application/json" -d '{"hexes":["01000000013ba3edfd7a7b12b27ac72c3e67768f617fc81bc3888a51323a9fb8aa4b1e5e4a000000006a4730440220540986d1c58d6e76f8f05501c520c38ce55393d0ed7ed3c3a82c69af04221232022058ea43ed6c05fec0eccce749a63332ed4525460105346f11108b9c26df93cd72012103083dfc5a0254613941ddc91af39ff90cd711cdcde03a87b144b883b524660c39ffffffff01807c814a000000001976a914d7e7c4e0b70eaa67ceff9d2823d1bbb9f6df9a5188ac00000000","01000000013ba3edfd7a7b12b27ac72c3e67768f617fc81bc3888a51323a9fb8aa4b1e5e4a000000006a4730440220540986d1c58d6e76f8f05501c520c38ce55393d0ed7ed3c3a82c69af04221232022058ea43ed6c05fec0eccce749a63332ed4525460105346f11108b9c26df93cd72012103083dfc5a0254613941ddc91af39ff90cd711cdcde03a87b144b883b524660c39ffffffff01807c814a000000001976a914d7e7c4e0b70eaa67ceff9d2823d1bbb9f6df9a5188ac00000000"]}' *curl -X POST "https://api.fullstack.cash/v3/rawtransactions/decodeScript" -H "accept:" -H "Content-Type: application/json" -d '{"hexes":["01000000013ba3edfd7a7b12b27ac72c3e67768f617fc81bc3888a51323a9fb8aa4b1e5e4a000000006a4730440220540986d1c58d6e76f8f05501c520c38ce55393d0ed7ed3c3a82c69af04221232022058ea43ed6c05fec0eccce749a63332ed4525460105346f11108b9c26df93cd72012103083dfc5a0254613941ddc91af39ff90cd711cdcde03a87b144b883b524660c39ffffffff01807c814a000000001976a914d7e7c4e0b70eaa67ceff9d2823d1bbb9f6df9a5188ac00000000","01000000013ba3edfd7a7b12b27ac72c3e67768f617fc81bc3888a51323a9fb8aa4b1e5e4a000000006a4730440220540986d1c58d6e76f8f05501c520c38ce55393d0ed7ed3c3a82c69af04221232022058ea43ed6c05fec0eccce749a63332ed4525460105346f11108b9c26df93cd72012103083dfc5a0254613941ddc91af39ff90cd711cdcde03a87b144b883b524660c39ffffffff01807c814a000000001976a914d7e7c4e0b70eaa67ceff9d2823d1bbb9f6df9a5188ac00000000"]}'
* *
* *
*/ */
@@ -308,7 +308,7 @@ class RawTransactions {
* *
* *
* @apiExample Example usage: * @apiExample Example usage:
* curl -X POST "https://api.fullstack.cash/v4/rawtransactions/getRawTransaction" -H "accept: application/json" -H "Content-Type: application/json" -d '{"txids":["a5f972572ee1753e2fd2457dd61ce5f40fa2f8a30173d417e49feef7542c96a1","5165dc531aad05d1149bb0f0d9b7bda99c73e2f05e314bcfb5b4bb9ca5e1af5e"],"verbose":true}' * curl -X POST "https://api.fullstack.cash/v3/rawtransactions/getRawTransaction" -H "accept: application/json" -H "Content-Type: application/json" -d '{"txids":["a5f972572ee1753e2fd2457dd61ce5f40fa2f8a30173d417e49feef7542c96a1","5165dc531aad05d1149bb0f0d9b7bda99c73e2f05e314bcfb5b4bb9ca5e1af5e"],"verbose":true}'
* *
*/ */
async getRawTransactionBulk (req, res, next) { async getRawTransactionBulk (req, res, next) {
@@ -383,7 +383,7 @@ class RawTransactions {
* *
* *
* @apiExample Example usage: * @apiExample Example usage:
* curl -X GET "https://api.fullstack.cash/v4/rawtransactions/getRawTransaction/fe28050b93faea61fa88c4c630f0e1f0a1c24d0082dd0e10d369e13212128f33?verbose=true" -H "accept: application/json" * curl -X GET "https://api.fullstack.cash/v3/rawtransactions/getRawTransaction/fe28050b93faea61fa88c4c630f0e1f0a1c24d0082dd0e10d369e13212128f33?verbose=true" -H "accept: application/json"
* *
* *
*/ */
@@ -429,7 +429,7 @@ class RawTransactions {
* *
* *
* @apiExample Example usage: * @apiExample Example usage:
* curl -X POST "https://api.fullstack.cash/v4/rawtransactions/sendRawTransaction" -H "accept:application/json" -H "Content-Type: application/json" -d '{"hexes":["01000000013ba3edfd7a7b12b27ac72c3e67768f617fc81bc3888a51323a9fb8aa4b1e5e4a000000006a4730440220540986d1c58d6e76f8f05501c520c38ce55393d0ed7ed3c3a82c69af04221232022058ea43ed6c05fec0eccce749a63332ed4525460105346f11108b9c26df93cd72012103083dfc5a0254613941ddc91af39ff90cd711cdcde03a87b144b883b524660c39ffffffff01807c814a000000001976a914d7e7c4e0b70eaa67ceff9d2823d1bbb9f6df9a5188ac00000000","01000000013ba3edfd7a7b12b27ac72c3e67768f617fc81bc3888a51323a9fb8aa4b1e5e4a000000006a4730440220540986d1c58d6e76f8f05501c520c38ce55393d0ed7ed3c3a82c69af04221232022058ea43ed6c05fec0eccce749a63332ed4525460105346f11108b9c26df93cd72012103083dfc5a0254613941ddc91af39ff90cd711cdcde03a87b144b883b524660c39ffffffff01807c814a000000001976a914d7e7c4e0b70eaa67ceff9d2823d1bbb9f6df9a5188ac00000000"]}' * curl -X POST "https://api.fullstack.cash/v3/rawtransactions/sendRawTransaction" -H "accept:application/json" -H "Content-Type: application/json" -d '{"hexes":["01000000013ba3edfd7a7b12b27ac72c3e67768f617fc81bc3888a51323a9fb8aa4b1e5e4a000000006a4730440220540986d1c58d6e76f8f05501c520c38ce55393d0ed7ed3c3a82c69af04221232022058ea43ed6c05fec0eccce749a63332ed4525460105346f11108b9c26df93cd72012103083dfc5a0254613941ddc91af39ff90cd711cdcde03a87b144b883b524660c39ffffffff01807c814a000000001976a914d7e7c4e0b70eaa67ceff9d2823d1bbb9f6df9a5188ac00000000","01000000013ba3edfd7a7b12b27ac72c3e67768f617fc81bc3888a51323a9fb8aa4b1e5e4a000000006a4730440220540986d1c58d6e76f8f05501c520c38ce55393d0ed7ed3c3a82c69af04221232022058ea43ed6c05fec0eccce749a63332ed4525460105346f11108b9c26df93cd72012103083dfc5a0254613941ddc91af39ff90cd711cdcde03a87b144b883b524660c39ffffffff01807c814a000000001976a914d7e7c4e0b70eaa67ceff9d2823d1bbb9f6df9a5188ac00000000"]}'
* *
* *
*/ */
@@ -522,7 +522,7 @@ class RawTransactions {
* *
* *
* @apiExample Example usage: * @apiExample Example usage:
* curl -X GET "https://api.fullstack.cash/v4/rawtransactions/sendRawTransaction/01000000013ba3edfd7a7b12b27ac72c3e67768f617fc81bc3888a51323a9fb8aa4b1e5e4a000000006a4730440220540986d1c58d6e76f8f05501c520c38ce55393d0ed7ed3c3a82c69af04221232022058ea43ed6c05fec0eccce749a63332ed4525460105346f11108b9c26df93cd72012103083dfc5a0254613941ddc91af39ff90cd711cdcde03a87b144b883b524660c39ffffffff01807c814a000000001976a914d7e7c4e0b70eaa67ceff9d2823d1bbb9f6df9a5188ac00000000" -H "accept: " * curl -X GET "https://api.fullstack.cash/v3/rawtransactions/sendRawTransaction/01000000013ba3edfd7a7b12b27ac72c3e67768f617fc81bc3888a51323a9fb8aa4b1e5e4a000000006a4730440220540986d1c58d6e76f8f05501c520c38ce55393d0ed7ed3c3a82c69af04221232022058ea43ed6c05fec0eccce749a63332ed4525460105346f11108b9c26df93cd72012103083dfc5a0254613941ddc91af39ff90cd711cdcde03a87b144b883b524660c39ffffffff01807c814a000000001976a914d7e7c4e0b70eaa67ceff9d2823d1bbb9f6df9a5188ac00000000" -H "accept: "
* *
* *
*/ */
@@ -24,17 +24,11 @@ class Price {
this.routeUtils = routeUtils this.routeUtils = routeUtils
this.priceUrl = 'https://api.coinbase.com/v2/exchange-rates?currency=BCH' this.priceUrl = 'https://api.coinbase.com/v2/exchange-rates?currency=BCH'
this.coinexPriceUrl =
'https://api.coinex.com/v1/market/ticker?market=bchausdt'
this.bchCoinexPriceUrl =
'https://api.coinex.com/v1/market/ticker?market=bchusdt'
this.router = express.Router() this.router = express.Router()
this.router.get('/', _this.root) this.router.get('/', _this.root)
this.router.get('/usd', _this.getUSD) this.router.get('/usd', _this.getUSD)
this.router.get('/rates', _this.getBCHRate) this.router.get('/rates', _this.getBCHRate)
this.router.get('/bchausd', _this.getBCHAUSD)
} }
// DRY error handler. // DRY error handler.
@@ -59,11 +53,11 @@ class Price {
* @api {get} /price/usd Get the USD price of BCH * @api {get} /price/usd Get the USD price of BCH
* @apiName Get the USD price of BCH * @apiName Get the USD price of BCH
* @apiGroup Price * @apiGroup Price
* @apiDescription Get the USD price of BCH from Coinbase. * @apiDescription Get the USD price of BCH
* *
* *
* @apiExample Example usage: * @apiExample Example usage:
* curl -X GET "https://api.fullstack.cash/v4/price/usd" -H "accept: application/json" * curl -X GET "https://api.fullstack.cash/v3/price/usd" -H "accept: application/json"
* *
*/ */
async getUSD (req, res, next) { async getUSD (req, res, next) {
@@ -91,11 +85,11 @@ class Price {
* @api {get} /price/usd Get rates for several different currencies * @api {get} /price/usd Get rates for several different currencies
* @apiName Get rates for several different currencies * @apiName Get rates for several different currencies
* @apiGroup Price * @apiGroup Price
* @apiDescription Get rates for several different currencies from Coinbase. * @apiDescription Get rates for several different currencies
* *
* *
* @apiExample Example usage: * @apiExample Example usage:
* curl -X GET "https://api.fullstack.cash/v4/price/rates" -H "accept: application/json" * curl -X GET "https://api.fullstack.cash/v3/price/rates" -H "accept: application/json"
* *
*/ */
// Get rates for several different currencies // Get rates for several different currencies
@@ -119,74 +113,6 @@ class Price {
return _this.errorHandler(err, res) return _this.errorHandler(err, res)
} }
} }
/**
* @api {get} /price/bchausd Get the USD price of BCHA
* @apiName Get the USD price of BCHA
* @apiGroup Price
* @apiDescription Get the USD price of BCHA from Coinex.
*
*
* @apiExample Example usage:
* curl -X GET "https://api.fullstack.cash/v4/price/bchausd" -H "accept: application/json"
*
*/
async getBCHAUSD (req, res, next) {
try {
// Request options
const opt = {
method: 'get',
baseURL: _this.coinexPriceUrl,
timeout: 15000
}
const response = await axios.request(opt)
// console.log(`response.data: ${JSON.stringify(response.data, null, 2)}`)
const price = Number(response.data.data.ticker.last)
return res.json({ usd: price })
} catch (err) {
// Write out error to error log.
wlogger.error('Error in price.js/getBCHAUSD().', err)
return _this.errorHandler(err, res)
}
}
/**
* @api {get} /price/bchusd Get the USD price of BCH
* @apiName Get the USD price of BCH
* @apiGroup Price
* @apiDescription Get the USD price of BCH from Coinex.
*
*
* @apiExample Example usage:
* curl -X GET "https://api.fullstack.cash/v4/price/bchusd" -H "accept: application/json"
*
*/
async getBCHUSD (req, res, next) {
try {
// Request options
const opt = {
method: 'get',
baseURL: _this.bchCoinexPriceUrl,
timeout: 15000
}
const response = await axios.request(opt)
// console.log(`response.data: ${JSON.stringify(response.data, null, 2)}`)
const price = Number(response.data.data.ticker.last)
return res.json({ usd: price })
} catch (err) {
// Write out error to error log.
wlogger.error('Error in price.js/getBCHUSD().', err)
return _this.errorHandler(err, res)
}
}
} }
module.exports = Price module.exports = Price
@@ -175,17 +175,8 @@ function decodeError (err) {
} }
} }
// Handle general Error objects.
if (err.message) {
return {
message: err.message,
status: 422
}
}
return { msg: false, status: 500 } return { msg: false, status: 500 }
} catch (err) { } catch (err) {
console.error('unhandled error in route-utils.js/decodeError(): ', err)
wlogger.error('unhandled error in route-utils.js/decodeError(): ', err) wlogger.error('unhandled error in route-utils.js/decodeError(): ', err)
return { msg: false, status: 500 } return { msg: false, status: 500 }
} }
+41 -564
View File
@@ -16,10 +16,8 @@ const wlogger = require('../../util/winston-logging')
// Instantiate a local copy of bch-js using the local REST API server. // Instantiate a local copy of bch-js using the local REST API server.
const LOCAL_RESTURL = process.env.LOCAL_RESTURL const LOCAL_RESTURL = process.env.LOCAL_RESTURL
? process.env.LOCAL_RESTURL ? process.env.LOCAL_RESTURL
: 'https://api.fullstack.cash/v4/' : 'https://api.fullstack.cash/v3/'
const BCHJS = require('@psf/bch-js') const BCHJS = require('@psf/bch-js')
// const BCHJS = require('../../../../bch-js')
const bchjs = new BCHJS({ restURL: LOCAL_RESTURL }) const bchjs = new BCHJS({ restURL: LOCAL_RESTURL })
// Used to convert error messages to strings, to safely pass to users. // Used to convert error messages to strings, to safely pass to users.
@@ -35,10 +33,7 @@ util.inspect.defaultOptions = { depth: 5 }
// Determine the Access password for a private instance of SLPDB. // Determine the Access password for a private instance of SLPDB.
// https://gist.github.com/christroutner/fc717ca704dec3dded8b52fae387eab2 // https://gist.github.com/christroutner/fc717ca704dec3dded8b52fae387eab2
// Password for General Purpose (GP) SLPDB. const SLPDB_PASS = process.env.SLPDB_PASS ? process.env.SLPDB_PASS : 'BITBOX'
const SLPDB_PASS_GP = process.env.SLPDB_PASS_GP ? process.env.SLPDB_PASS_GP : 'BITBOX'
// Password for Whitelist (WL) SLPDB.
const SLPDB_PASS_WL = process.env.SLPDB_PASS_WL ? process.env.SLPDB_PASS_WL : 'BITBOX'
// const rawtransactions = require('./full-node/rawtransactions') // const rawtransactions = require('./full-node/rawtransactions')
const RawTransactions = require('./full-node/rawtransactions') const RawTransactions = require('./full-node/rawtransactions')
@@ -46,9 +41,9 @@ const rawTransactions = new RawTransactions()
// Setup REST and TREST URLs used by slpjs // Setup REST and TREST URLs used by slpjs
// Dev note: this allows for unit tests to mock the URL. // Dev note: this allows for unit tests to mock the URL.
if (!process.env.REST_URL) process.env.REST_URL = 'https://bchn.fullstack.cash/v4/' if (!process.env.REST_URL) process.env.REST_URL = 'https://rest.bitcoin.com/v2/'
if (!process.env.TREST_URL) { if (!process.env.TREST_URL) {
process.env.TREST_URL = 'https://testnet.fullstack.cash/v4/' process.env.TREST_URL = 'https://trest.bitcoin.com/v2/'
} }
let _this let _this
@@ -79,9 +74,6 @@ class Slp {
_this.router.post('/validateTxid', _this.validateBulk) _this.router.post('/validateTxid', _this.validateBulk)
_this.router.get('/validateTxid/:txid', _this.validateSingle) _this.router.get('/validateTxid/:txid', _this.validateSingle)
_this.router.get('/validateTxid2/:txid', _this.validate2Single) _this.router.get('/validateTxid2/:txid', _this.validate2Single)
_this.router.get('/validateTxid3/:txid', _this.validate3Single)
_this.router.post('/validateTxid3', _this.validate3Bulk)
_this.router.get('/whitelist', _this.getSlpWhitelist)
_this.router.get('/txDetails/:txid', _this.txDetails) _this.router.get('/txDetails/:txid', _this.txDetails)
_this.router.get('/tokenStats/:tokenId', _this.tokenStats) _this.router.get('/tokenStats/:tokenId', _this.tokenStats)
_this.router.get( _this.router.get(
@@ -94,8 +86,6 @@ class Slp {
) )
_this.router.post('/generateSendOpReturn', _this.generateSendOpReturn) _this.router.post('/generateSendOpReturn', _this.generateSendOpReturn)
_this.router.post('/hydrateUtxos', _this.hydrateUtxos) _this.router.post('/hydrateUtxos', _this.hydrateUtxos)
_this.router.post('/hydrateUtxosWL', _this.hydrateUtxosWL)
_this.router.get('/status', _this.getStatus)
} }
// DRY error handler. // DRY error handler.
@@ -164,7 +154,7 @@ class Slp {
* *
* *
* @apiExample Example usage: * @apiExample Example usage:
* curl -X GET "https://api.fullstack.cash/v4/slp/list/259908ae44f46ef585edef4bcc1e50dc06e4c391ac4be929fae27235b8158cf1" -H "accept:application/json" * curl -X GET "https://api.fullstack.cash/v3/slp/list/259908ae44f46ef585edef4bcc1e50dc06e4c391ac4be929fae27235b8158cf1" -H "accept:application/json"
* *
* *
*/ */
@@ -197,7 +187,7 @@ class Slp {
* *
* *
* @apiExample Example usage: * @apiExample Example usage:
* curl -X POST "https://api.fullstack.cash/v4/slp/list" -H "accept:application/json" -H "Content-Type: application/json" -d '{"tokenIds":["7380843cd1089a1a01783f86af37734dc99667a1cdc577391b5f6ea42fc1bfb4","9ba379fe8171176d4e7e6771d9a24cd0e044c7b788d5f86a3fdf80904832b2c0"]}' * curl -X POST "https://api.fullstack.cash/v3/slp/list" -H "accept:application/json" -H "Content-Type: application/json" -d '{"tokenIds":["7380843cd1089a1a01783f86af37734dc99667a1cdc577391b5f6ea42fc1bfb4","9ba379fe8171176d4e7e6771d9a24cd0e044c7b788d5f86a3fdf80904832b2c0"]}'
* *
* *
*/ */
@@ -345,7 +335,7 @@ class Slp {
* *
* *
* @apiExample Example usage: * @apiExample Example usage:
* curl -X GET "https://api.fullstack.cash/v4/slp/balancesForAddress/simpleledger:qz9tzs6d5097ejpg279rg0rnlhz546q4fsnck9wh5m" -H "accept:application/json" * curl -X GET "https://api.fullstack.cash/v3/slp/balancesForAddress/simpleledger:qz9tzs6d5097ejpg279rg0rnlhz546q4fsnck9wh5m" -H "accept:application/json"
* *
* *
*/ */
@@ -437,7 +427,7 @@ class Slp {
const b64 = Buffer.from(s).toString('base64') const b64 = Buffer.from(s).toString('base64')
const url = `${process.env.SLPDB_URL}q/${b64}` const url = `${process.env.SLPDB_URL}q/${b64}`
const options = _this.generateCredentialsGP() const options = _this.generateCredentials()
// Request options // Request options
const opt = { const opt = {
method: 'get', method: 'get',
@@ -529,7 +519,7 @@ class Slp {
* *
* *
* @apiExample Example usage: * @apiExample Example usage:
* curl -X POST "https://api.fullstack.cash/v4/slp/balancesForAddress" -d "{\"addresses\":[\"simpleledger:qqss4zp80hn6szsa4jg2s9fupe7g5tcg5ucdyl3r57\"]}" -H "accept:application/json" * curl -X POST "https://api.fullstack.cash/v3/slp/balancesForAddress" -d "{\"addresses\":[\"simpleledger:qqss4zp80hn6szsa4jg2s9fupe7g5tcg5ucdyl3r57\"]}" -H "accept:application/json"
* *
* *
*/ */
@@ -588,7 +578,7 @@ class Slp {
} }
} }
const options = _this.generateCredentialsGP() const options = _this.generateCredentials()
// Collect an array of promises, one for each request to slpserve. // Collect an array of promises, one for each request to slpserve.
// This is a nested array of promises. // This is a nested array of promises.
@@ -744,7 +734,7 @@ class Slp {
* *
* *
* @apiExample Example usage: * @apiExample Example usage:
* curl -X GET "https://api.fullstack.cash/v4/slp/balancesForToken/9ba379fe8171176d4e7e6771d9a24cd0e044c7b788d5f86a3fdf80904832b2c0" -H "accept:application/json" * curl -X GET "https://api.fullstack.cash/v3/slp/balancesForToken/9ba379fe8171176d4e7e6771d9a24cd0e044c7b788d5f86a3fdf80904832b2c0" -H "accept:application/json"
* *
* *
*/ */
@@ -810,7 +800,7 @@ class Slp {
const b64 = Buffer.from(s).toString('base64') const b64 = Buffer.from(s).toString('base64')
const url = `${process.env.SLPDB_URL}q/${b64}` const url = `${process.env.SLPDB_URL}q/${b64}`
const options = _this.generateCredentialsGP() const options = _this.generateCredentials()
const opt = { const opt = {
method: 'get', method: 'get',
baseURL: url, baseURL: url,
@@ -851,7 +841,7 @@ class Slp {
* *
* *
* @apiExample Example usage: * @apiExample Example usage:
* curl -X GET "https://api.fullstack.cash/v4/slp/convert/simpleledger:qz9tzs6d5097ejpg279rg0rnlhz546q4fsnck9wh5m" -H "accept:application/json" * curl -X GET "https://api.fullstack.cash/v3/slp/convert/simpleledger:qz9tzs6d5097ejpg279rg0rnlhz546q4fsnck9wh5m" -H "accept:application/json"
* *
* *
*/ */
@@ -897,7 +887,7 @@ class Slp {
* *
* *
* @apiExample Example usage: * @apiExample Example usage:
* curl -X POST "https://api.fullstack.cash/v4/slp/convert" -H "accept:application/json" -H "Content-Type: application/json" -d '{"addresses":["simpleledger:qrxa0unrn67rtn85v7asfddhhth43ecnxua0antk2l"]}' * curl -X POST "https://api.fullstack.cash/v3/slp/convert" -H "accept:application/json" -H "Content-Type: application/json" -d '{"addresses":["simpleledger:qrxa0unrn67rtn85v7asfddhhth43ecnxua0antk2l"]}'
* *
* *
*/ */
@@ -959,7 +949,7 @@ class Slp {
* *
* *
* @apiExample Example usage: * @apiExample Example usage:
* curl -X POST "https://api.fullstack.cash/v4/slp/validateTxid" -H "accept:application/json" -H "Content-Type: application/json" -d '{"txids":["f7e5199ef6669ad4d078093b3ad56e355b6ab84567e59ad0f08a5ad0244f783a","fb0eeaa501a6e1acb721669c62a3f70741f48ae0fd7f4b8e1d72088785c51952"]}' * curl -X POST "https://api.fullstack.cash/v3/slp/validateTxid" -H "accept:application/json" -H "Content-Type: application/json" -d '{"txids":["f7e5199ef6669ad4d078093b3ad56e355b6ab84567e59ad0f08a5ad0244f783a","fb0eeaa501a6e1acb721669c62a3f70741f48ae0fd7f4b8e1d72088785c51952"]}'
* *
* *
*/ */
@@ -997,9 +987,8 @@ class Slp {
const s = JSON.stringify(query) const s = JSON.stringify(query)
const b64 = Buffer.from(s).toString('base64') const b64 = Buffer.from(s).toString('base64')
const url = `${process.env.SLPDB_URL}q/${b64}` const url = `${process.env.SLPDB_URL}q/${b64}`
// console.log('url: ', url)
const options = _this.generateCredentialsGP() const options = _this.generateCredentials()
// Get data from SLPDB. // Get data from SLPDB.
const opt = { const opt = {
@@ -1013,7 +1002,7 @@ class Slp {
let formattedTokens = [] let formattedTokens = []
// Combine the confirmed and unconfirmed collections. // Combine the arrays. Why? Generally there is nothing in the u array.
const concatArray = tokenRes.data.c.concat(tokenRes.data.u) const concatArray = tokenRes.data.c.concat(tokenRes.data.u)
const tokenIds = [] const tokenIds = []
@@ -1035,27 +1024,15 @@ class Slp {
}) })
// If a user-provided txid doesn't exist in the data, add it with // If a user-provided txid doesn't exist in the data, add it with
// valid:null property. // valid:false property.
// 'null' indicates that SLPDB does not know about the transaction. It
// either has not seen it or has not processed it yet. A determination
// can not be made.
txids.forEach((txid) => { txids.forEach((txid) => {
if (!tokenIds.includes(txid)) { if (!tokenIds.includes(txid)) {
formattedTokens.push({ formattedTokens.push({
txid: txid, txid: txid,
valid: null valid: false
}) })
} }
}) })
} else {
// Corner case: No results were returned from SLPDB. Mark each entry
// as 'null'
for (let i = 0; i < txids.length; i++) {
formattedTokens.push({
txid: txids[i],
valid: null
})
}
} }
// Catch a corner case of repeated txids. SLPDB will remove redundent TXIDs, // Catch a corner case of repeated txids. SLPDB will remove redundent TXIDs,
@@ -1075,21 +1052,8 @@ class Slp {
formattedTokens = newOutput formattedTokens = newOutput
} }
// Put the output array in the same order as the input array.
const outAry = []
for (let i = 0; i < txids.length; i++) {
const thisTxid = txids[i]
// Need to use Array.find() because the returned output array is out
// of order with respect to the txid input array.
const output = formattedTokens.find((elem) => elem.txid === thisTxid)
// console.log(`output: ${JSON.stringify(output, null, 2)}`)
outAry.push(output)
}
res.status(200) res.status(200)
return res.json(outAry) return res.json(formattedTokens)
} catch (err) { } catch (err) {
wlogger.error('Error in slp.ts/validateBulk().', err) wlogger.error('Error in slp.ts/validateBulk().', err)
@@ -1105,7 +1069,7 @@ class Slp {
* *
* *
* @apiExample Example usage: * @apiExample Example usage:
* curl -X GET "https://api.fullstack.cash/v4/slp/validateTxid/f7e5199ef6669ad4d078093b3ad56e355b6ab84567e59ad0f08a5ad0244f783a" -H "accept:application/json" * curl -X GET "https://api.fullstack.cash/v3/slp/validateTxid/f7e5199ef6669ad4d078093b3ad56e355b6ab84567e59ad0f08a5ad0244f783a" -H "accept:application/json"
* *
* *
*/ */
@@ -1133,7 +1097,7 @@ class Slp {
} }
} }
const options = _this.generateCredentialsGP() const options = _this.generateCredentials()
const s = JSON.stringify(query) const s = JSON.stringify(query)
const b64 = Buffer.from(s).toString('base64') const b64 = Buffer.from(s).toString('base64')
@@ -1146,12 +1110,11 @@ class Slp {
} }
// Get data from SLPDB. // Get data from SLPDB.
const tokenRes = await _this.axios.request(opt) const tokenRes = await _this.axios.request(opt)
// console.log(`tokenRes.data: ${JSON.stringify(tokenRes.data, null, 2)}`)
// Default return value. // Default return value.
let result = { let result = {
txid: txid, txid: txid,
valid: null valid: false
} }
// Build result. // Build result.
@@ -1169,15 +1132,15 @@ class Slp {
res.status(200) res.status(200)
return res.json(result) return res.json(result)
} catch (err) { } catch (err) {
wlogger.error('Error in slp.js/validateSingle().', err) wlogger.error('Error in slp.ts/validateSingle().', err)
return _this.errorHandler(err, res) return _this.errorHandler(err, res)
} }
} }
/** /**
* @api {get} /slp/validateTxid2/{txid} Validate 2 Single * @api {get} /slp/validateTxid2/{txid} Validate single SLP transaction by txid.
* @apiName Validate a single SLP transaction by txid using slp-validate. * @apiName Validate single SLP transaction by txid.
* @apiGroup SLP * @apiGroup SLP
* @apiDescription Validate single SLP transaction by txid, using slp-validate. * @apiDescription Validate single SLP transaction by txid, using slp-validate.
* Slower, less efficient method of validating an SLP TXID using the slp-validate * Slower, less efficient method of validating an SLP TXID using the slp-validate
@@ -1186,7 +1149,7 @@ class Slp {
* *
* *
* @apiExample Example usage: * @apiExample Example usage:
* curl -X GET "https://api.fullstack.cash/v4/slp/validateTxid2/f7e5199ef6669ad4d078093b3ad56e355b6ab84567e59ad0f08a5ad0244f783a" -H "accept:application/json" * curl -X GET "https://api.fullstack.cash/v3/slp/validateTxid2/f7e5199ef6669ad4d078093b3ad56e355b6ab84567e59ad0f08a5ad0244f783a" -H "accept:application/json"
* *
* *
*/ */
@@ -1236,299 +1199,6 @@ class Slp {
} }
} }
/**
* @api {get} /slp/whitelist SLP token whitelist.
* @apiName SLP token whitelist
* @apiGroup SLP
* @apiDescription Get tokens that are on the whitelist.
* SLPDB is typically used to validate SLP transactions. It can become unstable
* during periods of high network usage. A second SLPDB has been implemented
* that is much more stable, because it only tracks a whitelist of SLP tokens.
* This endpoint will return information on the SLP tokens that are included
* in that whitelist.
*
* For tokens on the whitelist, the /slp/validateTxid3 endpoints can be used.
*
*
* @apiExample Example usage:
* curl -X GET "https://api.fullstack.cash/v4/slp/whitelist" -H "accept:application/json"
*
*/
async getSlpWhitelist (req, res, next) {
try {
const list = [
{
name: 'USDH',
tokenId:
'c4b0d62156b3fa5c8f3436079b5394f7edc1bef5dc1cd2f9d0c4d46f82cca479'
},
{
name: 'SPICE',
tokenId:
'4de69e374a8ed21cbddd47f2338cc0f479dc58daa2bbe11cd604ca488eca0ddf'
},
{
name: 'PSF',
tokenId:
'38e97c5d7d3585a2cbf3f9580c82ca33985f9cb0845d4dcce220cb709f9538b0'
},
{
name: 'TROUT',
tokenId:
'a4fb5c2da1aa064e25018a43f9165040071d9e984ba190c222a7f59053af84b2'
},
{
name: 'PSFTEST',
tokenId:
'd0ef4de95b78222bfee2326ab11382f4439aa0855936e2fe6ac129a8d778baa0'
}
]
res.status(200)
return res.json(list)
} catch (err) {
wlogger.error('Error in slp.ts/whitelist().', err)
return _this.errorHandler(err, res)
}
}
/**
* @api {get} /slp/validateTxid3/{txid} Validate 3 Single
* @apiName Validate a single txid against a whitelist SLPDB
* @apiGroup SLP
* @apiDescription Alternative validation for tokens on the whitelist
* This endpoint is exactly the same as /slp/validateTxid/{txid} but it uses
* a different SLPDB. This server only indexes the SLP tokens that are on the
* whitelist. You can see which tokens are on the whitelist by calling the
* /slp/whitelist endpoint.
*
* @apiExample Example usage:
* curl -X GET "https://api.fullstack.cash/v4/slp/validateTxid3/f7e5199ef6669ad4d078093b3ad56e355b6ab84567e59ad0f08a5ad0244f783a" -H "accept:application/json"
*
*
*/
async validate3Single (req, res, next) {
try {
const txid = req.params.txid
// console.log('validate3Single txid: ', txid)
// Validate input
if (!txid || txid === '') {
res.status(400)
return res.json({ error: 'txid can not be empty' })
}
wlogger.debug('Executing slp/validate/:txid with this txid: ', txid)
const query = {
v: 3,
q: {
db: ['c', 'u'],
find: {
'tx.h': txid
},
limit: 300,
project: { 'slp.valid': 1, 'tx.h': 1, 'slp.invalidReason': 1 }
}
}
const options = _this.generateCredentialsWL()
const s = JSON.stringify(query)
const b64 = Buffer.from(s).toString('base64')
const url = `${process.env.SLPDB_WHITELIST_URL}q/${b64}`
const opt = {
method: 'get',
baseURL: url,
headers: options.headers,
timeout: options.timeout
}
// Get data from SLPDB.
const tokenRes = await _this.axios.request(opt)
// console.log(`tokenRes.data: ${JSON.stringify(tokenRes.data, null, 2)}`)
// Default return value.
let result = {
txid: txid,
valid: null
}
// Build result.
const concatArray = tokenRes.data.c.concat(tokenRes.data.u)
if (concatArray.length > 0) {
result = {
txid: concatArray[0].tx.h,
valid: concatArray[0].slp.valid
}
if (!result.valid) {
result.invalidReason = concatArray[0].slp.invalidReason
}
}
res.status(200)
return res.json(result)
} catch (err) {
wlogger.error('Error in slp.js/validate3Single().', err)
return _this.errorHandler(err, res)
}
}
/**
* @api {post} /slp/validateTxid3/ Validate 3 Bulk
* @apiName Validate an array of TXIDs against a whitelist SLPDB
* @apiGroup SLP
* @apiDescription Alternative validation for tokens on the whitelist
* This endpoint is exactly the same as /slp/validateTxid but it uses
* a different SLPDB. This server only indexes the SLP tokens that are on the
* whitelist. You can see which tokens are on the whitelist by calling the
* /slp/whitelist endpoint.
*
* @apiExample Example usage:
* curl -X POST "https://api.fullstack.cash/v4/slp/validateTxid3" -H "accept:application/json" -H "Content-Type: application/json" -d '{"txids":["f7e5199ef6669ad4d078093b3ad56e355b6ab84567e59ad0f08a5ad0244f783a","fb0eeaa501a6e1acb721669c62a3f70741f48ae0fd7f4b8e1d72088785c51952"]}'
*
*
*/
async validate3Bulk (req, res, next) {
try {
const txids = req.body.txids
// console.log(`validate3Bulk txids: `, txids)
// Reject if txids is not an array.
if (!Array.isArray(txids)) {
res.status(400)
return res.json({ error: 'txids needs to be an array' })
}
// Enforce array size rate limits
if (!_this.routeUtils.validateArraySize(req, txids)) {
res.status(429) // https://github.com/Bitcoin-com/rest.bitcoin.com/issues/330
return res.json({
error: 'Array too large.'
})
}
wlogger.debug('Executing slp/validate with these txids: ', txids)
const query = {
v: 3,
q: {
db: ['c', 'u'],
find: {
'tx.h': { $in: txids }
},
limit: 300,
project: { 'slp.valid': 1, 'tx.h': 1, 'slp.invalidReason': 1 }
}
}
const s = JSON.stringify(query)
const b64 = Buffer.from(s).toString('base64')
const url = `${process.env.SLPDB_WHITELIST_URL}q/${b64}`
// console.log('url: ', url)
const options = _this.generateCredentialsWL()
// Get data from SLPDB.
const opt = {
method: 'get',
baseURL: url,
headers: options.headers,
timeout: options.timeout
}
const tokenRes = await _this.axios.request(opt)
// console.log(`tokenRes.data: ${JSON.stringify(tokenRes.data, null, 2)}`)
let formattedTokens = []
// Combine the confirmed and unconfirmed collections.
const concatArray = tokenRes.data.c.concat(tokenRes.data.u)
const tokenIds = []
if (concatArray.length > 0) {
concatArray.forEach((token) => {
tokenIds.push(token.tx.h) // txid
const validationResult = {
txid: token.tx.h,
valid: token.slp.valid
}
// If the txid is invalid, add the reason it's invalid.
if (!validationResult.valid) {
validationResult.invalidReason = token.slp.invalidReason
}
formattedTokens.push(validationResult)
})
// If a user-provided txid doesn't exist in the data, add it with
// valid:null property.
// 'null' indicates that SLPDB does not know about the transaction. It
// either has not seen it or has not processed it yet. A determination
// can not be made.
txids.forEach((txid) => {
if (!tokenIds.includes(txid)) {
formattedTokens.push({
txid: txid,
valid: null
})
}
})
} else {
// Corner case: No results were returned from SLPDB. Mark each entry
// as 'null'
for (let i = 0; i < txids.length; i++) {
formattedTokens.push({
txid: txids[i],
valid: null
})
}
}
// Catch a corner case of repeated txids. SLPDB will remove redundent TXIDs,
// which will cause the output array to be smaller than the input array.
if (txids.length > formattedTokens.length) {
const newOutput = []
for (let i = 0; i < txids.length; i++) {
const thisTxid = txids[i]
// Find the element that matches the current txid.
const elem = formattedTokens.filter((x) => x.txid === thisTxid)
newOutput.push(elem[0])
}
// Replace the original output object with the new output object.
formattedTokens = newOutput
}
// console.log(
// `formattedTokens: ${JSON.stringify(formattedTokens, null, 2)}`
// )
// Put the output array in the same order as the input array.
const outAry = []
for (let i = 0; i < txids.length; i++) {
const thisTxid = txids[i]
// Need to use Array.find() because the returned output array is out
// of order with respect to the txid input array.
const output = formattedTokens.find((elem) => elem.txid === thisTxid)
// console.log(`output: ${JSON.stringify(output, null, 2)}`)
outAry.push(output)
}
res.status(200)
return res.json(outAry)
} catch (err) {
wlogger.error('Error in slp.js/validate3Bulk().', err)
return _this.errorHandler(err, res)
}
}
/** /**
* @api {get} /slp/txDetails/{txid} SLP transaction details. * @api {get} /slp/txDetails/{txid} SLP transaction details.
* @apiName SLP transaction details. * @apiName SLP transaction details.
@@ -1537,7 +1207,7 @@ class Slp {
* *
* *
* @apiExample Example usage: * @apiExample Example usage:
* curl -X GET "https://api.fullstack.cash/v4/slp/txDetails/8ab4ac5dea3f9024e3954ee5b61452955d659a34561f79ef62ac44e133d0980e" -H "accept:application/json" * curl -X GET "https://api.fullstack.cash/v3/slp/txDetails/8ab4ac5dea3f9024e3954ee5b61452955d659a34561f79ef62ac44e133d0980e" -H "accept:application/json"
* *
* *
*/ */
@@ -1570,7 +1240,7 @@ class Slp {
const b64 = Buffer.from(s).toString('base64') const b64 = Buffer.from(s).toString('base64')
const url = `${process.env.SLPDB_URL}q/${b64}` const url = `${process.env.SLPDB_URL}q/${b64}`
const options = _this.generateCredentialsGP() const options = _this.generateCredentials()
const opt = { const opt = {
method: 'get', method: 'get',
baseURL: url, baseURL: url,
@@ -1581,9 +1251,7 @@ class Slp {
const tokenRes = await _this.axios.request(opt) const tokenRes = await _this.axios.request(opt)
// console.log(`tokenRes: ${util.inspect(tokenRes)}`) // console.log(`tokenRes: ${util.inspect(tokenRes)}`)
// Return 'not found' error if both the confirmed and unconfirmed if (tokenRes.data.c.length === 0) {
// collections are empty.
if (tokenRes.data.c.length === 0 && tokenRes.data.u.length === 0) {
res.status(404) res.status(404)
return res.json({ error: 'TXID not found' }) return res.json({ error: 'TXID not found' })
} }
@@ -1629,7 +1297,7 @@ class Slp {
* *
* *
* @apiExample Example usage: * @apiExample Example usage:
* curl -X GET "https://api.fullstack.cash/v4/slp/tokenStats/9ba379fe8171176d4e7e6771d9a24cd0e044c7b788d5f86a3fdf80904832b2c0" -H "accept:application/json" * curl -X GET "https://api.fullstack.cash/v3/slp/tokenStats/9ba379fe8171176d4e7e6771d9a24cd0e044c7b788d5f86a3fdf80904832b2c0" -H "accept:application/json"
* *
* *
*/ */
@@ -1660,7 +1328,7 @@ class Slp {
* *
* *
* @apiExample Example usage: * @apiExample Example usage:
* curl -X GET "https://api.fullstack.cash/v4/slp/transactions/9ba379fe8171176d4e7e6771d9a24cd0e044c7b788d5f86a3fdf80904832b2c0/simpleledger:qrxa0unrn67rtn85v7asfddhhth43ecnxua0antk2l" -H "accept:application/json" * curl -X GET "https://api.fullstack.cash/v3/slp/transactions/9ba379fe8171176d4e7e6771d9a24cd0e044c7b788d5f86a3fdf80904832b2c0/simpleledger:qrxa0unrn67rtn85v7asfddhhth43ecnxua0antk2l" -H "accept:application/json"
* *
* *
*/ */
@@ -1730,32 +1398,11 @@ class Slp {
} }
// Generates a Basic Authorization header for slpserve. // Generates a Basic Authorization header for slpserve.
generateCredentialsGP () { generateCredentials () {
// Generate the Basic Authentication header for a private instance of SLPDB. // Generate the Basic Authentication header for a private instance of SLPDB.
const username = 'BITBOX' const username = 'BITBOX'
const password = SLPDB_PASS_GP const password = SLPDB_PASS
const combined = `${username}:${password}` const combined = `${username}:${password}`
// console.log(`combined: ${combined}`)
var base64Credential = Buffer.from(combined).toString('base64')
var readyCredential = `Basic ${base64Credential}`
const options = {
headers: {
authorization: readyCredential
},
timeout: 15000
}
return options
}
// Generates a Basic Authorization header for slpserve.
generateCredentialsWL () {
// Generate the Basic Authentication header for a private instance of SLPDB.
const username = 'BITBOX'
const password = SLPDB_PASS_WL
const combined = `${username}:${password}`
// console.log(`combined: ${combined}`)
var base64Credential = Buffer.from(combined).toString('base64') var base64Credential = Buffer.from(combined).toString('base64')
var readyCredential = `Basic ${base64Credential}` var readyCredential = `Basic ${base64Credential}`
@@ -1790,29 +1437,12 @@ class Slp {
sendOutputs.push(string.toString()) sendOutputs.push(string.toString())
}) })
// Because you are not using Insight API indexer, you do not get the
// sending addresses from an indexer or from the node.
// However, they are available from the SLPDB output.
const tokenInputs = transaction.in
// Collect the input addresses
const sendInputs = []
for (let i = 0; i < tokenInputs.length; i += 1) {
const tokenInput = tokenInputs[i]
const sendInput = {}
sendInput.address = tokenInput.e.a
sendInputs.push(sendInput)
}
const obj = { const obj = {
tokenInfo: { tokenInfo: {
versionType: transaction.slp.detail.versionType, versionType: transaction.slp.detail.versionType,
tokenName: transaction.slp.detail.name,
tokenTicker: transaction.slp.detail.symbol,
transactionType: transaction.slp.detail.transactionType, transactionType: transaction.slp.detail.transactionType,
tokenIdHex: transaction.slp.detail.tokenIdHex, tokenIdHex: transaction.slp.detail.tokenIdHex,
sendOutputs: sendOutputs, sendOutputs: sendOutputs
sendInputsFull: sendInputs,
sendOutputsFull: transaction.slp.detail.outputs
}, },
tokenIsValid: transaction.slp.valid tokenIsValid: transaction.slp.valid
} }
@@ -1886,7 +1516,7 @@ class Slp {
* (1 or 2) will also be returned. * (1 or 2) will also be returned.
* *
* @apiExample Example usage: * @apiExample Example usage:
* curl -X POST "https://api.fullstack.cash/v4/slp/generateSendOpReturn" -H "accept:application/json" -H "Content-Type: application/json" -d '{"tokenUtxos":[{"tokenId": "0a321bff9761f28e06a268b14711274bb77617410a16807bd0437ef234a072b1","decimals": 0, "tokenQty": 2}], "sendQty": 1.5}' * curl -X POST "https://api.fullstack.cash/v3/slp/generateSendOpReturn" -H "accept:application/json" -H "Content-Type: application/json" -d '{"tokenUtxos":[{"tokenId": "0a321bff9761f28e06a268b14711274bb77617410a16807bd0437ef234a072b1","decimals": 0, "tokenQty": 2}], "sendQty": 1.5}'
* *
* *
*/ */
@@ -1925,8 +1555,6 @@ class Slp {
}) })
} }
// console.log('sendQty: ', sendQty)
// console.log(`tokenUtxos: `, tokenUtxos)
const opReturn = await _this.bchjs.SLP.TokenType1.generateSendOpReturn( const opReturn = await _this.bchjs.SLP.TokenType1.generateSendOpReturn(
tokenUtxos, tokenUtxos,
sendQty sendQty
@@ -1938,7 +1566,6 @@ class Slp {
res.status(200) res.status(200)
return res.json({ script, outputs: opReturn.outputs }) return res.json({ script, outputs: opReturn.outputs })
} catch (err) { } catch (err) {
console.log('err: ', err)
wlogger.error('Error in slp.js/generateSendOpReturn().', err) wlogger.error('Error in slp.js/generateSendOpReturn().', err)
// Decode the error message. // Decode the error message.
@@ -1971,7 +1598,7 @@ class Slp {
* not been confirmed. * not been confirmed.
* *
* @apiExample Example usage: * @apiExample Example usage:
* curl -X POST "https://api.fullstack.cash/v4/slp/hydrateUtxos" -H "accept:application/json" -H "Content-Type: application/json" -d '{"utxos":[{"utxos":[{"txid": "d56a2b446d8149c39ca7e06163fe8097168c3604915f631bc58777d669135a56","vout": 3, "value": "6816", "height": 606848, "confirmations": 13, "satoshis": 6816}, {"txid": "d56a2b446d8149c39ca7e06163fe8097168c3604915f631bc58777d669135a56","vout": 2, "value": "546", "height": 606848, "confirmations": 13, "satoshis": 546}]}]}' * curl -X POST "https://api.fullstack.cash/v3/slp/hydrateUtxos" -H "accept:application/json" -H "Content-Type: application/json" -d '{"utxos":[{"utxos":[{"txid": "d56a2b446d8149c39ca7e06163fe8097168c3604915f631bc58777d669135a56","vout": 3, "value": "6816", "height": 606848, "confirmations": 13, "satoshis": 6816}, {"txid": "d56a2b446d8149c39ca7e06163fe8097168c3604915f631bc58777d669135a56","vout": 2, "value": "546", "height": 606848, "confirmations": 13, "satoshis": 546}]}]}'
* *
* *
*/ */
@@ -1979,24 +1606,6 @@ class Slp {
try { try {
const utxos = req.body.utxos const utxos = req.body.utxos
// Extract a delay value if the user passed it in.
const usrObjIn = req.body.usrObj
let utxoDelay = 0
if (usrObjIn && usrObjIn.utxoDelay) { utxoDelay = usrObjIn.utxoDelay }
// console.log('req: ', req)
// console.log(`req._remoteAddress: ${req._remoteAddress}`)
// Generate a user object that can be passed along with internal calls
// from bch-js.
const usrObj = {
ip: req._remoteAddress,
jwtToken: req.locals.jwtToken,
proLimit: req.locals.proLimit,
apiLevel: req.locals.apiLevel,
utxoDelay
}
// Validate inputs // Validate inputs
if (!Array.isArray(utxos)) { if (!Array.isArray(utxos)) {
res.status(422) res.status(422)
@@ -2031,8 +1640,8 @@ class Slp {
const theseUtxos = utxos[i].utxos const theseUtxos = utxos[i].utxos
// Get SLP token details. // Get SLP token details.
const details = await _this.bchjs.SLP.Utils.tokenUtxoDetails(theseUtxos, usrObj) const details = await _this.bchjs.SLP.Utils.tokenUtxoDetails(theseUtxos)
// console.log('details: ', details) // console.log('details : ', details)
// Replace the original UTXO data with the hydrated data. // Replace the original UTXO data with the hydrated data.
utxos[i].utxos = details utxos[i].utxos = details
@@ -2046,149 +1655,17 @@ class Slp {
// Decode the error message. // Decode the error message.
const { msg, status } = routeUtils.decodeError(err) const { msg, status } = routeUtils.decodeError(err)
console.log('msg: ', msg)
console.log('status: ', status)
if (msg) { if (msg) {
res.status(status) res.status(status)
return res.json({ error: msg, message: msg, success: false }) return res.json({ error: msg })
} }
res.status(500) res.status(500)
return res.json({ return res.json({
error: 'Undetermined error in hydrateUtxos()', error: 'Error in hydrateUtxos()'
message: err.message
}) })
} }
} }
/**
* @api {post} /slp/hydrateUtxosWL/ hydrateUtxosWL
* @apiName SLP hydrateUtxosWL
* @apiGroup SLP
* @apiDescription Hydrate UTXO data with SLP information, using only the whitelist SLPDB.
*
* This call is identical to `hydrateUtxos`, except it will only use the
* filtered SLPDB with a whitelist. This results in faster performance, more
* reliable uptime, but more frequent `isValid: null` values. Some use-cases
* prioritize the speed and reliability over acceptance of a wide range of
* SLP tokens.
*
* @apiExample Example usage:
* curl -X POST "https://api.fullstack.cash/v4/slp/hydrateUtxosWL" -H "accept:application/json" -H "Content-Type: application/json" -d '{"utxos":[{"utxos":[{"txid": "d56a2b446d8149c39ca7e06163fe8097168c3604915f631bc58777d669135a56","vout": 3, "value": "6816", "height": 606848, "confirmations": 13, "satoshis": 6816}, {"txid": "d56a2b446d8149c39ca7e06163fe8097168c3604915f631bc58777d669135a56","vout": 2, "value": "546", "height": 606848, "confirmations": 13, "satoshis": 546}]}]}'
*
*
*/
async hydrateUtxosWL (req, res, next) {
try {
const utxos = req.body.utxos
// Validate inputs
if (!Array.isArray(utxos)) {
res.status(422)
return res.json({
error: 'Input must be an array.'
})
}
if (!utxos.length) {
res.status(422)
return res.json({
error: 'Array should not be empty'
})
}
if (utxos.length > 20) {
res.status(422)
return res.json({
error: 'Array too long, max length is 20'
})
}
if (!utxos[0].utxos) {
res.status(422)
return res.json({
error: 'Each element in array should have a utxos property'
})
}
// Loop through each address and query the UTXOs for that element.
for (let i = 0; i < utxos.length; i++) {
const theseUtxos = utxos[i].utxos
// console.log(`theseUtxos: ${JSON.stringify(theseUtxos, null, 2)}`)
// Get SLP token details.
const details = await _this.bchjs.SLP.Utils.tokenUtxoDetailsWL(theseUtxos)
// console.log('details : ', details)
// Replace the original UTXO data with the hydrated data.
utxos[i].utxos = details
}
res.status(200)
return res.json({ slpUtxos: utxos })
} catch (err) {
wlogger.error('Error in slp.js/hydrateUtxosWL().', err)
console.error('Error in slp.js/hydrateUtxosWL().', err)
// Decode the error message.
const { msg, status } = routeUtils.decodeError(err)
console.log('msg: ', msg)
console.log('status: ', status)
if (msg) {
res.status(status)
return res.json({ error: msg, message: msg, success: false })
}
res.status(500)
return res.json({
error: 'Error in hydrateUtxosWL()',
message: 'Error in hydrateUtxosWL()'
})
}
}
/**
* @api {get} /slp/status Get the health status of SLPDB
* @apiName Get the health status of SLPDB
* @apiGroup SLP
* @apiDescription Get the health status of SLPDB
*
*
* @apiExample Example usage:
* curl -X GET "https://api.fullstack.cash/v4/slp/status" -H "accept:application/json" -H "Content-Type: application/json"
*
*/
async getStatus (req, res, next) {
try {
const query = {
v: 3,
q: {
db: ['s'],
find: { context: 'SLPDB' },
limit: 10
}
}
const s = JSON.stringify(query)
const b64 = Buffer.from(s).toString('base64')
const url = `${process.env.SLPDB_URL}q/${b64}`
// Request options
const opt = {
method: 'get',
baseURL: url
}
const tokenRes = await _this.axios.request(opt)
const status = tokenRes.data.s[0]
res.status(200)
return res.json(status)
} catch (err) {
// console.log(err)
wlogger.error('Error in slp.js/getStatus().', err)
return _this.errorHandler(err, res)
}
}
} }
module.exports = Slp module.exports = Slp
+41 -41
View File
@@ -6,13 +6,15 @@ const axios = require('axios')
const routeUtils = require('./route-utils') const routeUtils = require('./route-utils')
const wlogger = require('../../util/winston-logging') const wlogger = require('../../util/winston-logging')
const Blockbook = require('./blockbook')
const blockbook = new Blockbook()
const util = require('util') const util = require('util')
util.inspect.defaultOptions = { depth: 1 } util.inspect.defaultOptions = { depth: 1 }
const BCHJS = require('@psf/bch-js') const BCHJS = require('@psf/bch-js')
const bchjs = new BCHJS() const bchjs = new BCHJS()
// const BCHJS_TESTNET = 'https://testnet.bchjs.cash/v4/' // const BCHJS_TESTNET = 'https://testnet.bchjs.cash/v3/'
// const bchjsHTTP = axios.create({ // const bchjsHTTP = axios.create({
// baseURL: process.env.RPC_BASEURL // baseURL: process.env.RPC_BASEURL
@@ -35,28 +37,9 @@ const bchjs = new BCHJS()
let _this let _this
class UtilRoute { class UtilRoute {
constructor (utilConfig) { constructor () {
this.bchjs = bchjs this.bchjs = bchjs
// this.blockbook = blockbook this.blockbook = blockbook
if (!utilConfig) {
throw new Error(
'Must pass a config object when instantiating the Util library.'
)
}
if (!utilConfig.electrumx) {
throw new Error(
'Must pass an instance of Electrumx when instantiating the Util library.'
)
}
this.electrumx = utilConfig.electrumx
this.router = router
this.router.get('/', this.root)
this.router.get('/validateAddress/:address', this.validateAddressSingle)
this.router.post('/validateAddress', this.validateAddressBulk)
this.router.post('/sweep', this.sweepWif)
_this = this _this = this
} }
@@ -73,7 +56,7 @@ class UtilRoute {
* *
* *
* @apiExample Example usage: * @apiExample Example usage:
* curl -X GET "https://api.fullstack.cash/v4/util/validateAddress/bitcoincash:qzs02v05l7qs5s24srqju498qu55dwuj0cx5ehjm2c" -H "accept: application/json" * curl -X GET "https://api.fullstack.cash/v3/util/validateAddress/bitcoincash:qzs02v05l7qs5s24srqju498qu55dwuj0cx5ehjm2c" -H "accept: application/json"
* *
* *
*/ */
@@ -122,8 +105,8 @@ class UtilRoute {
* *
* *
* @apiExample Example usage: * @apiExample Example usage:
* curl -X POST "https://api.fullstack.cash/v4/util/validateAddress" -H "accept: application/json" -H "Content-Type: application/json" -d '{"addresses":["bitcoincash:qzs02v05l7qs5s24srqju498qu55dwuj0cx5ehjm2c","bitcoincash:qrehqueqhw629p6e57994436w730t4rzasnly00ht0"]}' * curl -X POST "https://api.fullstack.cash/v3/util/validateAddress" -H "accept: application/json" -H "Content-Type: application/json" -d '{"addresses":["bitcoincash:qzs02v05l7qs5s24srqju498qu55dwuj0cx5ehjm2c","bitcoincash:qrehqueqhw629p6e57994436w730t4rzasnly00ht0"]}'
* curl -X POST "https://api.fullstack.cash/v4/util/validateAddress" -H "accept: application/json" -H "Content-Type: application/json" -d '{"addresses":["bitcoincash:qzs02v05l7qs5s24srqju498qu55dwuj0cx5ehjm2c","bitcoincash:qrehqueqhw629p6e57994436w730t4rzasnly00ht0"],"from": 1, "to": 5}' * curl -X POST "https://api.fullstack.cash/v3/util/validateAddress" -H "accept: application/json" -H "Content-Type: application/json" -d '{"addresses":["bitcoincash:qzs02v05l7qs5s24srqju498qu55dwuj0cx5ehjm2c","bitcoincash:qrehqueqhw629p6e57994436w730t4rzasnly00ht0"],"from": 1, "to": 5}'
* *
* *
*/ */
@@ -182,7 +165,7 @@ class UtilRoute {
} = routeUtils.setEnvVars() } = routeUtils.setEnvVars()
// Loop through each address and creates an array of requests to call in parallel // Loop through each address and creates an array of requests to call in parallel
const promises = addresses.map(async (address) => { const promises = addresses.map(async address => {
requestConfig.data.id = 'validateaddress' requestConfig.data.id = 'validateaddress'
requestConfig.data.method = 'validateaddress' requestConfig.data.method = 'validateaddress'
requestConfig.data.params = [address] requestConfig.data.params = [address]
@@ -194,7 +177,7 @@ class UtilRoute {
const axiosResult = await axios.all(promises) const axiosResult = await axios.all(promises)
// Retrieve the data part of the result. // Retrieve the data part of the result.
const result = axiosResult.map((x) => x.data.result) const result = axiosResult.map(x => x.data.result)
res.status(200) res.status(200)
return res.json(result) return res.json(result)
@@ -225,8 +208,8 @@ class UtilRoute {
* *
* *
* @apiExample Example usage: * @apiExample Example usage:
* curl -X POST "https://api.fullstack.cash/v4/util/sweep" -H "accept: application/json" -H "Content-Type: application/json" -d '{"wif":"Kz52sdXLAiKtH82iAFRi6aTZanWtW5Eyv37KWdpY6tTv7pjoHste", "balanceOnly": true}' * curl -X POST "https://api.fullstack.cash/v3/util/sweep" -H "accept: application/json" -H "Content-Type: application/json" -d '{"wif":"Kz52sdXLAiKtH82iAFRi6aTZanWtW5Eyv37KWdpY6tTv7pjoHste", "balanceOnly": true}'
* curl -X POST "https://api.fullstack.cash/v4/util/sweep" -H "accept: application/json" -H "Content-Type: application/json" -d '{"wif":"Kz52sdXLAiKtH82iAFRi6aTZanWtW5Eyv37KWdpY6tTv7pjoHste", "toAddr": "bitcoincash:qpt8m4kqu963geedyrur6pdggqmv5kxwnq0rn322qu"}' * curl -X POST "https://api.fullstack.cash/v3/util/sweep" -H "accept: application/json" -H "Content-Type: application/json" -d '{"wif":"Kz52sdXLAiKtH82iAFRi6aTZanWtW5Eyv37KWdpY6tTv7pjoHste", "toAddr": "bitcoincash:qpt8m4kqu963geedyrur6pdggqmv5kxwnq0rn322qu"}'
* *
* *
*/ */
@@ -260,11 +243,12 @@ class UtilRoute {
const fromAddr = bchjs.ECPair.toCashAddress(ecPair) const fromAddr = bchjs.ECPair.toCashAddress(ecPair)
// Get a balance on the public address // Get a balance on the public address
const balances = await _this.electrumx._balanceFromElectrumx(fromAddr) const balances = await _this.blockbook.balanceFromBlockbook(fromAddr)
// console.log(`balances: ${JSON.stringify(balances, null, 2)}`) // console.log(`balances: ${JSON.stringify(balances, null, 2)}`)
// Total balance is the sum of the confirmed and unconfirmed balance. // Total balance is the sum of the confirmed and unconfirmed balance.
const totalBalance = balances.confirmed + balances.unconfirmed const totalBalance =
Number(balances.balance) + Number(balances.unconfirmedBalance)
// Exit if balance is zero. // Exit if balance is zero.
if (isNaN(totalBalance) || totalBalance === 0) { if (isNaN(totalBalance) || totalBalance === 0) {
@@ -279,7 +263,7 @@ class UtilRoute {
} }
// Get all UTXOs help by the address. // Get all UTXOs help by the address.
const utxos = await _this.electrumx._utxosFromElectrumx(fromAddr) const utxos = await _this.blockbook.utxosFromBlockbook(fromAddr)
// console.log(`utxos: ${JSON.stringify(utxos, null, 2)}`) // console.log(`utxos: ${JSON.stringify(utxos, null, 2)}`)
const tokenUtxos = [] const tokenUtxos = []
@@ -384,7 +368,7 @@ class UtilRoute {
let utxos = options.utxos let utxos = options.utxos
// Ensure all utxos have the satoshis property. // Ensure all utxos have the satoshis property.
utxos = utxos.map((x) => { utxos = utxos.map(x => {
x.satoshis = Number(x.value) x.satoshis = Number(x.value)
return x return x
}) })
@@ -402,9 +386,9 @@ class UtilRoute {
for (let i = 0; i < utxos.length; i++) { for (let i = 0; i < utxos.length; i++) {
const utxo = utxos[i] const utxo = utxos[i]
originalAmount = originalAmount + utxo.value originalAmount = originalAmount + utxo.satoshis
transactionBuilder.addInput(utxo.tx_hash, utxo.tx_pos) transactionBuilder.addInput(utxo.txid, utxo.vout)
} }
if (originalAmount < 546) { if (originalAmount < 546) {
@@ -439,7 +423,7 @@ class UtilRoute {
ecPair, ecPair,
redeemScript, redeemScript,
transactionBuilder.hashTypes.SIGHASH_ALL, transactionBuilder.hashTypes.SIGHASH_ALL,
utxo.value utxo.satoshis
) )
} }
@@ -477,7 +461,7 @@ class UtilRoute {
// Ensure there is only one class of token in the wallet. Throw an error if // Ensure there is only one class of token in the wallet. Throw an error if
// there is more than one. // there is more than one.
const tokenId = tokenUtxos[0].tokenId const tokenId = tokenUtxos[0].tokenId
const otherTokens = tokenUtxos.filter((x) => x.tokenId !== tokenId) const otherTokens = tokenUtxos.filter(x => x.tokenId !== tokenId)
if (otherTokens.length > 0) { if (otherTokens.length > 0) {
throw new Error( throw new Error(
'Multiple token classes detected. This function only supports a single class of token.' 'Multiple token classes detected. This function only supports a single class of token.'
@@ -499,9 +483,9 @@ class UtilRoute {
for (let i = 0; i < allUtxos.length; i++) { for (let i = 0; i < allUtxos.length; i++) {
const utxo = allUtxos[i] const utxo = allUtxos[i]
originalAmount = originalAmount + utxo.value originalAmount = originalAmount + utxo.satoshis
transactionBuilder.addInput(utxo.tx_hash, utxo.tx_pos) transactionBuilder.addInput(utxo.txid, utxo.vout)
} }
if (originalAmount < 300) { if (originalAmount < 300) {
@@ -581,7 +565,7 @@ class UtilRoute {
ecPair, ecPair,
redeemScript, redeemScript,
transactionBuilder.hashTypes.SIGHASH_ALL, transactionBuilder.hashTypes.SIGHASH_ALL,
thisUtxo.value thisUtxo.satoshis
) )
} }
@@ -601,4 +585,20 @@ class UtilRoute {
} }
} }
module.exports = UtilRoute const utilRoute = new UtilRoute()
router.get('/', utilRoute.root)
router.get('/validateAddress/:address', utilRoute.validateAddressSingle)
router.post('/validateAddress', utilRoute.validateAddressBulk)
router.post('/sweep', utilRoute.sweepWif)
module.exports = {
router,
// testableComponents: {
// root,
// validateAddressSingle,
// validateAddressBulk,
// sweepWif
// }
UtilRoute
}
-9
View File
@@ -154,17 +154,8 @@ class RouteUtils {
} }
} }
// Handle general Error objects.
if (err.message) {
return {
msg: err.message,
status: 422
}
}
return { msg: false, status: 500 } return { msg: false, status: 500 }
} catch (err) { } catch (err) {
console.error('unhandled error in route-utils.js/decodeError(): ', err)
wlogger.error('unhandled error in route-utils.js/decodeError(): ', err) wlogger.error('unhandled error in route-utils.js/decodeError(): ', err)
return { msg: false, status: 500 } return { msg: false, status: 500 }
} }
+2 -8
View File
@@ -11,19 +11,13 @@ require('winston-daily-rotate-file')
var NETWORK = process.env.NETWORK var NETWORK = process.env.NETWORK
// Default 1 Megabyte
var LOG_MAX_SIZE = process.env.LOG_MAX_SIZE ? process.env.LOG_MAX_SIZE : '1m'
// Default 5 days.
var LOG_MAX_FILES = process.env.LOG_MAX_FILES ? process.env.LOG_MAX_FILES : '5d'
// Configure daily-rotation transport. // Configure daily-rotation transport.
var transport = new winston.transports.DailyRotateFile({ var transport = new winston.transports.DailyRotateFile({
filename: `${__dirname}/../../logs/rest-${NETWORK}-%DATE%.log`, filename: `${__dirname}/../../logs/rest-${NETWORK}-%DATE%.log`,
datePattern: 'YYYY-MM-DD', datePattern: 'YYYY-MM-DD',
zippedArchive: false, zippedArchive: false,
maxSize: LOG_MAX_SIZE, maxSize: '1m',
maxFiles: LOG_MAX_FILES, maxFiles: '5d',
format: winston.format.combine( format: winston.format.combine(
winston.format.timestamp(), winston.format.timestamp(),
winston.format.json() winston.format.json()
+9 -50
View File
@@ -1,64 +1,23 @@
#!/bin/bash #!/bin/bash
# Docker containers for the below infrastructure is described here:
# https://psfoundation.cash/blog/cash-stack
# Pre-synced databases can be downloaded on the CashStrap page:
# https://fullstack.cash/cashstrap
# Which network are you using?
export NETWORK=mainnet
#export NETWORK=testnet
# Full node # Full node
export RPC_IP=<ip>:8332 export RPC_BASEURL=http://<full node ip>:8332/
export RPC_BASEURL=http://$RPC_IP/ export RPC_USERNAME=<RPC username>
export RPC_USERNAME=bitcoin export RPC_PASSWORD=<RPC password>
export RPC_PASSWORD=password export NETWORK=mainnet
# SLPDB # SLPDB
export SLPDB_PASS_GP=somelongpassword
export SLPDB_URL=http://<SLPDB IP>:12300/ export SLPDB_URL=http://<SLPDB IP>:12300/
# Use the same address as SLPDB_URL if you don't have a separate whitelist server.
export SLPDB_PASS_WL=somelongpassword # Blockbook
export SLPDB_WHITELIST_URL=http://<SLPDB IP>:12300/ export BLOCKBOOK_URL=https://<Blockbook IP>:9131/
# slp-api alternative SLP validator using slp-validate: # Allow node.js to make network calls to https using self-signed certificate.
# https://github.com/Permissionless-Software-Foundation/slp-api export NODE_TLS_REJECT_UNAUTHORIZED=0
export SLP_API_URL=http://10.0.0.5:5001/
# Mainnet Fulcrum / ElectrumX # Mainnet Fulcrum / ElectrumX
export FULCRUM_URL=192.168.0.6 export FULCRUM_URL=192.168.0.6
export FULCRUM_PORT=50002 export FULCRUM_PORT=50002
# Redis DB - Used for rate limiting - customize to your own Redis installation.
export REDIS_PORT=6379
export REDIS_HOST=172.17.0.1
# JWT Token Secret
# This is used to verify JWT tokens generated with jwt-bch-api:
# https://github.com/Permissionless-Software-Foundation/jwt-bch-api
export TOKENSECRET=somelongpassword export TOKENSECRET=somelongpassword
# So that bch-api can call bch-js locally.
export LOCAL_RESTURL=http://127.0.0.1:3000/v4/
# Basic Authentication password
export PRO_PASS=somerandomepassword:someotherrandompassword:aThirdPassword
# Whitelisted domains. Automatically give pro-tier rate limit access to apps
# that originate froma domain on the whitelist.
export WHITELIST_DOMAINS=fullstack.cash,psfoundation.cash,torlist.cash
# Rate Limits. Numbers are divided into 1000. e.g. 1000 / 50 = 20 RPM for ANON.
# Requests use the ANON rate limit if they fail to pass in a JWT token.
# ANON = 20 requests per minute (RPM)
export ANON_RATE_LIMIT=50
# 10 = 100 RPM
export WHITELIST_RATE_LIMIT=10
# Set logging parameters
#1m means no more than 1 megabyte
export LOG_MAX_SIZE=1m
#5d means store no more than 5 days
export LOG_MAX_FILES=5d
npm start npm start
@@ -20,7 +20,7 @@ const assert = chai.assert
const sinon = require('sinon') const sinon = require('sinon')
const ElecrumxRoute = require('../../src/routes/v4/electrumx') const ElecrumxRoute = require('../../src/routes/v3/electrumx')
// Mocking data. // Mocking data.
const { mockReq, mockRes } = require('./mocks/express-mocks') const { mockReq, mockRes } = require('./mocks/express-mocks')
@@ -42,7 +42,7 @@ function expectRouteError (res, result, expectedError, code = 400) {
assert.equal(result.success, false) assert.equal(result.success, false)
} }
describe('#Electrumx', () => { describe('#ElectrumX Router', () => {
let req, res let req, res
let sandbox let sandbox
const electrumxRoute = new ElecrumxRoute() const electrumxRoute = new ElecrumxRoute()
@@ -199,11 +199,11 @@ describe('#Electrumx', () => {
}) })
describe('#getUtxos', () => { describe('#getUtxos', () => {
it('should throw 422 if address is empty', async () => { it('should throw 400 if address is empty', async () => {
const result = await electrumxRoute.getUtxos(req, res) const result = await electrumxRoute.getUtxos(req, res)
// console.log(`result: ${util.inspect(result)}`) // console.log(`result: ${util.inspect(result)}`)
assert.equal(res.statusCode, 422, 'Expect 422 status code') assert.equal(res.statusCode, 400, 'Expect 400 status code')
assert.property(result, 'error') assert.property(result, 'error')
assert.include(result.error, 'Unsupported address format') assert.include(result.error, 'Unsupported address format')
@@ -233,7 +233,7 @@ describe('#Electrumx', () => {
const result = await electrumxRoute.getUtxos(req, res) const result = await electrumxRoute.getUtxos(req, res)
// console.log(`result: ${util.inspect(result)}`) // console.log(`result: ${util.inspect(result)}`)
assert.equal(res.statusCode, 422, 'Expect 422 status code') assert.equal(res.statusCode, 400, 'Expect 400 status code')
assert.property(result, 'error') assert.property(result, 'error')
assert.include(result.error, 'Unsupported address format') assert.include(result.error, 'Unsupported address format')
@@ -1063,7 +1063,7 @@ describe('#Electrumx', () => {
const result = await electrumxRoute.getBalance(req, res) const result = await electrumxRoute.getBalance(req, res)
// console.log(`result: ${util.inspect(result)}`) // console.log(`result: ${util.inspect(result)}`)
assert.equal(res.statusCode, 422, 'Expect 422 status code') assert.equal(res.statusCode, 400, 'Expect 400 status code')
assert.property(result, 'error') assert.property(result, 'error')
assert.include(result.error, 'Unsupported address format') assert.include(result.error, 'Unsupported address format')
@@ -1093,7 +1093,7 @@ describe('#Electrumx', () => {
const result = await electrumxRoute.getBalance(req, res) const result = await electrumxRoute.getBalance(req, res)
// console.log(`result: ${util.inspect(result)}`) // console.log(`result: ${util.inspect(result)}`)
assert.equal(res.statusCode, 422, 'Expect 422 status code') assert.equal(res.statusCode, 400, 'Expect 400 status code')
assert.property(result, 'error') assert.property(result, 'error')
assert.include(result.error, 'Unsupported address format') assert.include(result.error, 'Unsupported address format')
@@ -1350,7 +1350,7 @@ describe('#Electrumx', () => {
const result = await electrumxRoute.getTransactions(req, res) const result = await electrumxRoute.getTransactions(req, res)
// console.log(`result: ${util.inspect(result)}`) // console.log(`result: ${util.inspect(result)}`)
assert.equal(res.statusCode, 422, 'Expect 422 status code') assert.equal(res.statusCode, 400, 'Expect 400 status code')
assert.property(result, 'error') assert.property(result, 'error')
assert.include(result.error, 'Unsupported address format') assert.include(result.error, 'Unsupported address format')
@@ -1380,7 +1380,7 @@ describe('#Electrumx', () => {
const result = await electrumxRoute.getTransactions(req, res) const result = await electrumxRoute.getTransactions(req, res)
// console.log(`result: ${util.inspect(result)}`) // console.log(`result: ${util.inspect(result)}`)
assert.equal(res.statusCode, 422, 'Expect 422 status code') assert.equal(res.statusCode, 400, 'Expect 400 status code')
assert.property(result, 'error') assert.property(result, 'error')
assert.include(result.error, 'Unsupported address format') assert.include(result.error, 'Unsupported address format')
@@ -1645,7 +1645,7 @@ describe('#Electrumx', () => {
const result = await electrumxRoute.getMempool(req, res) const result = await electrumxRoute.getMempool(req, res)
// console.log(`result: ${util.inspect(result)}`) // console.log(`result: ${util.inspect(result)}`)
assert.equal(res.statusCode, 422, 'Expect 422 status code') assert.equal(res.statusCode, 400, 'Expect 400 status code')
assert.property(result, 'error') assert.property(result, 'error')
assert.include(result.error, 'Unsupported address format') assert.include(result.error, 'Unsupported address format')
@@ -1675,7 +1675,7 @@ describe('#Electrumx', () => {
const result = await electrumxRoute.getMempool(req, res) const result = await electrumxRoute.getMempool(req, res)
// console.log(`result: ${util.inspect(result)}`) // console.log(`result: ${util.inspect(result)}`)
assert.equal(res.statusCode, 422, 'Expect 422 status code') assert.equal(res.statusCode, 400, 'Expect 400 status code')
assert.property(result, 'error') assert.property(result, 'error')
assert.include(result.error, 'Unsupported address format') assert.include(result.error, 'Unsupported address format')
+1122
View File
File diff suppressed because it is too large Load Diff
@@ -12,7 +12,7 @@ const chai = require('chai')
const assert = chai.assert const assert = chai.assert
const sinon = require('sinon') const sinon = require('sinon')
const Blockchain = require('../../src/routes/v4/full-node/blockchain') const Blockchain = require('../../src/routes/v3/full-node/blockchain')
const uut = new Blockchain() const uut = new Blockchain()
const util = require('util') const util = require('util')
@@ -821,7 +821,6 @@ describe('#BlockchainRouter', () => {
]) ])
}) })
}) })
describe('getRawMempool()', () => { describe('getRawMempool()', () => {
it('should throw 503 when network issues', async () => { it('should throw 503 when network issues', async () => {
// Save the existing RPC URL. // Save the existing RPC URL.
@@ -843,7 +842,6 @@ describe('#BlockchainRouter', () => {
'Error message expected' 'Error message expected'
) )
}) })
it('returns proper error when downstream service stalls', async () => { it('returns proper error when downstream service stalls', async () => {
// Mock the timeout error. // Mock the timeout error.
sandbox.stub(uut.axios, 'request').throws({ code: 'ECONNABORTED' }) sandbox.stub(uut.axios, 'request').throws({ code: 'ECONNABORTED' })
@@ -858,7 +856,6 @@ describe('#BlockchainRouter', () => {
'Error message expected' 'Error message expected'
) )
}) })
it('returns proper error when downstream service is down', async () => { it('returns proper error when downstream service is down', async () => {
// Mock the timeout error. // Mock the timeout error.
sandbox.stub(uut.axios, 'request').throws({ code: 'ECONNREFUSED' }) sandbox.stub(uut.axios, 'request').throws({ code: 'ECONNREFUSED' })
@@ -874,7 +871,7 @@ describe('#BlockchainRouter', () => {
) )
}) })
it('should GET /getRawMempool', async () => { it('should GET /getMempoolInfo', async () => {
// Mock the RPC call for unit tests. // Mock the RPC call for unit tests.
if (process.env.TEST === 'unit') { if (process.env.TEST === 'unit') {
sandbox sandbox
@@ -889,7 +886,6 @@ describe('#BlockchainRouter', () => {
// Not sure what other assertions should be made here. // Not sure what other assertions should be made here.
}) })
}) })
describe('getMempoolEntrySingle()', () => { describe('getMempoolEntrySingle()', () => {
it('should throw 400 if txid is empty', async () => { it('should throw 400 if txid is empty', async () => {
const result = await uut.getMempoolEntrySingle(req, res) const result = await uut.getMempoolEntrySingle(req, res)
+1 -1
View File
@@ -11,7 +11,7 @@
const chai = require('chai') const chai = require('chai')
const assert = chai.assert const assert = chai.assert
const ControlRoute = require('../../src/routes/v4/full-node/control') const ControlRoute = require('../../src/routes/v3/full-node/control')
const uut = new ControlRoute() const uut = new ControlRoute()
const sinon = require('sinon') const sinon = require('sinon')
+10 -10
View File
@@ -19,7 +19,7 @@ const sinon = require('sinon')
if (!process.env.TEST) process.env.TEST = 'unit' if (!process.env.TEST) process.env.TEST = 'unit'
// Only load blockbook library after setting BLOCKBOOK_URL env var. // Only load blockbook library after setting BLOCKBOOK_URL env var.
const EncryptionRoute = require('../../src/routes/v4/encryption') const EncryptionRoute = require('../../src/routes/v3/encryption')
const encryptionRoute = new EncryptionRoute() const encryptionRoute = new EncryptionRoute()
// Mocking data. // Mocking data.
@@ -78,11 +78,11 @@ describe('#Encryption Router', () => {
// Mock the Insight URL for unit tests. // Mock the Insight URL for unit tests.
if (process.env.TEST === 'unit') { if (process.env.TEST === 'unit') {
sandbox sandbox
.stub(encryptionRoute.bchjs.Electrumx, 'transactions') .stub(encryptionRoute.blockbook, 'balanceFromBlockbook')
.resolves(mockData.mockFulcrumTxHistory) .resolves(mockData.mockBalance)
sandbox sandbox
.stub(encryptionRoute.bchjs.RawTransactions, 'getRawTransaction') .stub(encryptionRoute.rawTransactions, 'getRawTransactionsFromNode')
.resolves(mockData.mockTxDetails2) .resolves(mockData.mockTxDetails)
} }
const result = await encryptionRoute.getPublicKey(req, res) const result = await encryptionRoute.getPublicKey(req, res)
@@ -105,8 +105,8 @@ describe('#Encryption Router', () => {
// Mock the Insight URL for unit tests. // Mock the Insight URL for unit tests.
if (process.env.TEST === 'unit') { if (process.env.TEST === 'unit') {
sandbox sandbox
.stub(encryptionRoute.bchjs.Electrumx, 'transactions') .stub(encryptionRoute.blockbook, 'balanceFromBlockbook')
.resolves(mockData.mockFulcrumNoTxHistory) .resolves(mockData.mockNoTxHistory)
} }
const result = await encryptionRoute.getPublicKey(req, res) const result = await encryptionRoute.getPublicKey(req, res)
@@ -126,10 +126,10 @@ describe('#Encryption Router', () => {
// Mock the Insight URL for unit tests. // Mock the Insight URL for unit tests.
if (process.env.TEST === 'unit') { if (process.env.TEST === 'unit') {
sandbox sandbox
.stub(encryptionRoute.bchjs.Electrumx, 'transactions') .stub(encryptionRoute.blockbook, 'balanceFromBlockbook')
.resolves(mockData.mockFulcrumNoSendBalance) .resolves(mockData.mockNoSendBalance)
sandbox sandbox
.stub(encryptionRoute.bchjs.RawTransactions, 'getRawTransaction') .stub(encryptionRoute.rawTransactions, 'getRawTransactionsFromNode')
.resolves(mockData.mockNoSendTx) .resolves(mockData.mockNoSendTx)
} }
@@ -4,14 +4,14 @@
'use strict' 'use strict'
const assert = require('chai').assert // const assert = require('chai').assert
// const axios = require('axios') // const axios = require('axios')
// Used for debugging. // Used for debugging.
const util = require('util') const util = require('util')
util.inspect.defaultOptions = { depth: 1 } util.inspect.defaultOptions = { depth: 1 }
const Price = require('../../../src/routes/v4/price') const Price = require('../../../src/routes/v3/price')
const price = new Price() const price = new Price()
const { mockReq, mockRes } = require('../mocks/express-mocks') const { mockReq, mockRes } = require('../mocks/express-mocks')
@@ -29,25 +29,6 @@ describe('#price', () => {
it('should get the USD price', async () => { it('should get the USD price', async () => {
const result = await price.getUSD(req, res) const result = await price.getUSD(req, res)
console.log(`result: ${util.inspect(result)}`) console.log(`result: ${util.inspect(result)}`)
assert.isNumber(result.usd)
})
})
describe('#getBCHAUSD', () => {
it('should get the USD price of BCHA', async () => {
const result = await price.getBCHAUSD(req, res)
console.log(`result: ${util.inspect(result)}`)
assert.isNumber(result.usd)
})
})
describe('#getBCHUSD', () => {
it('should get the USD price of BCH', async () => {
const result = await price.getBCHUSD(req, res)
console.log(`result: ${util.inspect(result)}`)
assert.isNumber(result.usd)
}) })
}) })
}) })
@@ -1,6 +1,7 @@
/* /*
These tests have been deprecated. To test bch-api rate limits, run the e2e This integration tests should be run against a live bch-api REST server. It
tests in the bch-js repository. tests to ensure the rate-limits are working as expected. Adjust the values
in the tests below to match the rate limit setting in your own installation.
*/ */
'use strict' 'use strict'
@@ -13,14 +14,14 @@ const axios = require('axios')
const util = require('util') const util = require('util')
util.inspect.defaultOptions = { depth: 1 } util.inspect.defaultOptions = { depth: 1 }
// const SERVER = `http://192.168.0.36:12400/v4/` // const SERVER = `http://192.168.0.36:12400/v3/`
const SERVER = 'http://localhost:3000/v4/' const SERVER = 'http://localhost:3000/v3/'
// const SERVER = 'https://api.fullstack.cash/v4/' // const SERVER = 'https://api.fullstack.cash/v3/'
//
// const TEST_JWT =
// 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpZCI6IjVlODhhY2JmMDIyMWMxMDAxMmFkOTNmZiIsImVtYWlsIjoiY2hyaXMudHJvdXRuZXJAZ21haWwuY29tIiwiYXBpTGV2ZWwiOjQwLCJyYXRlTGltaXQiOjMsImlhdCI6MTYwMDYyODk1MSwiZXhwIjoxNjAzMjIwOTUxfQ.JPXDJQsxJFtCGZjHOd-hRfJuY41Ef_FQ4ET06CtYdNk'
describe('#JWT rate limits', () => { const TEST_JWT =
'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpZCI6IjVlODhhY2JmMDIyMWMxMDAxMmFkOTNmZiIsImVtYWlsIjoiY2hyaXMudHJvdXRuZXJAZ21haWwuY29tIiwiYXBpTGV2ZWwiOjQwLCJyYXRlTGltaXQiOjMsImlhdCI6MTYwMDYyODk1MSwiZXhwIjoxNjAzMjIwOTUxfQ.JPXDJQsxJFtCGZjHOd-hRfJuY41Ef_FQ4ET06CtYdNk'
describe('#rate limits', () => {
it('should get control/getNetworkInfo() with no auth', async () => { it('should get control/getNetworkInfo() with no auth', async () => {
const options = { const options = {
method: 'GET', method: 'GET',
@@ -35,24 +36,25 @@ describe('#JWT rate limits', () => {
assert.hasAnyKeys(result.data, ['version']) assert.hasAnyKeys(result.data, ['version'])
}) })
it('should trigger rate-limit handler if rate limits exceeds 20 request per minute', async () => { it('should trigger rate-limit handler if rate limits exceeds 5 request per minute', async () => {
try { try {
// Actual rate limit is 60 per minute X 4 nodes = 240 rpm.
const options = { const options = {
method: 'GET', method: 'GET',
url: `${SERVER}control/getNetworkInfo` url: `${SERVER}control/getNetworkInfo`
} }
const promises = [] const promises = []
for (let i = 0; i < 30; i++) { for (let i = 0; i < 5; i++) {
const promise = axios(options) const promise = axios(options)
promises.push(promise) promises.push(promise)
} }
await Promise.all(promises) await Promise.all(promises)
assert.fail('Unexpected result!') assert.equal(true, false, 'Unexpected result!')
} catch (err) { } catch (err) {
console.log('err: ', err) // console.log(`err.response: ${util.inspect(err.response)}`)
assert.equal(err.response.status, 429) assert.equal(err.response.status, 429)
assert.include(err.response.data.error, 'Too many requests') assert.include(err.response.data.error, 'Too many requests')
@@ -133,32 +135,32 @@ describe('#JWT rate limits', () => {
// } // }
// }) // })
// it('should unlock pro-tier for a valid JWT token', async () => { it('should unlock pro-tier for a valid JWT token', async () => {
// try { try {
// // Actual rate limit is 60 per minute X 4 nodes = 240 rpm. // Actual rate limit is 60 per minute X 4 nodes = 240 rpm.
// const options = { const options = {
// method: 'GET', method: 'GET',
// url: `${SERVER}control/`, url: `${SERVER}control/`,
// headers: { headers: {
// Authorization: `Token ${TEST_JWT}` Authorization: `Token ${TEST_JWT}`
// } }
// } }
//
// const promises = [] const promises = []
// for (let i = 0; i < 60; i++) { for (let i = 0; i < 60; i++) {
// const promise = axios(options) const promise = axios(options)
// promises.push(promise) promises.push(promise)
// } }
//
// await Promise.all(promises) await Promise.all(promises)
//
// // assert.equal(true, false, "Unexpected result!") // assert.equal(true, false, "Unexpected result!")
// assert.equal(true, true, 'Not throwing an error is a pass!') assert.equal(true, true, 'Not throwing an error is a pass!')
// } catch (err) { } catch (err) {
// console.log(`err.response: ${util.inspect(err.response)}`) console.log(`err.response: ${util.inspect(err.response)}`)
//
// assert.equal(true, false, 'Unexpected result!') assert.equal(true, false, 'Unexpected result!')
// } }
// // Override default timeout for this test. // Override default timeout for this test.
// }).timeout(20000) }).timeout(20000)
}) })
@@ -7,22 +7,21 @@
TODO: TODO:
-/rawtransactions/sendRawTransaction is more appropropriate for an e2e test, -/rawtransactions/sendRawTransaction is more appropropriate for an e2e test,
so it is omitted here. so it is omitted here.
- Replace request-promise with axios.
*/ */
'use strict' 'use strict'
// const chai = require('chai') const chai = require('chai')
// const assert = chai.assert const assert = chai.assert
// const rawtransactions = require('../../../dist/routes/v2/rawtransactions') // const rawtransactions = require('../../../dist/routes/v2/rawtransactions')
// const rp = require('request-promise') const rp = require('request-promise')
// Used for debugging. // Used for debugging.
const util = require('util') const util = require('util')
util.inspect.defaultOptions = { depth: 1 } util.inspect.defaultOptions = { depth: 1 }
// const mockData = require('../mocks/raw-transactions-mocks') // const mockData = require('../mocks/raw-transactions-mocks')
/*
describe('#Raw-Transactions', () => { describe('#Raw-Transactions', () => {
describe('#root', () => { describe('#root', () => {
it('should return root', async () => { it('should return root', async () => {
@@ -190,4 +189,3 @@ describe('#Raw-Transactions', () => {
}) })
}) })
}) })
*/
@@ -19,7 +19,7 @@ if (!process.env.SLPDB_URL) {
) )
} }
const SLP = require('../../../src/routes/v4/slp') const SLP = require('../../../src/routes/v3/slp')
const slp = new SLP() const slp = new SLP()
const BCHJS = require('@psf/bch-js') const BCHJS = require('@psf/bch-js')
@@ -110,8 +110,8 @@ describe('#slp', () => {
req.body.tokenUtxos = [ req.body.tokenUtxos = [
{ {
tokenId: tokenId:
'38e97c5d7d3585a2cbf3f9580c82ca33985f9cb0845d4dcce220cb709f9538b0', '0a321bff9761f28e06a268b14711274bb77617410a16807bd0437ef234a072b1',
decimals: 8, decimals: 0,
tokenQty: 2 tokenQty: 2
} }
] ]
@@ -214,39 +214,6 @@ describe('#slp', () => {
}) })
}) })
describe('#hydrateUtxosWL', () => {
it('should return utxo details', async () => {
const utxos = [
{
utxos: [
{
tx_hash:
'89b3f0c84efe8b01b24e2d7ac08636de5781f31dbb84478e3de868ca0a7ed93a',
tx_pos: 1,
value: 546
}
]
}
]
req.body.utxos = utxos
const result = await slp.hydrateUtxosWL(req, res)
// console.log(`result: ${JSON.stringify(result, null, 2)}`)
// Test the general structure of the output.
assert.isArray(result.slpUtxos)
assert.equal(result.slpUtxos.length, 1)
assert.equal(result.slpUtxos[0].utxos.length, 1)
// Test the non-slp UTXO.
assert.property(result.slpUtxos[0].utxos[0], 'tx_hash')
assert.property(result.slpUtxos[0].utxos[0], 'tx_pos')
assert.property(result.slpUtxos[0].utxos[0], 'value')
assert.property(result.slpUtxos[0].utxos[0], 'isValid')
assert.equal(result.slpUtxos[0].utxos[0].isValid, true)
})
})
describe('#validate2Single', () => { describe('#validate2Single', () => {
it('should invalidate a known invalid TXID', async () => { it('should invalidate a known invalid TXID', async () => {
const txid = const txid =
@@ -291,171 +258,4 @@ describe('#slp', () => {
// ) // )
// }) // })
}) })
describe('#validateBulk', () => {
it('should handle a mix of valid, invalid, and non-SLP txs', async () => {
const txids = [
// Malformed SLP tx
'f7e5199ef6669ad4d078093b3ad56e355b6ab84567e59ad0f08a5ad0244f783a',
// Normal TX (non-SLP)
'01cdaec2f8b311fc2d6ecc930247bd45fa696dc204ab684596e281fe1b06c1f0',
// Valid PSF SLP tx
'daf4d8b8045e7a90b7af81bfe2370178f687da0e545511bce1c9ae539eba5ffd',
// Valid SLP token not in whitelist
'3a4b628cbcc183ab376d44ce5252325f042268307ffa4a53443e92b6d24fb488',
// Token send on BCHN network.
'402c663379d9699b6e2dd38737061e5888c5a49fca77c97ab98e79e08959e019',
// Token send on ABC network.
'336bfe2168aac4c3303508a9e8548a0d33797a83b85b76a12d845c8d6674f79d',
// Known invalid SLP token send of PSF tokens.
'2bf691ad3679d928fef880b8a45b93b233f8fa0d0a92cf792313dbe77b1deb74'
]
req.body.txids = txids
const result = await slp.validateBulk(req, res)
// console.log(`result: ${JSON.stringify(result, null, 2)}`)
// BCHN expected results
if (process.env.ISBCHN) {
assert.equal(result[0].txid, txids[0])
assert.equal(result[0].valid, null)
assert.equal(result[1].txid, txids[1])
assert.equal(result[1].valid, null)
assert.equal(result[2].txid, txids[2])
assert.equal(result[2].valid, true)
assert.equal(result[3].txid, txids[3])
assert.equal(result[3].valid, true)
// Note: This should change from null to true once SLPDB finishes indexing.
assert.equal(result[4].txid, txids[4])
assert.equal(result[4].valid, null)
assert.equal(result[5].txid, txids[5])
assert.equal(result[5].valid, null)
assert.equal(result[6].txid, txids[6])
assert.equal(result[6].valid, false)
assert.include(
result[6].invalidReason,
'Token outputs are greater than valid token inputs'
)
} else {
assert.equal(result[0].txid, txids[0])
assert.equal(result[0].valid, null)
assert.equal(result[1].txid, txids[1])
assert.equal(result[1].valid, null)
assert.equal(result[2].txid, txids[2])
assert.equal(result[2].valid, true)
assert.equal(result[3].txid, txids[3])
assert.equal(result[3].valid, true)
assert.equal(result[4].txid, txids[4])
assert.equal(result[4].valid, null)
assert.equal(result[5].txid, txids[5])
assert.equal(result[5].valid, true)
assert.equal(result[6].txid, txids[6])
assert.equal(result[6].valid, false)
assert.include(
result[6].invalidReason,
'Token outputs are greater than valid token inputs'
)
}
})
})
describe('#validate3Bulk', () => {
it('should handle a mix of valid, invalid, and non-SLP txs', async () => {
const txids = [
// Malformed SLP tx
'f7e5199ef6669ad4d078093b3ad56e355b6ab84567e59ad0f08a5ad0244f783a',
// Normal TX (non-SLP)
'01cdaec2f8b311fc2d6ecc930247bd45fa696dc204ab684596e281fe1b06c1f0',
// Valid PSF SLP tx
'daf4d8b8045e7a90b7af81bfe2370178f687da0e545511bce1c9ae539eba5ffd',
// Valid SLP token not in whitelist
'3a4b628cbcc183ab376d44ce5252325f042268307ffa4a53443e92b6d24fb488',
// Token send on BCHN network.
'402c663379d9699b6e2dd38737061e5888c5a49fca77c97ab98e79e08959e019',
// Token send on ABC network.
'336bfe2168aac4c3303508a9e8548a0d33797a83b85b76a12d845c8d6674f79d',
// Known invalid SLP token send of PSF tokens.
'2bf691ad3679d928fef880b8a45b93b233f8fa0d0a92cf792313dbe77b1deb74'
]
req.body.txids = txids
const result = await slp.validate3Bulk(req, res)
console.log(`result: ${JSON.stringify(result, null, 2)}`)
// BCHN expected results
if (process.env.ISBCHN) {
assert.equal(result[0].txid, txids[0])
assert.equal(result[0].valid, null)
assert.equal(result[1].txid, txids[1])
assert.equal(result[1].valid, null)
assert.equal(result[2].txid, txids[2])
assert.equal(result[2].valid, true)
assert.equal(result[3].txid, txids[3])
assert.equal(result[3].valid, null)
// Note: This should change from null to true once SLPDB finishes indexing.
assert.equal(result[4].txid, txids[4])
assert.equal(result[4].valid, null)
assert.equal(result[5].txid, txids[5])
assert.equal(result[5].valid, null)
assert.equal(result[6].txid, txids[6])
assert.equal(result[6].valid, false)
assert.include(
result[6].invalidReason,
'Token outputs are greater than valid token inputs'
)
} else {
assert.equal(result[0].txid, txids[0])
assert.equal(result[0].valid, null)
assert.equal(result[1].txid, txids[1])
assert.equal(result[1].valid, null)
assert.equal(result[2].txid, txids[2])
assert.equal(result[2].valid, true)
assert.equal(result[3].txid, txids[3])
assert.equal(result[3].valid, true)
assert.equal(result[4].txid, txids[4])
assert.equal(result[4].valid, null)
assert.equal(result[5].txid, txids[5])
assert.equal(result[5].valid, true)
assert.equal(result[6].txid, txids[6])
assert.equal(result[6].valid, false)
assert.include(
result[6].invalidReason,
'Token outputs are greater than valid token inputs'
)
}
})
})
describe('#getStatus', () => {
it('should get the SLPDB status', async () => {
const result = await slp.getStatus(req, res)
// console.log(`result: ${JSON.stringify(result, null, 2)}`)
assert.property(result, 'bchBlockHeight')
})
})
}) })
@@ -18,7 +18,7 @@ if (!process.env.SLPDB_URL) {
throw new Error('SLPDB_URL and SLPDB_PASS must be defined in order to run these tests.') throw new Error('SLPDB_URL and SLPDB_PASS must be defined in order to run these tests.')
} }
const SLPDB = require('../../../src/routes/v4/services/slpdb') const SLPDB = require('../../../src/routes/v3/services/slpdb')
const slpdb = new SLPDB() const slpdb = new SLPDB()
describe('#slpdb', () => { describe('#slpdb', () => {
+1 -1
View File
@@ -10,7 +10,7 @@
const chai = require('chai') const chai = require('chai')
const assert = chai.assert const assert = chai.assert
const MiningRoute = require('../../src/routes/v4/full-node/mining') const MiningRoute = require('../../src/routes/v3/full-node/mining')
const uut = new MiningRoute() const uut = new MiningRoute()
// const nock = require('nock') // HTTP mocking // const nock = require('nock') // HTTP mocking
+179
View File
@@ -0,0 +1,179 @@
/*
Mock data for the encryption test library
*/
const mockBalance = {
page: 1,
totalPages: 1,
itemsOnPage: 1000,
address: 'bitcoincash:qrehqueqhw629p6e57994436w730t4rzasnly00ht0',
balance: '582434',
totalReceived: '213083250',
totalSent: '212500816',
unconfirmedBalance: '0',
unconfirmedTxs: 0,
txs: 12,
txids: [
'c42f8f16d3baa2ee343ea89ef110dfe094992379d08edd30887b8ca7ee671c9a',
'1afcc63b244182647909539ebe3f4a44b8ea4120a95edb8d9eebe5347b9491bb',
'e4a0ac48ff3f42fc342717a2a3d34248e5e85bae79d59bd20e1b60e61b1c500f',
'0f9b49cafeb9ae1d741cdb12137c92816aa8470944c270a78ba2e610bd59190d',
'ceb0cab0e37b59caf3ca29e1a698d19ff47f2827dd09cb2f3b91b9100b1dad1c',
'8bc2134c7e48e56e1769b3d7c4c1e3a0acc68e1e58160eee6fa67f3208c07262',
'b3792d28377b975560e1b6f09e48aeff8438d4c6969ca578bd406393bd50bd7d',
'ecc1b51bac767880382bf3190ff17abf78d0936843a022a943d871116ed50368',
'9ea667bcfc9cd337bd6c5583d8094c1b1942bd2015d95b54189deac5070eeff0',
'6960255abe64893073921e96bf3c053c82686e0fc22a565494fbe2a31e766975',
'7e9aa7a74de2b30200a2d6fc748ff35a0c753221444194f720bb7f61ef1d9153',
'eff00a9538487ff44243c75fb13de19b5783454c42c81b9aff9afbfd09cbaec3'
]
}
const mockTxDetails = {
txid: '1afcc63b244182647909539ebe3f4a44b8ea4120a95edb8d9eebe5347b9491bb',
hash: '1afcc63b244182647909539ebe3f4a44b8ea4120a95edb8d9eebe5347b9491bb',
version: 1,
size: 437,
locktime: 0,
vin: [
{
txid: 'c42f8f16d3baa2ee343ea89ef110dfe094992379d08edd30887b8ca7ee671c9a',
vout: 0,
scriptSig: {
asm:
'30450221008052d3b067418d53585fb8f91e1b57cf3c040dc9c07a70f393ed663b3f7502c50220749aa8e09ac922e78cb474c8097873cfb2634108d7acaa7db32a73a35743da97[ALL|FORKID] 044eb40b025df18409f2a5197b010dd62a9e65d9a74e415e5b10367721a9c4baa7ebfee22d14b8ece1c9bd70c0d9e5e8b00b61b81b88a1b5ce6f24eac6b8a34b2c',
hex:
'4830450221008052d3b067418d53585fb8f91e1b57cf3c040dc9c07a70f393ed663b3f7502c50220749aa8e09ac922e78cb474c8097873cfb2634108d7acaa7db32a73a35743da974141044eb40b025df18409f2a5197b010dd62a9e65d9a74e415e5b10367721a9c4baa7ebfee22d14b8ece1c9bd70c0d9e5e8b00b61b81b88a1b5ce6f24eac6b8a34b2c'
},
sequence: 4294967295
},
{
txid: 'e4a0ac48ff3f42fc342717a2a3d34248e5e85bae79d59bd20e1b60e61b1c500f',
vout: 1,
scriptSig: {
asm:
'3044022050d7fe7cdcec81eefa0987b88ddb83274d8e9063d927090dc4c2d1db76c512d302207dc1eea439a627476265ed87f59cc9823fb572ffc2640f0218d7bddc9a621c6e[ALL|FORKID] 044eb40b025df18409f2a5197b010dd62a9e65d9a74e415e5b10367721a9c4baa7ebfee22d14b8ece1c9bd70c0d9e5e8b00b61b81b88a1b5ce6f24eac6b8a34b2c',
hex:
'473044022050d7fe7cdcec81eefa0987b88ddb83274d8e9063d927090dc4c2d1db76c512d302207dc1eea439a627476265ed87f59cc9823fb572ffc2640f0218d7bddc9a621c6e4141044eb40b025df18409f2a5197b010dd62a9e65d9a74e415e5b10367721a9c4baa7ebfee22d14b8ece1c9bd70c0d9e5e8b00b61b81b88a1b5ce6f24eac6b8a34b2c'
},
sequence: 4294967295
}
],
vout: [
{
value: 0.47,
n: 0,
scriptPubKey: {
asm:
'OP_DUP OP_HASH160 7ab928d0b41194411a2e87a782b688c7cc69ba46 OP_EQUALVERIFY OP_CHECKSIG',
hex: '76a9147ab928d0b41194411a2e87a782b688c7cc69ba4688ac',
reqSigs: 1,
type: 'pubkeyhash',
addresses: ['bitcoincash:qpatj2xsksgegsg696r60q4k3rruc6d6gc3srp333v']
}
},
{
value: 0.00531373,
n: 1,
scriptPubKey: {
asm:
'OP_DUP OP_HASH160 f3707320bbb4a28759a78a5ad63a77a2f5d462ec OP_EQUALVERIFY OP_CHECKSIG',
hex: '76a914f3707320bbb4a28759a78a5ad63a77a2f5d462ec88ac',
reqSigs: 1,
type: 'pubkeyhash',
addresses: ['bitcoincash:qrehqueqhw629p6e57994436w730t4rzasnly00ht0']
}
}
],
hex:
'01000000029a1c67eea78c7b8830dd8ed079239994e0df10f19ea83e34eea2bad3168f2fc4000000008b4830450221008052d3b067418d53585fb8f91e1b57cf3c040dc9c07a70f393ed663b3f7502c50220749aa8e09ac922e78cb474c8097873cfb2634108d7acaa7db32a73a35743da974141044eb40b025df18409f2a5197b010dd62a9e65d9a74e415e5b10367721a9c4baa7ebfee22d14b8ece1c9bd70c0d9e5e8b00b61b81b88a1b5ce6f24eac6b8a34b2cffffffff0f501c1be6601b0ed29bd579ae5be8e54842d3a3a2172734fc423fff48aca0e4010000008a473044022050d7fe7cdcec81eefa0987b88ddb83274d8e9063d927090dc4c2d1db76c512d302207dc1eea439a627476265ed87f59cc9823fb572ffc2640f0218d7bddc9a621c6e4141044eb40b025df18409f2a5197b010dd62a9e65d9a74e415e5b10367721a9c4baa7ebfee22d14b8ece1c9bd70c0d9e5e8b00b61b81b88a1b5ce6f24eac6b8a34b2cffffffff02c029cd02000000001976a9147ab928d0b41194411a2e87a782b688c7cc69ba4688acad1b0800000000001976a914f3707320bbb4a28759a78a5ad63a77a2f5d462ec88ac00000000',
blockhash: '0000000000000000045e5e52fb4f9746b3d15d3062855fd346aaef3debef4360',
confirmations: 71465,
time: 1545564654,
blocktime: 1545564654
}
const mockNoTxHistory = {
page: 1,
totalPages: 1,
itemsOnPage: 1000,
address: 'bitcoincash:qqwfpk04ecf69wuprj9yjys9rla5mk7rj5j8uthqel',
balance: '0',
totalReceived: '0',
totalSent: '0',
unconfirmedBalance: '0',
unconfirmedTxs: 0,
txs: 0
}
const mockNoSendBalance = {
page: 1,
totalPages: 1,
itemsOnPage: 1000,
address: 'bitcoincash:qq78nwj5x97yh6wtlfd27dtlwjuh70vkjc59h8tgtg',
balance: '0',
totalReceived: '0',
totalSent: '0',
unconfirmedBalance: '600',
unconfirmedTxs: 1,
txs: 0,
txids: [
'a3b62cd4f4c56ba52139179db14bffd4ab22a2e077f3c62bd5cf0541bfcaf023'
]
}
const mockNoSendTx = {
txid: 'a3b62cd4f4c56ba52139179db14bffd4ab22a2e077f3c62bd5cf0541bfcaf023',
hash: 'a3b62cd4f4c56ba52139179db14bffd4ab22a2e077f3c62bd5cf0541bfcaf023',
version: 2,
size: 226,
locktime: 0,
vin: [
{
txid: '681cc1d392975a7e78cbd490b8a940d7eb029c554f2cee332e3a7119d77fb189',
vout: 1,
scriptSig: {
asm: '30450221008a7c86ce3a9f5765573440143a00631bb5afa9a58b24674a7fa794bb11ffffc30220397dd4c672c3e0dbe0c4eb6390b5727c5fdc3a42cf3ae275ac13b44d27bb234f[ALL|FORKID] 02bc9fc26ae2bd9a9a14e9baeda4b4fdd95a00bcbf375732c647488b3ce82a2fd7',
hex: '4830450221008a7c86ce3a9f5765573440143a00631bb5afa9a58b24674a7fa794bb11ffffc30220397dd4c672c3e0dbe0c4eb6390b5727c5fdc3a42cf3ae275ac13b44d27bb234f412102bc9fc26ae2bd9a9a14e9baeda4b4fdd95a00bcbf375732c647488b3ce82a2fd7'
},
sequence: 4294967295
}
],
vout: [
{
value: 0.000006,
n: 0,
scriptPubKey: {
asm: 'OP_DUP OP_HASH160 3c79ba54317c4be9cbfa5aaf357f74b97f3d9696 OP_EQUALVERIFY OP_CHECKSIG',
hex: '76a9143c79ba54317c4be9cbfa5aaf357f74b97f3d969688ac',
reqSigs: 1,
type: 'pubkeyhash',
addresses: [
'bitcoincash:qq78nwj5x97yh6wtlfd27dtlwjuh70vkjc59h8tgtg'
]
}
},
{
value: 0.00003664,
n: 1,
scriptPubKey: {
asm: 'OP_DUP OP_HASH160 58502a73888e0a386fc69b44850af4b2a86ac9a6 OP_EQUALVERIFY OP_CHECKSIG',
hex: '76a91458502a73888e0a386fc69b44850af4b2a86ac9a688ac',
reqSigs: 1,
type: 'pubkeyhash',
addresses: [
'bitcoincash:qpv9q2nn3z8q5wr0c6d5fpg27je2s6kf5cavp4ney0'
]
}
}
],
hex: '020000000189b17fd719713a2e33ee2c4f559c02ebd740a9b890d4cb787e5a9792d3c11c68010000006b4830450221008a7c86ce3a9f5765573440143a00631bb5afa9a58b24674a7fa794bb11ffffc30220397dd4c672c3e0dbe0c4eb6390b5727c5fdc3a42cf3ae275ac13b44d27bb234f412102bc9fc26ae2bd9a9a14e9baeda4b4fdd95a00bcbf375732c647488b3ce82a2fd7ffffffff0258020000000000001976a9143c79ba54317c4be9cbfa5aaf357f74b97f3d969688ac500e0000000000001976a91458502a73888e0a386fc69b44850af4b2a86ac9a688ac00000000'
}
module.exports = {
mockBalance,
mockTxDetails,
mockNoTxHistory,
mockNoSendBalance,
mockNoSendTx
}
@@ -216,26 +216,6 @@ const mockCoinbaseFeed = {
} }
} }
const mockCoinexFeed = {
code: 0,
data: {
date: 1605649499848,
ticker: {
vol: '26717.39062407',
low: '10.4000',
open: '11.8000',
high: '19.0029',
last: '18.5000',
buy: '18.0100',
buy_amount: '200.00000000',
sell: '18.5000',
sell_amount: '47.89580474'
}
},
message: 'OK'
}
module.exports = { module.exports = {
mockCoinbaseFeed, mockCoinbaseFeed
mockCoinexFeed
} }
@@ -341,22 +341,6 @@ const mockTwoRedundentTxid = {
u: [] u: []
} }
const mockPsfToken = {
c: [
{
_id: '5fcaf6152898f9887902986c',
tx: {
h: 'cdfed769ef7b69e09be06d2821b88598d9a5d711b8f9bd369763c78b7a578fbc'
},
slp: {
valid: true,
invalidReason: null
}
}
],
u: []
}
const mockTxHistory = [ const mockTxHistory = [
{ {
tx: { tx: {
@@ -542,483 +526,6 @@ const mockTxHistory = [
} }
] ]
const mockValidateBulk = {
c: [
{
_id: '5fcc1ae2aff6379ca5bbb213',
tx: {
h: '336bfe2168aac4c3303508a9e8548a0d33797a83b85b76a12d845c8d6674f79d'
},
slp: {
valid: true,
invalidReason: null
}
},
{
_id: '5fc9b4db4d54eece25b52762',
tx: {
h: 'daf4d8b8045e7a90b7af81bfe2370178f687da0e545511bce1c9ae539eba5ffd'
},
slp: {
valid: true,
invalidReason: null
}
},
{
_id: '5fc989a14d54eece25af88a6',
tx: {
h: '2bf691ad3679d928fef880b8a45b93b233f8fa0d0a92cf792313dbe77b1deb74'
},
slp: {
valid: false,
invalidReason: 'Token outputs are greater than valid token inputs.'
}
},
{
_id: '5fc974d14d54eece25ad5df2',
tx: {
h: '3a4b628cbcc183ab376d44ce5252325f042268307ffa4a53443e92b6d24fb488'
},
slp: {
valid: true,
invalidReason: null
}
}
],
u: []
}
const mockValidate3Bulk = {
c: [
{
_id: '5fcaf60b2898f98879029754',
tx: {
h: 'daf4d8b8045e7a90b7af81bfe2370178f687da0e545511bce1c9ae539eba5ffd'
},
slp: {
valid: true,
invalidReason: null
}
},
{
_id: '5fcaf5382898f988790275d0',
tx: {
h: '2bf691ad3679d928fef880b8a45b93b233f8fa0d0a92cf792313dbe77b1deb74'
},
slp: {
valid: false,
invalidReason: 'Token outputs are greater than valid token inputs.'
}
}
],
u: []
}
const mockStatus = {
_id: '5fe408b7cd2bd2000f7fdd0a',
version: '1.0.0-beta-rc13',
versionHash: 'bb4a805610b9e4d67c1595b716df36190c75e8b1',
deplVersionHash: null,
startCmd: 'node index run',
context: 'SLPDB',
lastStatusUpdate: {
utc: 'Thu, 24 Dec 2020 03:19:19 GMT',
unix: 1608779959
},
lastIncomingTxnZmq: {
utc: 'Thu, 24 Dec 2020 03:19:16 GMT',
unix: 1608779956
},
lastIncomingBlockZmq: {
utc: 'Thu, 24 Dec 2020 03:00:27 GMT',
unix: 1608778827
},
lastOutgoingTxnZmq: null,
lastOutgoingBlockZmq: null,
state: 'RUNNING',
stateHistory: [
{
utc: 'Mon, 07 Dec 2020 15:26:23 GMT',
state: 'STARTUP_BLOCK_SYNC'
},
{
utc: 'Mon, 07 Dec 2020 15:52:20 GMT',
state: 'RUNNING'
},
{
utc: 'Tue, 08 Dec 2020 14:42:29 GMT',
state: 'PRE_STARTUP'
},
{
utc: 'Tue, 08 Dec 2020 14:42:29 GMT',
state: 'STARTUP_BLOCK_SYNC'
},
{
utc: 'Tue, 08 Dec 2020 14:59:53 GMT',
state: 'RUNNING'
},
{
utc: 'Tue, 08 Dec 2020 19:35:36 GMT',
state: 'EXITED_ON_ERROR'
},
{
utc: 'Tue, 08 Dec 2020 19:35:36 GMT',
state: 'EXITED_ON_ERROR'
},
{
utc: 'Tue, 08 Dec 2020 19:35:49 GMT',
state: 'PRE_STARTUP'
},
{
utc: 'Tue, 08 Dec 2020 19:35:49 GMT',
state: 'STARTUP_BLOCK_SYNC'
},
{
utc: 'Tue, 08 Dec 2020 19:53:04 GMT',
state: 'RUNNING'
}
],
network: 'mainnet',
bchBlockHeight: 667203,
bchBlockHash:
'0000000000000000035f9c26b9ef3db37eba8c9351218c67c92d80444b867858',
slpProcessedBlockHeight: 667203,
mempoolInfoBch: {
loaded: true,
size: 441,
bytes: 192651,
usage: 663056,
maxmempool: 300000000,
mempoolminfee: 0.00001,
minrelaytxfee: 0.00001
},
mempoolSizeSlp: 68,
tokensCount: 77879,
pastStackTraces: [
'[Tue, 08 Dec 2020 19:35:36 GMT] MongoServerSelectionError: connection <monitor> to 172.17.0.1:12301 timed out\n at Timeout._onTimeout (/home/safeuser/SLPDB/node_modules/mongodb/lib/core/sdam/topology.js:438:30)\n at listOnTimeout (internal/timers.js:554:17)\n at processTimers (internal/timers.js:497:7)',
'[Tue, 08 Dec 2020 19:35:36 GMT] MongoServerSelectionError: connection <monitor> to 172.17.0.1:12301 timed out\n at Timeout._onTimeout (/home/safeuser/SLPDB/node_modules/mongodb/lib/core/sdam/topology.js:438:30)\n at listOnTimeout (internal/timers.js:554:17)\n at processTimers (internal/timers.js:497:7)'
],
doubleSpends: [
{
txo:
'3e377f67e9d065f02e47633d847cdb04b0679d51baccdc0feea5297529e330b5:820',
details: {
originalTxid:
'dc148149d25a546fffe0b207c62b192811c288481df5f43f1f76f67b6004f98c',
current:
'de12db60e253c378eeebb1df85153168a998db8267ec54b38589cc31348b2b8d',
time: {
utc: 'Sat, 19 Dec 2020 02:25:37 GMT',
unix: 1608344737
}
}
},
{
txo: '4b7e94fc59a685bce31ee9444d20ebb0ae0478111a8bfa7ec9b5c381e2657191:2',
details: {
originalTxid:
'a4e00c025c07a770874ecc6b2d1004fe3c561f2aceb0a8b95996e26709df1a57',
current:
'33ccab4a971331897bb3ff3978366e3575c54d83ae1a35984e50d93cffe66b95',
time: {
utc: 'Sat, 19 Dec 2020 02:33:28 GMT',
unix: 1608345208
}
}
},
{
txo: 'bd5ac3d02652038cc888922e50c9b57db0dfdbb5b26bb28a2e85fd28493e85e1:2',
details: {
originalTxid:
'53032355ab142ac050f1e37445ecb19ad22c5331185df7b3173e88563194ba18',
current:
'0b3ab983c5abb1ceb6f60035ff869e159e27ab5d2a4173d0d6ce027348d4dbdd',
time: {
utc: 'Sat, 19 Dec 2020 04:21:27 GMT',
unix: 1608351687
}
}
},
{
txo:
'8d91432598943cc22b93d10df6808abccba4baebdf2273b375ce3616a2001c07:573',
details: {
originalTxid:
'53032355ab142ac050f1e37445ecb19ad22c5331185df7b3173e88563194ba18',
current:
'7ddc241b62625ddc1d134ae17e74bf2b6f63a4c28e3c3a4f11d833270bdebcdb',
time: {
utc: 'Sat, 19 Dec 2020 04:26:12 GMT',
unix: 1608351972
}
}
},
{
txo: 'ff3a9268952408217f60f1689aa1137e929e6572413e429554996ec3e9b223b5:2',
details: {
originalTxid:
'342adc882eff630aa7b50098adaace3626c1e60acbdf937e41832c4508bbdb4b',
current:
'01538a27e7e164bd52a9fd3df36cd724846f44a4c26aea8764ee982486b580ff',
time: {
utc: 'Sat, 19 Dec 2020 12:58:36 GMT',
unix: 1608382716
}
}
},
{
txo:
'9f7de7ea735d46fbb9214353fec0b250f48a24340e0909eecd83f53817b6aaa3:314',
details: {
originalTxid:
'342adc882eff630aa7b50098adaace3626c1e60acbdf937e41832c4508bbdb4b',
current:
'01538a27e7e164bd52a9fd3df36cd724846f44a4c26aea8764ee982486b580ff',
time: {
utc: 'Sat, 19 Dec 2020 12:58:36 GMT',
unix: 1608382716
}
}
},
{
txo: 'ff28ad4063faa250797fa39c823b919d157ecae82b8bf68199eb0b9babbb0c12:2',
details: {
originalTxid:
'eced12b6e07e4d830edf6d3244055ba22e953065070985755c9e7f9ea7626d1d',
current:
'512073e40e033106c45af47acfd05092b0206ac276bda459f76dfa019a0e257c',
time: {
utc: 'Sat, 19 Dec 2020 12:58:38 GMT',
unix: 1608382718
}
}
},
{
txo:
'9f7de7ea735d46fbb9214353fec0b250f48a24340e0909eecd83f53817b6aaa3:316',
details: {
originalTxid:
'53cffad1ffd9de20355f89ee0310cd39876c21e2d2a9d5b09548286de1e78565',
current:
'a988c9f46f97de27eccd4f50e1bba751cdc9c49a6cac31de2f4dbf3763fb4ab3',
time: {
utc: 'Sat, 19 Dec 2020 12:58:45 GMT',
unix: 1608382725
}
}
},
{
txo:
'1a57786c472034b2d3d944a3cad69447c30ed5de46bd708ddead33f946ea2578:10',
details: {
originalTxid:
'eced12b6e07e4d830edf6d3244055ba22e953065070985755c9e7f9ea7626d1d',
current:
'8ef975bd11d82c3dfb6645cc15eeaecebde23423b6506daa5145d3b20c02fab5',
time: {
utc: 'Sat, 19 Dec 2020 13:01:30 GMT',
unix: 1608382890
}
}
},
{
txo:
'9f7de7ea735d46fbb9214353fec0b250f48a24340e0909eecd83f53817b6aaa3:317',
details: {
originalTxid:
'5bf0b00b41a9cc1068d8573f0fb65e875743ce3ea5a3e521fff960da9223d508',
current:
'4f3bf7452f5409964cdb9f68a493b2c1f89dd16fc6eb1cc6d2b1dc753c7bfb59',
time: {
utc: 'Sat, 19 Dec 2020 13:04:46 GMT',
unix: 1608383086
}
}
},
{
txo:
'1a57786c472034b2d3d944a3cad69447c30ed5de46bd708ddead33f946ea2578:11',
details: {
originalTxid:
'f1973c96dabf923f7f1e65630366921da9a806316f3e4f2478cbd754d52f6c70',
current:
'76493aaa8bdcb21bb45452250f9518674ea94b3e70fb890252278c9f8bfe3cef',
time: {
utc: 'Sat, 19 Dec 2020 13:06:31 GMT',
unix: 1608383191
}
}
},
{
txo:
'1a57786c472034b2d3d944a3cad69447c30ed5de46bd708ddead33f946ea2578:12',
details: {
originalTxid:
'15b22f6c97a3390c13ae79a61eb9beb34ab87d2245bf8e63d91d7b2477ccdf00',
current:
'6e529eeecd268b1763467152ff76a427c80a26750229de3ad0edbfb2b40ee43a',
time: {
utc: 'Sat, 19 Dec 2020 13:07:37 GMT',
unix: 1608383257
}
}
},
{
txo: 'c75126b129584314d7657ce7140f4aef64ff410053a3d9017a13307b778d4e16:2',
details: {
originalTxid:
'cf2234e2a71a5ae7cf525c2ced332c16bde86734d841649913b4ce698a43506d',
current:
'f98a430d9772962353cb15a58d447d3de056abde41730b92d9301d7510d1d04c',
time: {
utc: 'Sat, 19 Dec 2020 14:52:28 GMT',
unix: 1608389548
}
}
},
{
txo:
'9f7de7ea735d46fbb9214353fec0b250f48a24340e0909eecd83f53817b6aaa3:343',
details: {
originalTxid:
'cf2234e2a71a5ae7cf525c2ced332c16bde86734d841649913b4ce698a43506d',
current:
'f98a430d9772962353cb15a58d447d3de056abde41730b92d9301d7510d1d04c',
time: {
utc: 'Sat, 19 Dec 2020 14:52:28 GMT',
unix: 1608389548
}
}
},
{
txo:
'22fa91e7961388ec071254c65cccf824d7923a0291af0967394fc7bffb6185c1:25',
details: {
originalTxid:
'5ed601fb5b2c6fee6eb1298671471108cf656f5d19186db484c2a6711f0867c8',
current:
'a30535841e874fe7ddefe6285bc44e5f932d1866c343b3298448985c428049a1',
time: {
utc: 'Sat, 19 Dec 2020 18:41:13 GMT',
unix: 1608403273
}
}
},
{
txo:
'22fa91e7961388ec071254c65cccf824d7923a0291af0967394fc7bffb6185c1:26',
details: {
originalTxid:
'a7015340be3ff27b1773208bf7c4501811e9adfb11363c11a7ac080946fb1cd2',
current:
'b9959428becca1c0850409a960153a55fa8cacac3ad5c7b0ba67c57e46814baf',
time: {
utc: 'Sat, 19 Dec 2020 18:42:13 GMT',
unix: 1608403333
}
}
},
{
txo:
'22fa91e7961388ec071254c65cccf824d7923a0291af0967394fc7bffb6185c1:27',
details: {
originalTxid:
'72435b1d8234d5ca00c6653beec318c3d40f4e902f0fec892f769125bdfc0987',
current:
'43277c2fffa2a7ff632fd2bdd0a2be950523816c42012fa00efa860ade428127',
time: {
utc: 'Sat, 19 Dec 2020 18:53:26 GMT',
unix: 1608404006
}
}
},
{
txo: 'b46d46d2d8b081cf4dd3fbf4bb1d36c8f22954e92f999a07c730be52006303ee:2',
details: {
originalTxid:
'93aca660517a8ea847ca5bb39dfa1b26ae72f2eabfedc7cb3ef6e804c293c697',
current:
'377c89f649de123c52ef23a1f0ec844aa6ac032c4ec1242b59ca15e1b64a883d',
time: {
utc: 'Sat, 19 Dec 2020 20:33:14 GMT',
unix: 1608409994
}
}
},
{
txo:
'9f7de7ea735d46fbb9214353fec0b250f48a24340e0909eecd83f53817b6aaa3:409',
details: {
originalTxid:
'4f355dfeed4a71ec0fd10688ddb3c03ea10db7629e844adfb033a859f12dc87f',
current:
'377c89f649de123c52ef23a1f0ec844aa6ac032c4ec1242b59ca15e1b64a883d',
time: {
utc: 'Sat, 19 Dec 2020 20:33:14 GMT',
unix: 1608409994
}
}
},
{
txo: '4bd177144edbf495f614b839c5828ec6c2d21b4a5a478b6324d681b7ecdce976:2',
details: {
originalTxid:
'4f355dfeed4a71ec0fd10688ddb3c03ea10db7629e844adfb033a859f12dc87f',
current:
'9c480d23e99fc3a31558ffb8a402bc819bf0906ca21351749db4d5fceca263b6',
time: {
utc: 'Sat, 19 Dec 2020 20:33:16 GMT',
unix: 1608409996
}
}
},
{
txo:
'190c84d8cb06fe750fcbf0bd11c62e23fa60cd4f7c970a1079ac746e270982be:155',
details: {
originalTxid:
'93aca660517a8ea847ca5bb39dfa1b26ae72f2eabfedc7cb3ef6e804c293c697',
current:
'75e3759d5167395aa1dca327b9b042eb1b86e893bdffc66c2480e8194c2cdc61',
time: {
utc: 'Sat, 19 Dec 2020 20:45:04 GMT',
unix: 1608410704
}
}
}
],
reorgs: [],
mongoDbStats: {
db: 'slpdb',
collections: 5,
views: 0,
objects: 2495075,
avgObjSize: 3188.4621913168944,
dataSize: 7586.910535812378,
storageSize: 3353.39453125,
indexes: 71,
indexSize: 1490.08203125,
totalSize: 4843.4765625,
scaleFactor: 1048576,
fsUsedSize: 74352.5859375,
fsTotalSize: 153676.98046875,
ok: 1
},
publicUrl: 'fullstack--bchn-02',
telemetryHash: null,
system: {
loadAvg1: 0.04,
loadAvg5: 0.03,
loadAvg15: 0,
platform: 'linux',
cpuCount: 8,
freeMem: 5832.546875,
totalMem: 31360.8828125,
uptime: 1603692,
processUptime: 1323812.053445836
}
}
module.exports = { module.exports = {
mockList, mockList,
mockSingleToken, mockSingleToken,
@@ -1034,9 +541,5 @@ module.exports = {
mockSingleValidTxid, mockSingleValidTxid,
mockTwoValidTxid, mockTwoValidTxid,
mockTwoRedundentTxid, mockTwoRedundentTxid,
mockTxHistory, mockTxHistory
mockPsfToken,
mockValidateBulk,
mockValidate3Bulk,
mockStatus
} }
@@ -13,70 +13,75 @@ const mockAddress = {
isscript: false isscript: false
} }
// const mockBalance = {
// page: 1,
// totalPages: 1,
// itemsOnPage: 1000,
// address: 'bitcoincash:qzp7gdl52edm24xlpkyqnza9rv43u3mdxyc77j3u6k',
// balance: '2546',
// totalReceived: '2546',
// totalSent: '0',
// unconfirmedBalance: '0',
// unconfirmedTxs: 0,
// txs: 2,
// txids: [
// 'e190d13b88578132608ab912a4d2be3e55aa2792d6042d481ae21d700639de56',
// '44e1f48c4093fc61db1a8fa206aa402fc34e482b3f788cb38c123ca0e1a35db6'
// ]
// }
const mockBalance = { const mockBalance = {
confirmed: 2546, page: 1,
unconfirmed: 0 totalPages: 1,
itemsOnPage: 1000,
address: 'bitcoincash:qzp7gdl52edm24xlpkyqnza9rv33u3mdxyc77j3u6k',
balance: '2546',
totalReceived: '2546',
totalSent: '0',
unconfirmedBalance: '0',
unconfirmedTxs: 0,
txs: 2,
txids: [
'e190d13b88578132608ab912a4d2be3e55aa2792d6042d481ae21d700639de56',
'44e1f48c4093fc61db1a8fa206aa402fc34e482b3f788cb38c123ca0e1a35db6'
]
} }
const mockUtxos = [ const mockUtxos = [
{ {
tx_hash: 'e190d13b88578132608ab912a4d2be3e55aa2792d6042d481ae21d700639de56', txid: 'e190d13b88578132608ab912a4d2be3e55aa2792d6042d481ae21d700639de56',
tx_pos: 0, vout: 0,
value: 2000, value: '2000',
height: 605873 height: 605873,
confirmations: 298,
satoshis: 2000
}, },
{ {
tx_hash: '44e1f48c4093fc61db1a8fa206aa402fc34e482b3f788cb38c123ca0e1a35db6', txid: '44e1f48c4093fc61db1a8fa206aa402fc34e482b3f788cb38c123ca0e1a35db6',
tx_pos: 1, vout: 1,
value: 546, value: '546',
height: 605873 height: 605873,
confirmations: 298,
satoshis: 546
} }
] ]
const mockThreeUtxos = [ const mockThreeUtxos = [
{ {
tx_hash: 'e190d13b88578132608ab912a4d2be3e55aa2792d6042d481ae21d700639de56', txid: 'e190d13b88578132608ab912a4d2be3e55aa2792d6042d481ae21d700639de56',
tx_pos: 0, vout: 0,
value: 2000, value: '2000',
height: 605873 height: 605873,
confirmations: 298,
satoshis: 2000
}, },
{ {
tx_hash: '44e1f48c4093fc61db1a8fa206aa402fc34e482b3f788cb38c123ca0e1a35db6', txid: '44e1f48c4093fc61db1a8fa206aa402fc34e482b3f788cb38c123ca0e1a35db6',
tx_pos: 1, vout: 1,
value: 546, value: '546',
height: 605873 height: 605873,
confirmations: 298,
satoshis: 546
}, },
{ {
tx_hash: '44e1f48c4093fc61db1a8fa206aa402fc34e482b3f788cb38c123ca0e1a35db6', txid: '44e1f48c4093fc61db1a8fa206aa402fc34e482b3f788cb38c123ca0e1a35db6',
tx_pos: 1, vout: 1,
value: 546, value: '546',
height: 605873 height: 605873,
confirmations: 298,
satoshis: 546
} }
] ]
const mockIsTokenUtxos = [ const mockIsTokenUtxos = [
false, false,
{ {
tx_hash: '44e1f48c4093fc61db1a8fa206aa402fc34e482b3f788cb38c123ca0e1a35db6', txid: '44e1f48c4093fc61db1a8fa206aa402fc34e482b3f788cb38c123ca0e1a35db6',
tx_out: 1, vout: 1,
value: 546, value: '546',
height: 605873, height: 605873,
confirmations: 298, confirmations: 298,
satoshis: 546, satoshis: 546,
+1 -133
View File
@@ -13,7 +13,7 @@ const chai = require('chai')
const assert = chai.assert const assert = chai.assert
const sinon = require('sinon') const sinon = require('sinon')
const Price = require('../../src/routes/v4/price') const Price = require('../../src/routes/v3/price')
let uut let uut
// Mocking data. // Mocking data.
@@ -211,136 +211,4 @@ describe('#PriceRouter', () => {
assert.property(result, 'error') assert.property(result, 'error')
}) })
}) })
describe('#getBCHAUSD', () => {
// const getNetworkInfo = controlRoute.testableComponents.getNetworkInfo
it('should throw 500 when network issues', async () => {
uut.coinexPriceUrl = 'http://fakeurl/api/'
await uut.getBCHAUSD(req, res)
// console.log(`result: ${util.inspect(result)}`)
assert.isAbove(
res.statusCode,
499,
'HTTP status code 500 or greater expected.'
)
// console.log(res)
assert.include(
res.output.error,
'Network error: Could not communicate with full node or other external service'
)
})
it('returns proper error when downstream service stalls', async () => {
// Mock the timeout error.
sandbox.stub(uut.axios, 'request').throws({ code: 'ECONNABORTED' })
const result = await uut.getBCHAUSD(req, res)
// console.log(`result: ${JSON.stringify(result, null, 2)}`)
assert.isAbove(res.statusCode, 499, 'HTTP status code 503 expected.')
assert.include(
result.error,
'Could not communicate with full node',
'Error message expected'
)
})
it('returns proper error when downstream service is down', async () => {
// Mock the timeout error.
sandbox.stub(uut.axios, 'request').throws({ code: 'ECONNREFUSED' })
const result = await uut.getBCHAUSD(req, res)
// console.log(`result: ${JSON.stringify(result, null, 2)}`)
assert.isAbove(res.statusCode, 499, 'HTTP status code 503 expected.')
assert.include(
result.error,
'Could not communicate with full node',
'Error message expected'
)
})
it('should get the USD price of BCH', async () => {
// Mock the RPC call for unit tests.
if (process.env.TEST === 'unit') {
sandbox
.stub(uut.axios, 'request')
.resolves({ data: mockData.mockCoinexFeed })
}
const result = await uut.getBCHAUSD(req, res)
// console.log(`result: ${util.inspect(result)}`)
assert.isNumber(result.usd)
})
})
describe('#getBCHUSD', () => {
// const getNetworkInfo = controlRoute.testableComponents.getNetworkInfo
it('should throw 500 when network issues', async () => {
uut.bchCoinexPriceUrl = 'http://fakeurl/api/'
await uut.getBCHUSD(req, res)
// console.log(`result: ${util.inspect(result)}`)
assert.isAbove(
res.statusCode,
499,
'HTTP status code 500 or greater expected.'
)
// console.log(res)
assert.include(
res.output.error,
'Network error: Could not communicate with full node or other external service'
)
})
it('returns proper error when downstream service stalls', async () => {
// Mock the timeout error.
sandbox.stub(uut.axios, 'request').throws({ code: 'ECONNABORTED' })
const result = await uut.getBCHUSD(req, res)
// console.log(`result: ${JSON.stringify(result, null, 2)}`)
assert.isAbove(res.statusCode, 499, 'HTTP status code 503 expected.')
assert.include(
result.error,
'Could not communicate with full node',
'Error message expected'
)
})
it('returns proper error when downstream service is down', async () => {
// Mock the timeout error.
sandbox.stub(uut.axios, 'request').throws({ code: 'ECONNREFUSED' })
const result = await uut.getBCHUSD(req, res)
// console.log(`result: ${JSON.stringify(result, null, 2)}`)
assert.isAbove(res.statusCode, 499, 'HTTP status code 503 expected.')
assert.include(
result.error,
'Could not communicate with full node',
'Error message expected'
)
})
it('should get the USD price of BCH', async () => {
// Mock the RPC call for unit tests.
if (process.env.TEST === 'unit') {
sandbox
.stub(uut.axios, 'request')
.resolves({ data: mockData.mockCoinexFeed })
}
const result = await uut.getBCHUSD(req, res)
// console.log(`result: ${util.inspect(result)}`)
assert.isNumber(result.usd)
})
})
}) })
@@ -2,6 +2,7 @@
const chai = require('chai') const chai = require('chai')
const assert = chai.assert const assert = chai.assert
// const nock = require('nock') // HTTP mocking
const sinon = require('sinon') const sinon = require('sinon')
// Used for debugging. // Used for debugging.
@@ -15,7 +16,7 @@ const { mockReq, mockRes, mockNext } = require('./mocks/express-mocks')
const RateLimits = require('../../src/middleware/route-ratelimit') const RateLimits = require('../../src/middleware/route-ratelimit')
let rateLimits = new RateLimits() let rateLimits = new RateLimits()
// const controlRoute = require('../../src/routes/v4/full-node/control') // const controlRoute = require('../../src/routes/v3/full-node/control')
const jwtAuth = require('../../src/middleware/jwt-auth') const jwtAuth = require('../../src/middleware/jwt-auth')
let req, res, next let req, res, next
@@ -104,14 +105,14 @@ describe('#route-ratelimits & jwt-auth', () => {
}) })
describe('#calcPoints', () => { describe('#calcPoints', () => {
it('should return 50 points for anonymous user', () => { it('should return 300 points for anonymous user', () => {
const result = rateLimits.calcPoints() const result = rateLimits.calcPoints()
// console.log(`result: ${result}`) // console.log(`result: ${result}`)
assert.equal(result, 50) assert.equal(result, 300)
}) })
it('should return 50 points for free tier requesting full node access', () => { it('should return 100 points for free tier requesting full node access', () => {
const jwtInfo = { const jwtInfo = {
apiLevel: 10, apiLevel: 10,
resource: 'blockchain', resource: 'blockchain',
@@ -119,10 +120,10 @@ describe('#route-ratelimits & jwt-auth', () => {
} }
const result = rateLimits.calcPoints(jwtInfo) const result = rateLimits.calcPoints(jwtInfo)
assert.equal(result, 50) assert.equal(result, 100)
}) })
it('should return 50 points for free tier requesting indexer access', () => { it('should return 100 points for free tier requesting indexer access', () => {
const jwtInfo = { const jwtInfo = {
apiLevel: 10, apiLevel: 10,
resource: 'blockbook', resource: 'blockbook',
@@ -130,10 +131,10 @@ describe('#route-ratelimits & jwt-auth', () => {
} }
const result = rateLimits.calcPoints(jwtInfo) const result = rateLimits.calcPoints(jwtInfo)
assert.equal(result, 50) assert.equal(result, 100)
}) })
it('should return 50 points for free tier requesting SLPDB access', () => { it('should return 100 points for free tier requesting SLPDB access', () => {
const jwtInfo = { const jwtInfo = {
apiLevel: 10, apiLevel: 10,
resource: 'slp', resource: 'slp',
@@ -141,7 +142,7 @@ describe('#route-ratelimits & jwt-auth', () => {
} }
const result = rateLimits.calcPoints(jwtInfo) const result = rateLimits.calcPoints(jwtInfo)
assert.equal(result, 50) assert.equal(result, 100)
}) })
it('should return 10 points for full node tier requesting full node access', () => { it('should return 10 points for full node tier requesting full node access', () => {
@@ -155,7 +156,7 @@ describe('#route-ratelimits & jwt-auth', () => {
assert.equal(result, 10) assert.equal(result, 10)
}) })
it('should return 50 points for full-node tier requesting indexer access', () => { it('should return 100 points for full-node tier requesting indexer access', () => {
const jwtInfo = { const jwtInfo = {
apiLevel: 20, apiLevel: 20,
resource: 'blockbook', resource: 'blockbook',
@@ -163,10 +164,10 @@ describe('#route-ratelimits & jwt-auth', () => {
} }
const result = rateLimits.calcPoints(jwtInfo) const result = rateLimits.calcPoints(jwtInfo)
assert.equal(result, 50) assert.equal(result, 100)
}) })
it('should return 50 points for full node tier requesting SLPDB access', () => { it('should return 100 points for full node tier requesting SLPDB access', () => {
const jwtInfo = { const jwtInfo = {
apiLevel: 20, apiLevel: 20,
resource: 'slp', resource: 'slp',
@@ -174,7 +175,7 @@ describe('#route-ratelimits & jwt-auth', () => {
} }
const result = rateLimits.calcPoints(jwtInfo) const result = rateLimits.calcPoints(jwtInfo)
assert.equal(result, 50) assert.equal(result, 100)
}) })
it('should return 10 point for indexer tier requesting full node access', () => { it('should return 10 point for indexer tier requesting full node access', () => {
@@ -199,7 +200,7 @@ describe('#route-ratelimits & jwt-auth', () => {
assert.equal(result, 10) assert.equal(result, 10)
}) })
it('should return 50 points for indexer tier requesting SLPDB access', () => { it('should return 100 points for indexer tier requesting SLPDB access', () => {
const jwtInfo = { const jwtInfo = {
apiLevel: 30, apiLevel: 30,
resource: 'slp', resource: 'slp',
@@ -207,7 +208,7 @@ describe('#route-ratelimits & jwt-auth', () => {
} }
const result = rateLimits.calcPoints(jwtInfo) const result = rateLimits.calcPoints(jwtInfo)
assert.equal(result, 50) assert.equal(result, 100)
}) })
it('should return 10 point for SLP tier requesting full node access', () => { it('should return 10 point for SLP tier requesting full node access', () => {
@@ -248,7 +249,7 @@ describe('#route-ratelimits & jwt-auth', () => {
// NOTE: this test will fail if you run multiple integration tests in a // NOTE: this test will fail if you run multiple integration tests in a
// short period. Because it talks to the Redis DB. // short period. Because it talks to the Redis DB.
it('should pass through rate-limit middleware', async () => { it('should pass through rate-limit middleware', async () => {
req.baseUrl = '/v4' req.baseUrl = '/v3'
req.path = '/control/getNetworkInfo' req.path = '/control/getNetworkInfo'
req.url = req.path req.url = req.path
req.method = 'GET' req.method = 'GET'
@@ -262,7 +263,7 @@ describe('#route-ratelimits & jwt-auth', () => {
}) })
it('should trigger rate-limit handler if rate limits exceeds 5 request per minute', async () => { it('should trigger rate-limit handler if rate limits exceeds 5 request per minute', async () => {
req.baseUrl = '/v4' req.baseUrl = '/v3'
req.path = '/control/getNetworkInfo' req.path = '/control/getNetworkInfo'
req.url = req.path req.url = req.path
req.method = 'GET' req.method = 'GET'
@@ -286,7 +287,7 @@ describe('#route-ratelimits & jwt-auth', () => {
// Create a new instance of the rate limit so we start with zeroed tracking. // Create a new instance of the rate limit so we start with zeroed tracking.
rateLimits = new RateLimits() rateLimits = new RateLimits()
req.baseUrl = '/v4' req.baseUrl = '/v3'
req.path = '/control/getNetworkInfo' req.path = '/control/getNetworkInfo'
req.url = req.path req.url = req.path
req.method = 'GET' req.method = 'GET'
@@ -318,11 +319,11 @@ describe('#route-ratelimits & jwt-auth', () => {
) )
}) })
it('should trigger rate-limit for free tier after 20 RPM', async () => { it('should trigger rate-limit for free tier after 10 RPM', async () => {
// Create a new instance of the rate limit so we start with zeroed tracking. // Create a new instance of the rate limit so we start with zeroed tracking.
rateLimits = new RateLimits() rateLimits = new RateLimits()
req.baseUrl = '/v4' req.baseUrl = '/v3'
req.path = '/control/getNetworkInfo' req.path = '/control/getNetworkInfo'
req.url = req.path req.url = req.path
req.method = 'GET' req.method = 'GET'
@@ -337,7 +338,7 @@ describe('#route-ratelimits & jwt-auth', () => {
// Mock the call to the jwt library. // Mock the call to the jwt library.
sandbox.stub(rateLimits.jwt, 'verify').returns(jwtInfo) sandbox.stub(rateLimits.jwt, 'verify').returns(jwtInfo)
for (let i = 0; i < 22; i++) { for (let i = 0; i < 12; i++) {
next.reset() // reset the stubbed next() function. next.reset() // reset the stubbed next() function.
await rateLimits.rateLimitByResource(req, res, next) await rateLimits.rateLimitByResource(req, res, next)
@@ -356,7 +357,7 @@ describe('#route-ratelimits & jwt-auth', () => {
// Create a new instance of the rate limit so we start with zeroed tracking. // Create a new instance of the rate limit so we start with zeroed tracking.
rateLimits = new RateLimits() rateLimits = new RateLimits()
req.baseUrl = '/v4' req.baseUrl = '/v3'
req.path = '/control/getNetworkInfo' req.path = '/control/getNetworkInfo'
req.url = req.path req.url = req.path
req.method = 'GET' req.method = 'GET'
@@ -392,7 +393,7 @@ describe('#route-ratelimits & jwt-auth', () => {
// Create a new instance of the rate limit so we start with zeroed tracking. // Create a new instance of the rate limit so we start with zeroed tracking.
rateLimits = new RateLimits() rateLimits = new RateLimits()
req.baseUrl = '/v4' req.baseUrl = '/v3'
req.path = '/control/getNetworkInfo' req.path = '/control/getNetworkInfo'
req.url = req.path req.url = req.path
req.method = 'GET' req.method = 'GET'
@@ -424,52 +425,24 @@ describe('#route-ratelimits & jwt-auth', () => {
) )
}) })
// CT 2/24/21 This test may have been invalidated by the interal IP address it('should handle misconfigured token secret', async () => {
// passing that I implemented to get hydrateUtxos() working properly. // Create a new instance of the rate limit so we start with zeroed tracking.
// I'm commenting this out until I can study the side effects of this change, rateLimits = new RateLimits()
// and why exactly this test is breaking.
// it('should handle misconfigured token secret', async () => {
// // Create a new instance of the rate limit so we start with zeroed tracking.
// rateLimits = new RateLimits()
//
// req.baseUrl = '/v4'
// req.path = '/control/getNetworkInfo'
// req.url = req.path
// req.method = 'GET'
//
// req.locals.jwtToken = 'some-token'
//
// next.reset() // reset the stubbed next() function.
//
// await rateLimits.rateLimitByResource(req, res, next)
//
// // Issues with token secret should treat incoming requests as anonymous
// // calls with 50 points, or 20 RPM.
// assert.equal(res.locals.pointsToConsume, 50)
// })
})
describe('#isInWhitelist', () => { req.baseUrl = '/v3'
it('should return false when no argument is passed in', () => { req.path = '/control/getNetworkInfo'
const result = rateLimits.isInWhitelist() req.url = req.path
req.method = 'GET'
assert.equal(result, false) req.locals.jwtToken = 'some-token'
})
it('should return false when origin is not in the whitelist', () => { next.reset() // reset the stubbed next() function.
const origin = 'blah.com'
const result = rateLimits.isInWhitelist(origin) await rateLimits.rateLimitByResource(req, res, next)
assert.equal(result, false) // Issues with token secret should treat incoming requests as anonymous
}) // calls with 30 points or 3 RPM.
assert.equal(res.locals.pointsToConsume, 300)
it('should return true when origin is in the whitelist', () => {
const origin = 'message.fullstack.cash'
const result = rateLimits.isInWhitelist(origin)
assert.equal(result, true)
}) })
}) })
}) })
@@ -14,7 +14,7 @@
const chai = require('chai') const chai = require('chai')
const assert = chai.assert const assert = chai.assert
const Rawtransactions = require('../../src/routes/v4/full-node/rawtransactions') const Rawtransactions = require('../../src/routes/v3/full-node/rawtransactions')
const uut = new Rawtransactions() const uut = new Rawtransactions()
// const nock = require('nock') // HTTP mocking // const nock = require('nock') // HTTP mocking
@@ -975,7 +975,6 @@ describe('#Raw-Transactions', () => {
// Restore the saved URL. // Restore the saved URL.
process.env.RPC_BASEURL = savedUrl2 process.env.RPC_BASEURL = savedUrl2
}) })
it('should submit hex encoded transaction', async () => { it('should submit hex encoded transaction', async () => {
// This is a difficult test to run as transaction hex is invalid after a // 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' // block confirmation. So the unit tests simulates what the output 'should'
@@ -1004,13 +1003,8 @@ describe('#Raw-Transactions', () => {
// Integration test // Integration test
} else { } else {
if (process.env.ISBCHN) { assert.hasAllKeys(result, ['error'])
assert.hasAllKeys(result, ['error']) assert.include(result.error, 'Missing inputs')
assert.include(result.error, 'Missing inputs')
} else {
assert.hasAllKeys(result, ['error'])
assert.include(result.error, 'bad-txns-inputs-missingorspent')
}
} }
}) })
}) })
@@ -1164,13 +1158,8 @@ describe('#Raw-Transactions', () => {
// Integration test // Integration test
} else { } else {
if (process.env.ISBCHN) { assert.hasAllKeys(result, ['error'])
assert.hasAllKeys(result, ['error']) assert.include(result.error, 'Missing inputs')
assert.include(result.error, 'Missing inputs')
} else {
assert.hasAllKeys(result, ['error'])
assert.include(result.error, 'bad-txns-inputs-missingorspent')
}
} }
}) })
}) })
+52 -674
View File
@@ -40,11 +40,11 @@ if (process.env.TEST === 'unit') {
} }
// Prepare the slpRoute for stubbing dependcies on slpjs. // Prepare the slpRoute for stubbing dependcies on slpjs.
const SlpRoute = require('../../src/routes/v4/slp') const SlpRoute = require('../../src/routes/v3/slp')
const slpRoute = new SlpRoute() const slpRoute = new SlpRoute()
// const pathStub = {} // Used to stub methods within slpjs. // const pathStub = {} // Used to stub methods within slpjs.
// const slpRouteStub = proxyquire('../../src/routes/v4/slp', { slpjs: pathStub }) // const slpRouteStub = proxyquire('../../src/routes/v3/slp', { slpjs: pathStub })
// Mocking data. // Mocking data.
const { mockReq, mockRes } = require('./mocks/express-mocks') const { mockReq, mockRes } = require('./mocks/express-mocks')
@@ -192,7 +192,6 @@ describe('#SLP', () => {
'Error message expected' 'Error message expected'
) )
}) })
it('should get token balance for an address', async () => { it('should get token balance for an address', async () => {
// Mock the RPC call for unit tests. // Mock the RPC call for unit tests.
if (process.env.TEST === 'unit') { if (process.env.TEST === 'unit') {
@@ -339,231 +338,7 @@ describe('#SLP', () => {
} }
}) })
describe('#validate2Single', () => { describe('validateBulk()', () => {
it('should throw 400 if txid is empty', async () => {
req.params.txid = ''
const result = await slpRoute.validate2Single(req, res)
// console.log(`result: ${util.inspect(result)}`)
assert.hasAllKeys(result, ['error'])
assert.include(result.error, 'txid can not be empty')
})
it('should invalidate a known invalid TXID', async () => {
if (process.env.TEST === 'unit') {
// Mock to prevent live network connection.
sandbox
.stub(slpRoute.axios, 'request')
.resolves({ data: { isValid: false } })
}
const txid =
'f7e5199ef6669ad4d078093b3ad56e355b6ab84567e59ad0f08a5ad0244f783a'
req.params.txid = txid
const result = await slpRoute.validate2Single(req, res)
// console.log(`result: ${JSON.stringify(result, null, 2)}`)
assert.equal(result.txid, txid)
assert.equal(result.isValid, false)
})
it('should validate a known valid TXID', async () => {
if (process.env.TEST === 'unit') {
// Mock to prevent live network connection.
sandbox
.stub(slpRoute.axios, 'request')
.resolves({ data: { isValid: true } })
}
const txid =
'3a4b628cbcc183ab376d44ce5252325f042268307ffa4a53443e92b6d24fb488'
req.params.txid = txid
const result = await slpRoute.validate2Single(req, res)
// console.log(`result: ${JSON.stringify(result, null, 2)}`)
assert.equal(result.txid, txid)
assert.equal(result.isValid, true)
})
// This test can only be run as a mocked unit test. It's too inconsistent
// to run as an integration test, due to the caching built into slp-validate.
if (process.env.TEST === 'unit') {
it('should cancel if validation takes too long', async () => {
// Mock the timeout error.
sandbox.stub(slpRoute.axios, 'request').throws({
code: 'ECONNABORTED'
})
const txid =
'eacb1085dfa296fef6d4ae2c0f4529a1bef096dd2325bdcc6dcb5241b3bdb579'
req.params.txid = txid
const result = await slpRoute.validate2Single(req, res)
// console.log(`result: ${JSON.stringify(result, null, 2)}`)
assert.isAbove(res.statusCode, 499, 'HTTP status code 503 expected.')
assert.include(
result.error,
'Could not communicate with full node',
'Error message expected'
)
})
}
})
describe('#validateSingle', () => {
it('should throw 400 if txid is empty', async () => {
req.params.txid = ''
const result = await slpRoute.validateSingle(req, res)
// console.log(`result: ${util.inspect(result)}`)
assert.hasAllKeys(result, ['error'])
assert.include(result.error, 'txid can not be empty')
})
it('should invalidate a known invalid TXID', async () => {
if (process.env.TEST === 'unit') {
// Mock to prevent live network connection.
sandbox.stub(slpRoute.axios, 'request').resolves({
data: {
c: [],
u: []
}
})
}
const txid =
'f7e5199ef6669ad4d078093b3ad56e355b6ab84567e59ad0f08a5ad0244f783a'
req.params.txid = txid
const result = await slpRoute.validateSingle(req, res)
// console.log(`result: ${JSON.stringify(result, null, 2)}`)
assert.equal(result.txid, txid)
assert.equal(result.valid, null)
})
it('should validate a known valid TXID', async () => {
if (process.env.TEST === 'unit') {
// Mock to prevent live network connection.
sandbox
.stub(slpRoute.axios, 'request')
.resolves({ data: mockData.mockSingleValidTxid })
}
const txid =
'77872738b6bddee6c0cbdb9509603de20b15d4f6b26602f629417aec2f5d5e8d'
req.params.txid = txid
const result = await slpRoute.validateSingle(req, res)
// console.log(`result: ${JSON.stringify(result, null, 2)}`)
assert.equal(result.txid, txid)
assert.equal(result.valid, true)
})
if (process.env.TEST === 'unit') {
it('should cancel if validation takes too long', async () => {
// Mock the timeout error.
sandbox.stub(slpRoute.axios, 'request').throws({
code: 'ECONNABORTED'
})
const txid =
'eacb1085dfa296fef6d4ae2c0f4529a1bef096dd2325bdcc6dcb5241b3bdb579'
req.params.txid = txid
const result = await slpRoute.validateSingle(req, res)
// console.log(`result: ${JSON.stringify(result, null, 2)}`)
assert.isAbove(res.statusCode, 499, 'HTTP status code 503 expected.')
assert.include(
result.error,
'Could not communicate with full node',
'Error message expected'
)
})
}
})
describe('#validate3Single', () => {
it('should throw 400 if txid is empty', async () => {
req.params.txid = ''
const result = await slpRoute.validate3Single(req, res)
// console.log(`result: ${util.inspect(result)}`)
assert.hasAllKeys(result, ['error'])
assert.include(result.error, 'txid can not be empty')
})
it('should invalidate a known invalid TXID', async () => {
if (process.env.TEST === 'unit') {
// Mock to prevent live network connection.
sandbox.stub(slpRoute.axios, 'request').resolves({
data: {
c: [],
u: []
}
})
}
const txid =
'f7e5199ef6669ad4d078093b3ad56e355b6ab84567e59ad0f08a5ad0244f783a'
req.params.txid = txid
const result = await slpRoute.validate3Single(req, res)
// console.log(`result: ${JSON.stringify(result, null, 2)}`)
assert.equal(result.txid, txid)
assert.equal(result.valid, null)
})
it('should validate a known valid TXID', async () => {
if (process.env.TEST === 'unit') {
// Mock to prevent live network connection.
sandbox
.stub(slpRoute.axios, 'request')
.resolves({ data: mockData.mockSingleValidTxid })
}
const txid =
'cdfed769ef7b69e09be06d2821b88598d9a5d711b8f9bd369763c78b7a578fbc'
req.params.txid = txid
const result = await slpRoute.validate3Single(req, res)
// console.log(`result: ${JSON.stringify(result, null, 2)}`)
// assert.equal(result.txid, txid)
assert.equal(result.valid, true)
})
if (process.env.TEST === 'unit') {
it('should cancel if validation takes too long', async () => {
// Mock the timeout error.
sandbox.stub(slpRoute.axios, 'request').throws({
code: 'ECONNABORTED'
})
const txid =
'eacb1085dfa296fef6d4ae2c0f4529a1bef096dd2325bdcc6dcb5241b3bdb579'
req.params.txid = txid
const result = await slpRoute.validate3Single(req, res)
// console.log(`result: ${JSON.stringify(result, null, 2)}`)
assert.isAbove(res.statusCode, 499, 'HTTP status code 503 expected.')
assert.include(
result.error,
'Could not communicate with full node',
'Error message expected'
)
})
}
})
describe('#validateBulk()', () => {
const validateBulk = slpRoute.validateBulk const validateBulk = slpRoute.validateBulk
it('should throw 400 if txid array is empty', async () => { it('should throw 400 if txid array is empty', async () => {
@@ -686,272 +461,77 @@ describe('#SLP', () => {
assert.hasAllKeys(result[0], ['txid', 'valid']) assert.hasAllKeys(result[0], ['txid', 'valid'])
assert.equal(result.length, 2) assert.equal(result.length, 2)
}) })
it('should handle a mix of valid, invalid, and non-SLP txs', async () => {
// Mock the RPC call for unit tests.
if (process.env.TEST === 'unit') {
sandbox.stub(slpRoute.axios, 'request').resolves({
data: mockData.mockValidateBulk
})
}
const txids = [
// Malformed SLP tx
'f7e5199ef6669ad4d078093b3ad56e355b6ab84567e59ad0f08a5ad0244f783a',
// Normal TX (non-SLP)
'01cdaec2f8b311fc2d6ecc930247bd45fa696dc204ab684596e281fe1b06c1f0',
// Valid PSF SLP tx
'daf4d8b8045e7a90b7af81bfe2370178f687da0e545511bce1c9ae539eba5ffd',
// Valid SLP token not in whitelist
'3a4b628cbcc183ab376d44ce5252325f042268307ffa4a53443e92b6d24fb488',
// Token send on BCHN network.
'402c663379d9699b6e2dd38737061e5888c5a49fca77c97ab98e79e08959e019',
// Token send on ABC network.
'336bfe2168aac4c3303508a9e8548a0d33797a83b85b76a12d845c8d6674f79d',
// Known invalid SLP token send of PSF tokens.
'2bf691ad3679d928fef880b8a45b93b233f8fa0d0a92cf792313dbe77b1deb74'
]
req.body.txids = txids
const result = await validateBulk(req, res)
// console.log(`result: ${JSON.stringify(result, null, 2)}`)
// BCHN expected results
if (process.env.ISBCHN) {
assert.equal(result[0].txid, txids[0])
assert.equal(result[0].valid, null)
assert.equal(result[1].txid, txids[1])
assert.equal(result[1].valid, null)
assert.equal(result[2].txid, txids[2])
assert.equal(result[2].valid, true)
assert.equal(result[3].txid, txids[3])
assert.equal(result[3].valid, true)
// Note: This should change from null to true once SLPDB finishes indexing.
assert.equal(result[4].txid, txids[4])
assert.equal(result[4].valid, true)
assert.equal(result[5].txid, txids[5])
assert.equal(result[5].valid, null)
assert.equal(result[6].txid, txids[6])
assert.equal(result[6].valid, false)
assert.include(
result[6].invalidReason,
'Token outputs are greater than valid token inputs'
)
} else {
assert.equal(result[0].txid, txids[0])
assert.equal(result[0].valid, null)
assert.equal(result[1].txid, txids[1])
assert.equal(result[1].valid, null)
assert.equal(result[2].txid, txids[2])
assert.equal(result[2].valid, true)
assert.equal(result[3].txid, txids[3])
assert.equal(result[3].valid, true)
assert.equal(result[4].txid, txids[4])
assert.equal(result[4].valid, null)
assert.equal(result[5].txid, txids[5])
assert.equal(result[5].valid, true)
assert.equal(result[6].txid, txids[6])
assert.equal(result[6].valid, false)
assert.include(
result[6].invalidReason,
'Token outputs are greater than valid token inputs'
)
}
})
}) })
describe('#validate3Bulk', () => { describe('#validate2Single', () => {
const validate3Bulk = slpRoute.validate3Bulk it('should throw 400 if txid is empty', async () => {
req.params.txid = ''
it('should throw 400 if txid array is empty', async () => { const result = await slpRoute.validate2Single(req, res)
const result = await validate3Bulk(req, res)
// console.log(`result: ${util.inspect(result)}`) // console.log(`result: ${util.inspect(result)}`)
assert.hasAllKeys(result, ['error']) assert.hasAllKeys(result, ['error'])
assert.include(result.error, 'txids needs to be an array') assert.include(result.error, 'txid can not be empty')
assert.equal(res.statusCode, 400)
}) })
it('should throw 400 error if array is too large', async () => { it('should invalidate a known invalid TXID', async () => {
const testArray = [] if (process.env.TEST === 'unit') {
for (var i = 0; i < 25; i++) testArray.push('') // Mock to prevent live network connection.
sandbox
.stub(slpRoute.axios, 'request')
.resolves({ data: { isValid: false } })
}
req.body.txids = testArray const txid =
'f7e5199ef6669ad4d078093b3ad56e355b6ab84567e59ad0f08a5ad0244f783a'
const result = await validate3Bulk(req, res) req.params.txid = txid
// console.log(`result: ${util.inspect(result)}`) const result = await slpRoute.validate2Single(req, res)
assert.hasAllKeys(result, ['error'])
assert.include(result.error, 'Array too large')
})
it('returns proper error when downstream service stalls', async () => {
// Mock the timeout error.
sandbox.stub(slpRoute.axios, 'request').throws({ code: 'ECONNABORTED' })
req.body.txids = [
'77872738b6bddee6c0cbdb9509603de20b15d4f6b26602f629417aec2f5d5e8d'
]
const result = await validate3Bulk(req, res)
// console.log(`result: ${JSON.stringify(result, null, 2)}`) // console.log(`result: ${JSON.stringify(result, null, 2)}`)
assert.isAbove(res.statusCode, 499, 'HTTP status code 503 expected.') assert.equal(result.txid, txid)
assert.include( assert.equal(result.isValid, false)
result.error,
'Could not communicate with full node',
'Error message expected'
)
}) })
it('returns proper error when downstream service is down', async () => { it('should validate a known valid TXID', async () => {
// Mock the timeout error. if (process.env.TEST === 'unit') {
sandbox.stub(slpRoute.axios, 'request').throws({ code: 'ECONNREFUSED' }) // Mock to prevent live network connection.
sandbox
.stub(slpRoute.axios, 'request')
.resolves({ data: { isValid: true } })
}
req.body.txids = [ const txid =
'77872738b6bddee6c0cbdb9509603de20b15d4f6b26602f629417aec2f5d5e8d' '3a4b628cbcc183ab376d44ce5252325f042268307ffa4a53443e92b6d24fb488'
]
const result = await validate3Bulk(req, res) req.params.txid = txid
const result = await slpRoute.validate2Single(req, res)
// console.log(`result: ${JSON.stringify(result, null, 2)}`) // console.log(`result: ${JSON.stringify(result, null, 2)}`)
assert.isAbove(res.statusCode, 499, 'HTTP status code 503 expected.') assert.equal(result.txid, txid)
assert.include( assert.equal(result.isValid, true)
result.error,
'Could not communicate with full node',
'Error message expected'
)
})
it('should validate array with single element', async () => {
// Mock the RPC call for unit tests.
if (process.env.TEST === 'unit') {
sandbox.stub(slpRoute.axios, 'request').resolves({
data: mockData.mockPsfToken
})
}
req.body.txids = [
'cdfed769ef7b69e09be06d2821b88598d9a5d711b8f9bd369763c78b7a578fbc'
]
const result = await validate3Bulk(req, res)
// console.log(`result: ${util.inspect(result)}`)
assert.isArray(result)
assert.hasAllKeys(result[0], ['txid', 'valid'])
})
it('should validate array with two elements', async () => {
// Mock the RPC call for unit tests.
if (process.env.TEST === 'unit') {
sandbox.stub(slpRoute.axios, 'request').resolves({
data: mockData.mockPsfToken
})
}
req.body.txids = [
'cdfed769ef7b69e09be06d2821b88598d9a5d711b8f9bd369763c78b7a578fbc',
'cdfed769ef7b69e09be06d2821b88598d9a5d711b8f9bd369763c78b7a578fbc'
]
const result = await validate3Bulk(req, res)
// console.log(`result: ${util.inspect(result)}`)
assert.isArray(result)
assert.hasAllKeys(result[0], ['txid', 'valid'])
assert.equal(result.length, 2)
})
// Captures a regression bug that went out to production, captured in this
// GitHub Issue: https://github.com/Bitcoin-com/rest.bitcoin.com/issues/518
it('should return two elements if given two elements', async () => {
// Mock the RPC call for unit tests.
if (process.env.TEST === 'unit') {
sandbox.stub(slpRoute.axios, 'request').resolves({
data: mockData.mockPsfToken
})
}
req.body.txids = [
'cdfed769ef7b69e09be06d2821b88598d9a5d711b8f9bd369763c78b7a578fbc',
'cdfed769ef7b69e09be06d2821b88598d9a5d711b8f9bd369763c78b7a578fbc'
]
const result = await validate3Bulk(req, res)
// console.log(`result: ${util.inspect(result)}`)
assert.isArray(result)
assert.hasAllKeys(result[0], ['txid', 'valid'])
assert.equal(result.length, 2)
}) })
// This test can only be run as a mocked unit test. It's too inconsistent
// to run as an integration test, due to the caching built into slp-validate.
if (process.env.TEST === 'unit') { if (process.env.TEST === 'unit') {
// This is a unit-test only test, as the results change depending on if it('should cancel if validation takes too long', async () => {
// its tested against the BCHN or ABC networks. There are integration tests // Mock the timeout error.
// for this test case in the ../integration/slp.js file. sandbox.stub(slpRoute.axios, 'request').throws({
it('should handle a mix of valid, invalid, and non-SLP txs', async () => { code: 'ECONNABORTED'
// Mock the RPC call for unit tests.
sandbox.stub(slpRoute.axios, 'request').resolves({
data: mockData.mockValidate3Bulk
}) })
const txids = [ const txid =
// Malformed SLP tx 'eacb1085dfa296fef6d4ae2c0f4529a1bef096dd2325bdcc6dcb5241b3bdb579'
'f7e5199ef6669ad4d078093b3ad56e355b6ab84567e59ad0f08a5ad0244f783a',
// Normal TX (non-SLP)
'01cdaec2f8b311fc2d6ecc930247bd45fa696dc204ab684596e281fe1b06c1f0',
// Valid PSF SLP tx
'daf4d8b8045e7a90b7af81bfe2370178f687da0e545511bce1c9ae539eba5ffd',
// Valid SLP token not in whitelist
'3a4b628cbcc183ab376d44ce5252325f042268307ffa4a53443e92b6d24fb488',
// Token send on BCHN network.
'402c663379d9699b6e2dd38737061e5888c5a49fca77c97ab98e79e08959e019',
// Token send on ABC network.
'336bfe2168aac4c3303508a9e8548a0d33797a83b85b76a12d845c8d6674f79d',
// Known invalid SLP token send of PSF tokens.
'2bf691ad3679d928fef880b8a45b93b233f8fa0d0a92cf792313dbe77b1deb74'
]
req.body.txids = txids req.params.txid = txid
const result = await validate3Bulk(req, res) const result = await slpRoute.validate2Single(req, res)
// console.log(`result: ${JSON.stringify(result, null, 2)}`) // console.log(`result: ${JSON.stringify(result, null, 2)}`)
assert.equal(result[0].txid, txids[0]) assert.isAbove(res.statusCode, 499, 'HTTP status code 503 expected.')
assert.equal(result[0].valid, null)
assert.equal(result[1].txid, txids[1])
assert.equal(result[1].valid, null)
assert.equal(result[2].txid, txids[2])
assert.equal(result[2].valid, true)
assert.equal(result[3].txid, txids[3])
assert.equal(result[3].valid, null)
assert.equal(result[4].txid, txids[4])
assert.equal(result[4].valid, null)
assert.equal(result[5].txid, txids[5])
assert.equal(result[5].valid, null)
assert.equal(result[6].txid, txids[6])
assert.equal(result[6].valid, false)
assert.include( assert.include(
result[6].invalidReason, result.error,
'Token outputs are greater than valid token inputs' 'Could not communicate with full node',
'Error message expected'
) )
}) })
} }
@@ -1287,7 +867,7 @@ describe('#SLP', () => {
}) })
}) })
describe('#generateSendOpReturn()', () => { describe('generateSendOpReturn()', () => {
const generateSendOpReturn = slpRoute.generateSendOpReturn const generateSendOpReturn = slpRoute.generateSendOpReturn
// Validate tokenUtxos input // Validate tokenUtxos input
it('should throw 400 if tokenUtxos is missing', async () => { it('should throw 400 if tokenUtxos is missing', async () => {
@@ -1363,8 +943,8 @@ describe('#SLP', () => {
req.body.tokenUtxos = [ req.body.tokenUtxos = [
{ {
tokenId: tokenId:
'38e97c5d7d3585a2cbf3f9580c82ca33985f9cb0845d4dcce220cb709f9538b0', '0a321bff9761f28e06a268b14711274bb77617410a16807bd0437ef234a072b1',
decimals: 8, decimals: 0,
tokenQty: 2 tokenQty: 2
} }
] ]
@@ -1558,208 +1138,6 @@ describe('#SLP', () => {
) )
}) })
}) })
describe('#hydrateUtxosWL', () => {
it('should throw error if input is not an array.', async () => {
req.body.utxos = 'test'
const result = await slpRoute.hydrateUtxosWL(req, res)
// console.log(`result: `, result)
assert.hasAllKeys(result, ['error'])
assert.include(result.error, 'Input must be an array')
})
it('should throw error if Array is empty', async () => {
req.body.utxos = []
const result = await slpRoute.hydrateUtxosWL(req, res)
assert.hasAllKeys(result, ['error'])
assert.include(result.error, 'Array should not be empty')
})
it('should throw error if Array is too long', async () => {
const utxo = {
txid:
'bd158c564dd4ef54305b14f44f8e94c44b649f246dab14bcb42fb0d0078b8a90',
vout: 3,
amount: 0.00002015,
satoshis: 2015,
height: 594892,
confirmations: 5
}
const utxos = []
// Populate array with 21 utxos
for (let i = 0; i < 21; i++) {
utxos.push(utxo)
}
req.body.utxos = utxos
const result = await slpRoute.hydrateUtxosWL(req, res)
assert.hasAllKeys(result, ['error'])
assert.include(result.error, 'Array too long, max length is 20')
})
it('should return utxo details', async () => {
const utxos = [
{
utxos: [
{
txid:
'd56a2b446d8149c39ca7e06163fe8097168c3604915f631bc58777d669135a56',
vout: 3,
value: '6816',
height: 606848,
confirmations: 13,
satoshis: 6816
},
{
txid:
'd56a2b446d8149c39ca7e06163fe8097168c3604915f631bc58777d669135a56',
vout: 2,
value: '546',
height: 606848,
confirmations: 13,
satoshis: 546
}
]
}
]
// Mock the external network call.
sandbox.stub(slpRoute.bchjs.SLP.Utils, 'tokenUtxoDetailsWL').resolves([
{
txid:
'd56a2b446d8149c39ca7e06163fe8097168c3604915f631bc58777d669135a56',
vout: 3,
value: '6816',
height: 606848,
confirmations: 13,
satoshis: 6816,
isValid: false
},
{
txid:
'd56a2b446d8149c39ca7e06163fe8097168c3604915f631bc58777d669135a56',
vout: 2,
value: '546',
height: 606848,
confirmations: 13,
satoshis: 546,
utxoType: 'token',
transactionType: 'send',
tokenId:
'dd84ca78db4d617221b58eabc6667af8fe2f7eadbfcc213d35be9f1b419beb8d',
tokenTicker: 'TAP',
tokenName: 'Thoughts and Prayers',
tokenDocumentUrl: '',
tokenDocumentHash: '',
decimals: 0,
tokenType: 1,
tokenQty: 5,
isValid: true
}
])
req.body.utxos = utxos
const result = await slpRoute.hydrateUtxosWL(req, res)
// console.log(`result: ${JSON.stringify(result, null, 2)}`)
// Test the general structure of the output.
assert.isArray(result.slpUtxos)
assert.equal(result.slpUtxos.length, 1)
assert.equal(result.slpUtxos[0].utxos.length, 2)
// Test the non-slp UTXO.
assert.property(result.slpUtxos[0].utxos[0], 'txid')
assert.property(result.slpUtxos[0].utxos[0], 'vout')
assert.property(result.slpUtxos[0].utxos[0], 'value')
assert.property(result.slpUtxos[0].utxos[0], 'height')
assert.property(result.slpUtxos[0].utxos[0], 'confirmations')
assert.property(result.slpUtxos[0].utxos[0], 'satoshis')
assert.property(result.slpUtxos[0].utxos[0], 'isValid')
assert.equal(result.slpUtxos[0].utxos[0].isValid, false)
// Test the slp UTXO.
assert.property(result.slpUtxos[0].utxos[1], 'txid')
assert.property(result.slpUtxos[0].utxos[1], 'vout')
assert.property(result.slpUtxos[0].utxos[1], 'value')
assert.property(result.slpUtxos[0].utxos[1], 'height')
assert.property(result.slpUtxos[0].utxos[1], 'confirmations')
assert.property(result.slpUtxos[0].utxos[1], 'satoshis')
assert.property(result.slpUtxos[0].utxos[1], 'isValid')
assert.equal(result.slpUtxos[0].utxos[1].isValid, true)
assert.property(result.slpUtxos[0].utxos[1], 'transactionType')
assert.property(result.slpUtxos[0].utxos[1], 'tokenId')
assert.property(result.slpUtxos[0].utxos[1], 'tokenTicker')
assert.property(result.slpUtxos[0].utxos[1], 'tokenName')
assert.property(result.slpUtxos[0].utxos[1], 'tokenDocumentUrl')
assert.property(result.slpUtxos[0].utxos[1], 'tokenDocumentHash')
assert.property(result.slpUtxos[0].utxos[1], 'decimals')
assert.property(result.slpUtxos[0].utxos[1], 'tokenType')
assert.property(result.slpUtxos[0].utxos[1], 'tokenQty')
})
it('should throw error for missing properties', async () => {
const utxos = [
{
height: 639443,
tx_hash:
'30707fffb9b295a06a68d217f49c198e9e1dbe1edc3874a0928ca1905f1709df',
tx_pos: 0,
value: 6000
},
{
height: 639443,
tx_hash:
'8962566e413501224d178a02effc89be5ac0d8e4195f617415d443dc4c38fe50',
tx_pos: 1,
value: 546
}
]
req.body.utxos = utxos
const result = await slpRoute.hydrateUtxosWL(req, res)
assert.hasAllKeys(result, ['error'])
assert.include(
result.error,
'Each element in array should have a utxos property'
)
})
})
describe('#getStatus', () => {
it('should get the SLPDB status', async () => {
if (process.env.TEST === 'unit') {
// Mock to prevent live network connection.
sandbox
.stub(slpRoute.axios, 'request')
.resolves({ data: { s: [mockData.mockStatus] } })
}
const result = await slpRoute.getStatus(req, res)
// console.log(`result: ${JSON.stringify(result, null, 2)}`)
assert.property(result, 'bchBlockHeight')
})
it('returns proper error when downstream service is down', async () => {
// Mock the timeout error.
sandbox.stub(slpRoute.axios, 'request').throws({ code: 'ECONNREFUSED' })
slpRoute.getStatus(req, res)
// const result = slpRoute.getStatus(req, res)
// console.log(`result: ${JSON.stringify(result, null, 2)}`)
assert.isAbove(res.statusCode, 499, 'HTTP status code 503 expected.')
})
})
}) })
/* /*
+33 -46
View File
@@ -4,24 +4,16 @@
This test file uses the environment variable TEST to switch between unit 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 and integration tests. By default, TEST is set to 'unit'. Set this variable
to 'integration' to run the tests against BCH mainnet. to 'integration' to run the tests against BCH mainnet.
These tests use this private key:
L1wAGEN721LHDoiN8pLwwBb87bYrU6Gs21UPcCRR7LjKypQyVaCq
Which corresponds to this address:
bitcoincash:qp2g4cnekxsjspccmtvh5k73mczz6273js4mjr353r
*/ */
'use strict' 'use strict'
// Public npm libraries const chai = require('chai')
const assert = require('chai').assert const assert = chai.assert
const utilRoute = require('../../src/routes/v3/util')
const nock = require('nock') // HTTP mocking const nock = require('nock') // HTTP mocking
const sinon = require('sinon') const sinon = require('sinon')
// Local libraries
const Electrumx = require('../../src/routes/v4/electrumx')
const UtilRoute = require('../../src/routes/v4/util')
let originalEnvVars // Used during transition from integration to unit tests. let originalEnvVars // Used during transition from integration to unit tests.
// Mocking data. // Mocking data.
@@ -32,16 +24,13 @@ const util = require('util')
util.inspect.defaultOptions = { depth: 1 } util.inspect.defaultOptions = { depth: 1 }
// const UtilRoute = utilRoute.UtilRoute // const UtilRoute = utilRoute.UtilRoute
// const electrumx = new Electrumx() const utilRouteInst = new utilRoute.UtilRoute()
const utilRouteInst = new UtilRoute({ electrumx: {} })
describe('#Util', () => { describe('#Util', () => {
let req, res let req, res
let sandbox let sandbox
let electrumx
let utilRoute
before(async () => { before(() => {
// Save existing environment variables. // Save existing environment variables.
originalEnvVars = { originalEnvVars = {
BITCOINCOM_BASEURL: process.env.BITCOINCOM_BASEURL, BITCOINCOM_BASEURL: process.env.BITCOINCOM_BASEURL,
@@ -58,9 +47,6 @@ describe('#Util', () => {
process.env.RPC_USERNAME = 'fakeusername' process.env.RPC_USERNAME = 'fakeusername'
process.env.RPC_PASSWORD = 'fakepassword' process.env.RPC_PASSWORD = 'fakepassword'
} }
electrumx = new Electrumx()
// await electrumx.connect()
}) })
// Setup the mocks before each test. // Setup the mocks before each test.
@@ -78,8 +64,6 @@ describe('#Util', () => {
if (!nock.isActive()) nock.activate() if (!nock.isActive()) nock.activate()
sandbox = sinon.createSandbox() sandbox = sinon.createSandbox()
utilRoute = new UtilRoute({ electrumx })
}) })
afterEach(() => { afterEach(() => {
@@ -373,29 +357,29 @@ describe('#Util', () => {
// Mock the RPC call for unit tests. // Mock the RPC call for unit tests.
sandbox sandbox
.stub(utilRoute.electrumx, '_balanceFromElectrumx') .stub(utilRouteInst.blockbook, 'balanceFromBlockbook')
.resolves(mockData.mockBalance) .resolves(mockData.mockBalance)
sandbox sandbox
.stub(utilRoute.electrumx, '_utxosFromElectrumx') .stub(utilRouteInst.blockbook, 'utxosFromBlockbook')
.resolves(mockData.mockUtxos) .resolves(mockData.mockUtxos)
sandbox sandbox
.stub(utilRoute.bchjs.SLP.Utils, 'tokenUtxoDetails') .stub(utilRouteInst.bchjs.SLP.Utils, 'tokenUtxoDetails')
.resolves(mockData.mockIsTokenUtxos) .resolves(mockData.mockIsTokenUtxos)
// Mock sendRawTransaction() so that the hex does not actually get broadcast // Mock sendRawTransaction() so that the hex does not actually get broadcast
// to the network. // to the network.
sandbox sandbox
.stub(utilRoute.bchjs.RawTransactions, 'sendRawTransaction') .stub(utilRouteInst.bchjs.RawTransactions, 'sendRawTransaction')
.resolves('test-txid') .resolves('test-txid')
req.body = { req.body = {
wif: 'L1wAGEN721LHDoiN8pLwwBb87bYrU6Gs21UPcCRR7LjKypQyVaCq', wif: 'L5GEFg1tETLWBugmhSo9Zc4ms968qVmfmTroDxsJ982AiudAQGyt',
toAddr: 'bitcoincash:qp2g4cnekxsjspccmtvh5k73mczz6273js4mjr353r' toAddr: 'bitcoincash:qz2qn6zt4qmacf4r6c0e2pdcqsgnkxaa3ql2xpee6p'
} }
const result = await utilRoute.sweepWif(req, res) const result = await utilRouteInst.sweepWif(req, res)
// console.log(`result: ${JSON.stringify(result, null, 2)}`) // console.log(`result: ${JSON.stringify(result, null, 2)}`)
assert.equal(result, 'test-txid') assert.equal(result, 'test-txid')
@@ -405,13 +389,13 @@ describe('#Util', () => {
// Mock the RPC call for unit tests. // Mock the RPC call for unit tests.
sandbox sandbox
.stub(utilRoute.electrumx, '_balanceFromElectrumx') .stub(utilRouteInst.blockbook, 'balanceFromBlockbook')
.resolves(mockData.mockBalance) .resolves(mockData.mockBalance)
// Mock sendRawTransaction() so that the hex does not actually get broadcast // Mock sendRawTransaction() so that the hex does not actually get broadcast
// to the network. // to the network.
sandbox sandbox
.stub(utilRoute.bchjs.RawTransactions, 'sendRawTransaction') .stub(utilRouteInst.bchjs.RawTransactions, 'sendRawTransaction')
.resolves('test-txid') .resolves('test-txid')
req.body = { req.body = {
@@ -419,30 +403,33 @@ describe('#Util', () => {
balanceOnly: true balanceOnly: true
} }
const result = await utilRoute.sweepWif(req, res) const result = await utilRouteInst.sweepWif(req, res)
// console.log(`result: ${JSON.stringify(result, null, 2)}`) // console.log(`result: ${JSON.stringify(result, null, 2)}`)
assert.isNumber(result) assert.isNumber(result)
}) })
}
// Unit tests only
if (process.env.TEST === 'unit') {
it('should generate transaction for valid BCH-only sweep', async () => { it('should generate transaction for valid BCH-only sweep', async () => {
sandbox sandbox
.stub(utilRoute.electrumx, '_balanceFromElectrumx') .stub(utilRouteInst.blockbook, 'balanceFromBlockbook')
.resolves(mockData.mockBalance) .resolves(mockData.mockBalance)
sandbox sandbox
.stub(utilRoute.electrumx, '_utxosFromElectrumx') .stub(utilRouteInst.blockbook, 'utxosFromBlockbook')
.resolves(mockData.mockUtxos) .resolves(mockData.mockUtxos)
// Force token utxo to appear as regular BCH utxo. // Force token utxo to appear as regular BCH utxo.
sandbox sandbox
.stub(utilRoute.bchjs.SLP.Utils, 'tokenUtxoDetails') .stub(utilRouteInst.bchjs.SLP.Utils, 'tokenUtxoDetails')
.resolves([false, false]) .resolves([false, false])
// Mock sendRawTransaction() so that the hex does not actually get broadcast // Mock sendRawTransaction() so that the hex does not actually get broadcast
// to the network. // to the network.
sandbox sandbox
.stub(utilRoute.bchjs.RawTransactions, 'sendRawTransaction') .stub(utilRouteInst.bchjs.RawTransactions, 'sendRawTransaction')
.resolves('test-txid') .resolves('test-txid')
req.body = { req.body = {
@@ -450,7 +437,7 @@ describe('#Util', () => {
toAddr: 'bitcoincash:qz2qn6zt4qmacf4r6c0e2pdcqsgnkxaa3ql2xpee6p' toAddr: 'bitcoincash:qz2qn6zt4qmacf4r6c0e2pdcqsgnkxaa3ql2xpee6p'
} }
const result = await utilRoute.sweepWif(req, res) const result = await utilRouteInst.sweepWif(req, res)
// console.log(`result: ${JSON.stringify(result, null, 2)}`) // console.log(`result: ${JSON.stringify(result, null, 2)}`)
assert.equal(result, 'test-txid') assert.equal(result, 'test-txid')
@@ -458,22 +445,22 @@ describe('#Util', () => {
it('should throw 422 error if no non-token UTXOs', async () => { it('should throw 422 error if no non-token UTXOs', async () => {
sandbox sandbox
.stub(utilRoute.electrumx, '_balanceFromElectrumx') .stub(utilRouteInst.blockbook, 'balanceFromBlockbook')
.resolves(mockData.mockBalance) .resolves(mockData.mockBalance)
sandbox sandbox
.stub(utilRoute.electrumx, '_utxosFromElectrumx') .stub(utilRouteInst.blockbook, 'utxosFromBlockbook')
.resolves(mockData.mockUtxos) .resolves(mockData.mockUtxos)
// Force token utxo to appear as regular BCH utxo. // Force token utxo to appear as regular BCH utxo.
sandbox sandbox
.stub(utilRoute.bchjs.SLP.Utils, 'tokenUtxoDetails') .stub(utilRouteInst.bchjs.SLP.Utils, 'tokenUtxoDetails')
.resolves(mockData.tokensOnly) .resolves(mockData.tokensOnly)
// Mock sendRawTransaction() so that the hex does not actually get broadcast // Mock sendRawTransaction() so that the hex does not actually get broadcast
// to the network. // to the network.
sandbox sandbox
.stub(utilRoute.bchjs.RawTransactions, 'sendRawTransaction') .stub(utilRouteInst.bchjs.RawTransactions, 'sendRawTransaction')
.resolves('test-txid') .resolves('test-txid')
req.body = { req.body = {
@@ -481,7 +468,7 @@ describe('#Util', () => {
toAddr: 'bitcoincash:qz2qn6zt4qmacf4r6c0e2pdcqsgnkxaa3ql2xpee6p' toAddr: 'bitcoincash:qz2qn6zt4qmacf4r6c0e2pdcqsgnkxaa3ql2xpee6p'
} }
const result = await utilRoute.sweepWif(req, res) const result = await utilRouteInst.sweepWif(req, res)
// console.log(`result: ${JSON.stringify(result, null, 2)}`) // console.log(`result: ${JSON.stringify(result, null, 2)}`)
assert.equal(res.statusCode, 422) assert.equal(res.statusCode, 422)
@@ -494,22 +481,22 @@ describe('#Util', () => {
it('should detect and throw error for multiple token classes', async () => { it('should detect and throw error for multiple token classes', async () => {
sandbox sandbox
.stub(utilRoute.electrumx, '_balanceFromElectrumx') .stub(utilRouteInst.blockbook, 'balanceFromBlockbook')
.resolves(mockData.mockBalance) .resolves(mockData.mockBalance)
sandbox sandbox
.stub(utilRoute.electrumx, '_utxosFromElectrumx') .stub(utilRouteInst.blockbook, 'utxosFromBlockbook')
.resolves(mockData.mockThreeUtxos) .resolves(mockData.mockThreeUtxos)
// Force token utxo to appear as regular BCH utxo. // Force token utxo to appear as regular BCH utxo.
sandbox sandbox
.stub(utilRoute.bchjs.SLP.Utils, 'tokenUtxoDetails') .stub(utilRouteInst.bchjs.SLP.Utils, 'tokenUtxoDetails')
.resolves(mockData.multipleTokens) .resolves(mockData.multipleTokens)
// Mock sendRawTransaction() so that the hex does not actually get broadcast // Mock sendRawTransaction() so that the hex does not actually get broadcast
// to the network. // to the network.
sandbox sandbox
.stub(utilRoute.bchjs.RawTransactions, 'sendRawTransaction') .stub(utilRouteInst.bchjs.RawTransactions, 'sendRawTransaction')
.resolves('test-txid') .resolves('test-txid')
req.body = { req.body = {
@@ -517,7 +504,7 @@ describe('#Util', () => {
toAddr: 'bitcoincash:qz2qn6zt4qmacf4r6c0e2pdcqsgnkxaa3ql2xpee6p' toAddr: 'bitcoincash:qz2qn6zt4qmacf4r6c0e2pdcqsgnkxaa3ql2xpee6p'
} }
const result = await utilRoute.sweepWif(req, res) const result = await utilRouteInst.sweepWif(req, res)
// console.log(`result: ${JSON.stringify(result, null, 2)}`) // console.log(`result: ${JSON.stringify(result, null, 2)}`)
assert.equal(res.statusCode, 422) assert.equal(res.statusCode, 422)
+1 -1
View File
@@ -10,7 +10,7 @@
const chai = require('chai') const chai = require('chai')
const assert = chai.assert const assert = chai.assert
const xpubRoute = require('../../src/routes/v4/xpub') const xpubRoute = require('../../src/routes/v3/xpub')
const nock = require('nock') // HTTP mocking const nock = require('nock') // HTTP mocking
// let originalUrl // Used during transition from integration to unit tests. // let originalUrl // Used during transition from integration to unit tests.
-182
View File
@@ -1,182 +0,0 @@
/*
Mock data for the encryption test library
*/
const mockNoSendTx = {
txid: 'a3b62cd4f4c56ba52139179db14bffd4ab22a2e077f3c62bd5cf0541bfcaf023',
hash: 'a3b62cd4f4c56ba52139179db14bffd4ab22a2e077f3c62bd5cf0541bfcaf023',
version: 2,
size: 226,
locktime: 0,
vin: [
{
txid: '681cc1d392975a7e78cbd490b8a940d7eb029c554f2cee332e3a7119d77fb189',
vout: 1,
scriptSig: {
asm:
'30450221008a7c86ce3a9f5765573440143a00631bb5afa9a58b24674a7fa794bb11ffffc30220397dd4c672c3e0dbe0c4eb6390b5727c5fdc3a42cf3ae275ac13b44d27bb234f[ALL|FORKID] 02bc9fc26ae2bd9a9a14e9baeda4b4fdd95a00bcbf375732c647488b3ce82a2fd7',
hex:
'4830450221008a7c86ce3a9f5765573440143a00631bb5afa9a58b24674a7fa794bb11ffffc30220397dd4c672c3e0dbe0c4eb6390b5727c5fdc3a42cf3ae275ac13b44d27bb234f412102bc9fc26ae2bd9a9a14e9baeda4b4fdd95a00bcbf375732c647488b3ce82a2fd7'
},
sequence: 4294967295
}
],
vout: [
{
value: 0.000006,
n: 0,
scriptPubKey: {
asm:
'OP_DUP OP_HASH160 3c79ba54317c4be9cbfa5aaf357f74b97f3d9696 OP_EQUALVERIFY OP_CHECKSIG',
hex: '76a9143c79ba54317c4be9cbfa5aaf357f74b97f3d969688ac',
reqSigs: 1,
type: 'pubkeyhash',
addresses: ['bitcoincash:qq78nwj5x97yh6wtlfd27dtlwjuh70vkjc59h8tgtg']
}
},
{
value: 0.00003664,
n: 1,
scriptPubKey: {
asm:
'OP_DUP OP_HASH160 58502a73888e0a386fc69b44850af4b2a86ac9a6 OP_EQUALVERIFY OP_CHECKSIG',
hex: '76a91458502a73888e0a386fc69b44850af4b2a86ac9a688ac',
reqSigs: 1,
type: 'pubkeyhash',
addresses: ['bitcoincash:qpv9q2nn3z8q5wr0c6d5fpg27je2s6kf5cavp4ney0']
}
}
],
hex:
'020000000189b17fd719713a2e33ee2c4f559c02ebd740a9b890d4cb787e5a9792d3c11c68010000006b4830450221008a7c86ce3a9f5765573440143a00631bb5afa9a58b24674a7fa794bb11ffffc30220397dd4c672c3e0dbe0c4eb6390b5727c5fdc3a42cf3ae275ac13b44d27bb234f412102bc9fc26ae2bd9a9a14e9baeda4b4fdd95a00bcbf375732c647488b3ce82a2fd7ffffffff0258020000000000001976a9143c79ba54317c4be9cbfa5aaf357f74b97f3d969688ac500e0000000000001976a91458502a73888e0a386fc69b44850af4b2a86ac9a688ac00000000'
}
const mockFulcrumTxHistory = {
success: true,
transactions: [
{
height: 511463,
tx_hash:
'eff00a9538487ff44243c75fb13de19b5783454c42c81b9aff9afbfd09cbaec3'
},
{
height: 511464,
tx_hash:
'7e9aa7a74de2b30200a2d6fc748ff35a0c753221444194f720bb7f61ef1d9153'
},
{
height: 513373,
tx_hash:
'6960255abe64893073921e96bf3c053c82686e0fc22a565494fbe2a31e766975'
},
{
height: 513373,
tx_hash:
'9ea667bcfc9cd337bd6c5583d8094c1b1942bd2015d95b54189deac5070eeff0'
},
{
height: 560481,
tx_hash:
'ecc1b51bac767880382bf3190ff17abf78d0936843a022a943d871116ed50368'
},
{
height: 560615,
tx_hash:
'b3792d28377b975560e1b6f09e48aeff8438d4c6969ca578bd406393bd50bd7d'
},
{
height: 561568,
tx_hash:
'8bc2134c7e48e56e1769b3d7c4c1e3a0acc68e1e58160eee6fa67f3208c07262'
},
{
height: 561569,
tx_hash:
'ceb0cab0e37b59caf3ca29e1a698d19ff47f2827dd09cb2f3b91b9100b1dad1c'
},
{
height: 561572,
tx_hash:
'0f9b49cafeb9ae1d741cdb12137c92816aa8470944c270a78ba2e610bd59190d'
},
{
height: 561582,
tx_hash:
'e4a0ac48ff3f42fc342717a2a3d34248e5e85bae79d59bd20e1b60e61b1c500f'
},
{
height: 562106,
tx_hash:
'1afcc63b244182647909539ebe3f4a44b8ea4120a95edb8d9eebe5347b9491bb'
},
{
height: 562106,
tx_hash:
'c42f8f16d3baa2ee343ea89ef110dfe094992379d08edd30887b8ca7ee671c9a'
}
]
}
const mockTxDetails2 = {
txid: '7e9aa7a74de2b30200a2d6fc748ff35a0c753221444194f720bb7f61ef1d9153',
hash: '7e9aa7a74de2b30200a2d6fc748ff35a0c753221444194f720bb7f61ef1d9153',
version: 1,
size: 221,
locktime: 0,
vin: [
{
txid: 'eff00a9538487ff44243c75fb13de19b5783454c42c81b9aff9afbfd09cbaec3',
vout: 0,
scriptSig: {
asm:
'30440220150e7c9b646fe1f7f576456c9808c89a710e8fbb753e9f5ca6ed2d39a0b69ba602202212968163d8dfe35eec5975fce38440082029cb2691a3e0ad1714f9c412c47a[ALL|FORKID] 044eb40b025df18409f2a5197b010dd62a9e65d9a74e415e5b10367721a9c4baa7ebfee22d14b8ece1c9bd70c0d9e5e8b00b61b81b88a1b5ce6f24eac6b8a34b2c',
hex:
'4730440220150e7c9b646fe1f7f576456c9808c89a710e8fbb753e9f5ca6ed2d39a0b69ba602202212968163d8dfe35eec5975fce38440082029cb2691a3e0ad1714f9c412c47a4141044eb40b025df18409f2a5197b010dd62a9e65d9a74e415e5b10367721a9c4baa7ebfee22d14b8ece1c9bd70c0d9e5e8b00b61b81b88a1b5ce6f24eac6b8a34b2c'
},
sequence: 4294967295
}
],
vout: [
{
value: 0.01266023,
n: 0,
scriptPubKey: {
asm: 'OP_HASH160 91bacd6785e3f8c78d5f29b4020b94c753444130 OP_EQUAL',
hex: 'a91491bacd6785e3f8c78d5f29b4020b94c75344413087',
reqSigs: 1,
type: 'scripthash',
addresses: ['bitcoincash:pzgm4nt8sh3l33udtu5mgqstjnr4x3zpxqrrrndnw5']
}
}
],
hex:
'0100000001c3aecb09fdfb9aff9a1bc8424c4583579be13db15fc74342f47f4838950af0ef000000008a4730440220150e7c9b646fe1f7f576456c9808c89a710e8fbb753e9f5ca6ed2d39a0b69ba602202212968163d8dfe35eec5975fce38440082029cb2691a3e0ad1714f9c412c47a4141044eb40b025df18409f2a5197b010dd62a9e65d9a74e415e5b10367721a9c4baa7ebfee22d14b8ece1c9bd70c0d9e5e8b00b61b81b88a1b5ce6f24eac6b8a34b2cffffffff01675113000000000017a91491bacd6785e3f8c78d5f29b4020b94c7534441308700000000',
blockhash: '000000000000000001aab6818b4ad0379a3f7a13940d9b3d4838b3dea1d0a083',
confirmations: 149222,
time: 1515088493,
blocktime: 1515088493
}
const mockFulcrumNoTxHistory = {
success: true,
transactions: []
}
const mockFulcrumNoSendBalance = {
success: true,
transactions: [
{
height: 633578,
tx_hash:
'a3b62cd4f4c56ba52139179db14bffd4ab22a2e077f3c62bd5cf0541bfcaf023'
}
]
}
module.exports = {
mockNoSendTx,
mockFulcrumTxHistory,
mockTxDetails2,
mockFulcrumNoTxHistory,
mockFulcrumNoSendBalance
}