Compare commits

...
88 Commits
Author SHA1 Message Date
Chris Troutner 46c9e00fe1 Merge pull request #54 from Permissionless-Software-Foundation/ct-unstable
Updating deployment for bchn
2020-11-08 13:42:48 -08:00
Chris Troutner a1c19e8e0a Merge branch 'master' into ct-unstable 2020-11-08 13:40:08 -08:00
Chris Troutner fea8546bd2 fix(deployment): Updating deployment script 2020-11-08 13:39:10 -08:00
Chris Troutner c6d98380b1 Updating gitignore 2020-11-08 12:54:28 -08:00
Chris Troutner 534a652ffe Merge pull request #53 from Permissionless-Software-Foundation/ct-unstable
fix(encryption): Refactored encryption lib to use Fulcrum instead of Blockbook
2020-11-08 12:36:35 -08:00
Chris Troutner a5cee9636e Updating tests for bchn 2020-11-08 12:35:18 -08:00
Chris Troutner 555de52844 fix(encryption): Refactored encryption lib to use Fulcrum instead of Blockbook 2020-11-08 12:26:52 -08:00
Chris Troutner 63e8078681 Merge pull request #52 from Permissionless-Software-Foundation/ct-unstable
feat(rate limits): Bumping anonymous rate limits to 20 RPM
2020-11-08 08:18:36 -08:00
Chris Troutner 821f908fdb feat(rate limits): Bumping anonymous rate limits to 20 RPM 2020-11-08 08:07:16 -08:00
Chris Troutner 9d5a0d9520 Merge pull request #51 from Permissionless-Software-Foundation/ct-unstable
Ct unstable
2020-11-08 07:20:46 -08:00
Chris Troutner f146504ed0 Merge branch 'master' into ct-unstable 2020-11-08 07:17:40 -08:00
Chris Troutner 03a4eb1b72 Debugging deployment from CI 2020-11-08 07:17:30 -08:00
Chris Troutner 6286b4967b Merge pull request #50 from Permissionless-Software-Foundation/ct-unstable
Ct unstable
2020-11-08 07:10:36 -08:00
Chris Troutner 6bf86b29f8 Merge branch 'master' into ct-unstable 2020-11-08 07:09:18 -08:00
Chris Troutner 0b180e02a4 Debugging deployment from CI 2020-11-08 07:09:08 -08:00
Chris Troutner a2267c9590 Merge pull request #49 from Permissionless-Software-Foundation/ct-unstable
Ct unstable
2020-11-07 22:01:41 -08:00
Chris Troutner ed83f5eb3f Merge branch 'master' into ct-unstable 2020-11-07 21:59:36 -08:00
Chris Troutner 91e5423dd2 Debugging deployment from CI 2020-11-07 21:59:20 -08:00
Chris Troutner de4cb8762a Merge pull request #48 from Permissionless-Software-Foundation/ct-unstable
Ct unstable
2020-11-07 21:52:34 -08:00
Chris Troutner 284e9a9ee7 Merge branch 'master' into ct-unstable 2020-11-07 21:50:47 -08:00
Chris Troutner ec2fb8e24f testing ci 2020-11-07 21:50:34 -08:00
Chris Troutner 07fc05fa12 Merge pull request #47 from Permissionless-Software-Foundation/ct-unstable
testing ci
2020-11-07 21:37:12 -08:00
Chris Troutner 2bfad5d261 testing ci 2020-11-07 21:34:04 -08:00
Chris Troutner efe4cad08b Merge pull request #46 from Permissionless-Software-Foundation/ct-unstable
testing ci
2020-11-07 21:27:30 -08:00
Chris Troutner b704707ded Merge branch 'master' into ct-unstable 2020-11-07 21:26:00 -08:00
Chris Troutner 6c8a156f37 testing ci 2020-11-07 21:24:45 -08:00
Chris Troutner 19d9153629 Merge pull request #45 from Permissionless-Software-Foundation/ct-unstable
Switching CI from Travis to Jenkins
2020-11-07 21:10:20 -08:00
Chris Troutner 840e92e54c testing ci 2020-11-07 21:00:05 -08:00
Chris Troutner 8e4592826f Testing CI 2020-11-07 20:47:38 -08:00
Chris Troutner c4085fb929 Updating deploy script for Jenkins 2020-11-07 20:40:06 -08:00
Chris Troutner 00e1840ca2 Updating gitignore 2020-11-07 19:42:58 -08:00
Chris Troutner 7e9a9bc635 update to gitignore 2020-11-07 18:34:38 -08:00
Chris Troutner 41163096e3 Merge pull request #44 from Permissionless-Software-Foundation/ct-unstable
fix(raw transactions): Updating for change in output of abc full node
2020-10-31 10:14:19 -07:00
Chris Troutner 1b3e1cc293 fix(raw transactions): Updating for change in output of abc full node 2020-10-31 10:11:44 -07:00
Chris Troutner 72460369f5 Merge pull request #43 from Permissionless-Software-Foundation/ct-unstable
Adding price endpoint
2020-10-19 17:56:06 -07:00
Chris Troutner 5459fc8929 feat(price): Adding price endpoint 2020-10-19 17:50:49 -07:00
Chris Troutner b4571cf71c Update to gitignore 2020-10-16 22:22:55 -07:00
Chris Troutner 54a472af5f Fixing curl example for hydrateUtxos() 2020-10-12 11:03:46 -07:00
Chris Troutner ebbf12c394 Merge pull request #42 from Permissionless-Software-Foundation/ct-unstable
fix(bch-js): Updated to v 3.7.1
2020-10-11 18:09:42 -07:00
Chris Troutner eeb63ec373 fix(bch-js): Updated to v 3.7.1 2020-10-11 18:04:30 -07:00
Chris Troutner 8244fbbef5 removing a comment 2020-10-11 17:02:17 -07:00
Chris Troutner 43dfea7cbe Merge pull request #41 from Permissionless-Software-Foundation/ct-unstable
validateTxid2 - slp-validate based
2020-10-11 10:09:18 -07:00
Chris Troutner a2c30aee6d fix(validateTxid2): Added unit tests 2020-10-11 10:03:24 -07:00
Chris Troutner 1959b20115 Removed integrated slp-validate dependencies 2020-10-11 09:44:28 -07:00
Chris Troutner e399e05022 fix(validateTxid2): Final form. Updated integration tests 2020-10-11 09:10:35 -07:00
Chris Troutner 2d04169329 Working on killing promise that takes too long 2020-10-10 12:19:27 -07:00
Chris Troutner 5619c2fbed Added a couple integration tests around validate2Single() 2020-10-10 12:03:02 -07:00
Chris Troutner 388a3ee53b fix(validate2Single): Added alternative SLP TXID validator 2020-10-10 09:03:14 -07:00
Chris Troutner 8aff89dead Merge pull request #40 from Permissionless-Software-Foundation/ct-unstable
fix(hydrateUtxos): Adding support for a corner case
2020-10-06 13:23:02 -07:00
Chris Troutner efd669e5fe fix(hydrateUtxos): Adding support for a corner case 2020-10-06 13:15:53 -07:00
Chris Troutner d5433ed811 Merge pull request #39 from Permissionless-Software-Foundation/6-electrum-broadcast
fix(electrumx broadcast): Added body variable. Curl example works now
2020-10-02 13:36:56 -07:00
Chris Troutner 6b2588c487 fix(electrumx broadcast): Added body variable. Curl example works now 2020-10-02 13:33:35 -07:00
Chris Troutner 4aee109223 Merge pull request #35 from Permissionless-Software-Foundation/6-electrum-broadcast
Add POST /tx/broadcast endpoint + tests
2020-10-02 11:08:37 -07:00
Chris Troutner 5f63e53a6d Merge branch 'master' into 6-electrum-broadcast 2020-10-02 08:18:23 -07:00
Chris Troutner bde809d62d Merge pull request #38 from Permissionless-Software-Foundation/ct-unstable
fix(hydrateUtxos): Adding localhost to max rate limit
2020-10-01 20:58:42 -07:00
Chris Troutner c664758654 Fixing bug 2020-10-01 20:51:01 -07:00
Chris Troutner b4823be86a fix(hydrateUtxos): Adding localhost to max rate limit 2020-10-01 20:47:04 -07:00
Chris Troutner 1c9e0858de Merge pull request #37 from Permissionless-Software-Foundation/ct-unstable
fix(hydrateUtxos): Using local calls to the REST API
2020-10-01 20:22:57 -07:00
Chris Troutner 41a3cd91fc fix(hydrateUtxos): Using local calls to the REST API 2020-10-01 20:20:28 -07:00
Chris Troutner 0b4038eeb4 Merge pull request #36 from Permissionless-Software-Foundation/ct-unstable
Ct unstable
2020-10-01 17:38:38 -07:00
Chris Troutner 86bf504b78 Fixing unit tests 2020-10-01 17:34:36 -07:00
Chris Troutner d56e6d6b3a linting 2020-10-01 17:26:31 -07:00
Chris Troutner 7339b8554f fix(basic auth): Disabling Basic Authentication 2020-10-01 17:25:52 -07:00
Rosco Kalis e97a0be456 Remove .only 2020-10-01 06:53:46 -04:00
Rosco Kalis 085618470f Update comments 2020-09-30 20:55:43 -04:00
Rosco Kalis 22d7a2b7a2 Add POST /tx/broadcast endpoint + tests 2020-09-30 20:52:13 -04:00
Chris Troutner e6dcaa2f24 Merge pull request #34 from Permissionless-Software-Foundation/ct-unstable
fix(rate limits): Fixing hydrateUtxos rate limits
2020-09-28 20:24:43 -07:00
Chris Troutner 698ac38251 fix(rate limits): Fixing hydrateUtxos rate limits 2020-09-28 20:17:07 -07:00
Chris Troutner 7c66ce7f73 Merge pull request #33 from Permissionless-Software-Foundation/ct-unstable
Increasing rate limits for hydrateUtxos()
2020-09-28 19:57:55 -07:00
Chris Troutner 9e3dfd9848 Updating package lock 2020-09-28 19:55:28 -07:00
Chris Troutner 10eb4da112 fix(rate limits): Increasing rate limits for internal calls 2020-09-28 19:49:49 -07:00
Chris Troutner f4f5f02a2f fix(bch-js): bumping to version 3.6.3 2020-09-28 19:45:47 -07:00
Chris Troutner bb69a6dee9 Merge pull request #32 from Permissionless-Software-Foundation/ct-unstable
fix(rate limits): Increasing internal rate limits
2020-09-28 18:35:29 -07:00
Chris Troutner d67b3aa052 fix(rate limits): Increasing internal rate limits 2020-09-28 18:32:43 -07:00
Chris Troutner 7e1c3d6a73 Merge pull request #31 from Permissionless-Software-Foundation/ct-unstable
fix(hydrateUtxos): Bumping bch-js version to fix hydateUtxos
2020-09-28 18:13:43 -07:00
Chris Troutner 6165001790 fix(hydrateUtxos): Bumping bch-js version to fix hydateUtxos 2020-09-28 18:10:49 -07:00
Chris Troutner 77ba06d68c Merge pull request #30 from Permissionless-Software-Foundation/ct-unstable
fix(internal calls): Applying paid rate limits to internal calls
2020-09-28 08:48:00 -07:00
Chris Troutner 40f6b17d3b fix(internal calls): Applying paid rate limits to internal calls 2020-09-28 08:37:34 -07:00
Chris Troutner 4d40a7cb01 Merge pull request #29 from Permissionless-Software-Foundation/ct-unstable
fix(hydrateUtxos): Adjusting input to match output of Electrumx.utxo()
2020-09-27 17:30:20 -07:00
Chris Troutner 0a14ca08a4 fix(hydrateUtxos): Adjusting input to match output of Electrumx.utxo() 2020-09-27 17:26:34 -07:00
Chris Troutner 198141b75a Merge pull request #28 from Permissionless-Software-Foundation/ct-unstable
feat(hydrateUtxos): Porting hydrateUtxos convenience utility function
2020-09-27 00:36:09 -07:00
Chris Troutner 6f25ec4b68 Replacing @chris.troutner/bch-js with @psf/bch-js 2020-09-27 00:31:38 -07:00
Chris Troutner cb416d26c0 Replacing @chris.troutner/bch-js with @psf/bch-js 2020-09-27 00:26:57 -07:00
Chris Troutner 376094c6c2 feat(hydrateUtxos): Porting hydrateUtxos convenience utility function 2020-09-27 00:21:36 -07:00
Chris Troutner f566d44252 Merge pull request #27 from Permissionless-Software-Foundation/ct-unstable
fix(electrumx): Fixes GET tx details
2020-09-10 10:54:25 -07:00
Chris Troutner 0e7b45214f fix(elecrtrumx): Fixes GET tx details 2020-09-10 10:51:22 -07:00
Chris Troutner 58366b5442 Merge pull request #26 from Permissionless-Software-Foundation/25-fix-txdetails-get
Change url param name
2020-09-10 10:48:32 -07:00
Rosco Kalis f97e7016c5 Change url param name 2020-09-10 19:39:35 +02:00
33 changed files with 2550 additions and 1056 deletions
+7
View File
@@ -20,6 +20,13 @@ start-free-main.sh
start-local-mainnet.sh.save
start-local-testnet.sh.save
temp.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
coverage
start-my-infra
+1 -1
View File
@@ -18,7 +18,7 @@ Both bch-api and bch-js are part of the
- [API Documentation](https://fullstack.cash/documentation)
- [Example Code](https://fullstack.cash/examples)
Have questions? Need help? Join our community support
Have questions? Need help? Join our community support:
[Telegram channel](https://t.me/bch_js_toolkit)
## Features
+3 -1
View File
@@ -3,7 +3,9 @@
*/
const config = {
apiTokenSecret: process.env.TOKENSECRET ? process.env.TOKENSECRET : 'secret-jwt-token'
apiTokenSecret: process.env.TOKENSECRET
? process.env.TOKENSECRET
: 'secret-jwt-token'
}
module.exports = config
+19 -9
View File
@@ -1,17 +1,27 @@
#!/bin/bash
# Triggers a webhook to update the staging server at staging.fullstack.cash.
#!/bin/bash
#echo $DEPLOY_SECRET
echo "Deploy to production...."
# This if statement will only deploy on Jenkins when its testing the master branch.
echo "-->$GIT_BRANCH<--"
if [[ $GIT_BRANCH =~ "master" ]]
then
export DATA="{\"ref\":\"$DEPLOY_SECRET\"}"
echo "Deploying to production...."
#echo $DATA
export DATA="{\"ref\":\"$DEPLOY_SECRET\"}"
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
#echo $DATA
echo "...Finished deploying to production."
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://$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
+808 -672
View File
File diff suppressed because it is too large Load Diff
+3 -3
View File
@@ -17,9 +17,9 @@
"test": "npm run lint && npm run test-v3",
"lint": "standard --env mocha --fix",
"test-v3": "export NETWORK=mainnet && nyc --reporter=text mocha --timeout 60000 test/v3/",
"test:temp": "export NETWORK=mainnet && mocha --timeout 25000 test/v3/util.js",
"test:temp": "export NETWORK=mainnet && export TEST=integration && mocha --timeout 25000 test/v3/encryption.js",
"test:integration": "mocha test/v3/integration",
"test:integration:slpdb": "mocha --timeout 25000 test/v3/integration/slp*.js",
"test:integration:slpdb": "mocha --timeout 25000 -g '#validate2Single' test/v3/integration/slp*.js",
"coverage": "nyc report --reporter=text-lcov | coveralls",
"coverage:report": "export NETWORK=mainnet && nyc --reporter=html mocha --timeout 25000 test/v3/",
"docs": "./node_modules/.bin/apidoc -i src/routes/v3 -o docs"
@@ -28,7 +28,7 @@
"node": ">=10.15.1"
},
"dependencies": {
"@chris.troutner/bch-js": "^3.0.1",
"@psf/bch-js": "^3.7.1",
"apidoc": "^0.23.0",
"axios": "^0.19.0",
"bitcore-lib-cash": "^8.20.3",
+3
View File
@@ -39,6 +39,7 @@ const BlockbookV3 = require('./routes/v3/blockbook')
const Ninsight = require('./routes/v3/ninsight')
const ElectrumXV3 = require('./routes/v3/electrumx')
const EncryptionV3 = require('./routes/v3/encryption')
const PriceV3 = require('./routes/v3/price')
require('dotenv').config()
@@ -52,6 +53,7 @@ const blockbookV3 = new BlockbookV3()
const electrumxv3 = new ElectrumXV3()
electrumxv3.connect()
const encryptionv3 = new EncryptionV3()
const pricev3 = new PriceV3()
const app = express()
@@ -121,6 +123,7 @@ app.use(`/${v3prefix}/` + 'xpub', xpubV3.router)
app.use(`/${v3prefix}/` + 'blockbook', blockbookV3.router)
app.use(`/${v3prefix}/` + 'electrumx', electrumxv3.router)
app.use(`/${v3prefix}/` + 'encryption', encryptionv3.router)
app.use(`/${v3prefix}/` + 'price', pricev3.router)
const ninsight = new Ninsight()
app.use(`/${v3prefix}/` + 'ninsight', ninsight.router)
+49 -49
View File
@@ -22,9 +22,9 @@
'use strict'
const passport = require('passport')
const BasicStrategy = require('passport-http').BasicStrategy
// const BasicStrategy = require('passport-http').BasicStrategy
const AnonymousStrategy = require('passport-anonymous')
const wlogger = require('../util/winston-logging')
// const wlogger = require('../util/winston-logging')
// Used for debugging and iterrogating JS objects.
const util = require('util')
@@ -33,9 +33,9 @@ util.inspect.defaultOptions = { depth: 1 }
// let _this
// Set default rate limit value for testing
const PRO_PASSES = process.env.PRO_PASS ? process.env.PRO_PASS : 'BITBOX'
// const PRO_PASSES = process.env.PRO_PASS ? process.env.PRO_PASS : 'BITBOX'
// 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}`)
@@ -48,55 +48,55 @@ class AuthMW {
passport.use(new AnonymousStrategy())
// Initialize passport for 'basic' authentication.
passport.use(
new BasicStrategy({ passReqToCallback: true }, function (
req,
username,
password,
done
) {
// console.log(`req: ${util.inspect(req)}`)
// console.log(`username: ${username}`)
// console.log(`password: ${password}`)
// Create the req.locals property if it does not yet exist.
if (!req.locals) {
req.locals = {
// default values
proLimit: false,
apiLevel: 0
}
}
// Set pro-tier rate limit to flag to false by default.
req.locals.proLimit = false
// Evaluate the username and password and set the rate limit accordingly.
// if (username === "BITBOX" && password === PRO_PASS) {
if (username === 'BITBOX') {
for (let i = 0; i < PRO_PASS.length; i++) {
const thisPass = PRO_PASS[i]
if (password === thisPass) {
wlogger.verbose(`${req.url} called by ${password.slice(0, 6)}`)
// Success
req.locals.proLimit = true
break
}
}
}
// console.log(`req.locals: ${util.inspect(req.locals)}`)
return done(null, true)
})
)
// passport.use(
// new BasicStrategy({ passReqToCallback: true }, function (
// req,
// username,
// password,
// done
// ) {
// // console.log(`req: ${util.inspect(req)}`)
// // console.log(`username: ${username}`)
// // console.log(`password: ${password}`)
//
// // Create the req.locals property if it does not yet exist.
// if (!req.locals) {
// req.locals = {
// // default values
// proLimit: false,
// apiLevel: 0
// }
// }
//
// // Set pro-tier rate limit to flag to false by default.
// req.locals.proLimit = false
//
// // Evaluate the username and password and set the rate limit accordingly.
// // if (username === "BITBOX" && password === PRO_PASS) {
// if (username === 'BITBOX') {
// for (let i = 0; i < PRO_PASS.length; i++) {
// const thisPass = PRO_PASS[i]
//
// if (password === thisPass) {
// wlogger.verbose(`${req.url} called by ${password.slice(0, 6)}`)
//
// // Success
// req.locals.proLimit = true
// break
// }
// }
// }
//
// // console.log(`req.locals: ${util.inspect(req.locals)}`)
//
// return done(null, true)
// })
// )
}
// Middleware called by the route.
mw () {
return passport.authenticate(['basic', 'anonymous'], {
return passport.authenticate(['anonymous'], {
session: false
})
}
+36 -12
View File
@@ -1,3 +1,15 @@
/*
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'
const wlogger = require('../util/winston-logging')
@@ -20,7 +32,7 @@ const redisClient = new Redis(redisOptions)
const { RateLimiterRedis } = require('rate-limiter-flexible')
const rateLimitOptions = {
storeClient: redisClient,
points: 100, // Number of points
points: 1000, // Number of points
duration: 60 // Per minute (per 60 seconds)
}
@@ -98,8 +110,8 @@ class RateLimits {
wlogger.debug('No JWT token found!')
}
// Used for displaying error message. Default value is 3.
let rateLimit = 3
// Default value is 50 points per request = 20 RPM
let rateLimit = 50
// Code here for the rate limiter is adapted from this example:
// https://github.com/animir/node-rate-limiter-flexible/wiki/Overall-example#authorized-and-not-authorized-users
@@ -118,9 +130,12 @@ class RateLimits {
// Retrieve the origin.
let origin = req.get('origin')
// Handle calls coming from the intranet.
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
@@ -130,6 +145,15 @@ class RateLimits {
(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.
}
// For internal calls, increase rate limits to as fast as possible.
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.
@@ -139,7 +163,7 @@ class RateLimits {
`User ${key} consuming ${pointsToConsume} point for resource ${resource}.`
)
rateLimit = Math.floor(100 / pointsToConsume)
rateLimit = Math.floor(1000 / pointsToConsume)
// Update the key so that rate limits track both the user and the resource.
key = `${key}-${resource}`
@@ -169,7 +193,7 @@ class RateLimits {
// Calculates the points consumed, based on the jwt information and the route
// requested.
calcPoints (jwtInfo) {
let retVal = 30 // By default, use anonymous tier.
let retVal = 50 // By default, use anonymous tier.
try {
// console.log(`jwtInfo: ${JSON.stringify(jwtInfo, null, 2)}`)
@@ -186,22 +210,22 @@ class RateLimits {
if (jwtInfo.id) {
// SLP indexer routes
if (level40Routes.includes(resource)) {
if (apiLevel >= 40) retVal = 1
if (apiLevel >= 40) retVal = 10
// else if (apiLevel >= 10) retVal = 10
else retVal = 10
else retVal = 50
// Normal indexer routes
} else if (level30Routes.includes(resource)) {
if (apiLevel >= 30) retVal = 1
else retVal = 10
if (apiLevel >= 30) retVal = 10
else retVal = 50
// Full node tier
} else if (apiLevel >= 20) {
retVal = 1
retVal = 10
// Free tier, full node only.
} else {
retVal = 10
retVal = 50
}
}
@@ -209,7 +233,7 @@ class RateLimits {
} catch (err) {
wlogger.error('Error in route-ratelimit.js/calcPoints()')
// throw err
retVal = 30
retVal = 50
}
return retVal
+1 -1
View File
@@ -23,7 +23,7 @@ const router = express.Router()
const util = require('util')
util.inspect.defaultOptions = { depth: 1 }
const BCHJS = require('@chris.troutner/bch-js')
const BCHJS = require('@psf/bch-js')
const bchjs = new BCHJS()
let _this
+89 -2
View File
@@ -19,7 +19,7 @@ const config = require('../../../config')
const RouteUtils = require('../../util/route-utils')
const routeUtils = new RouteUtils()
const BCHJS = require('@chris.troutner/bch-js')
const BCHJS = require('@psf/bch-js')
const bchjs = new BCHJS()
let _this
@@ -50,8 +50,9 @@ class Electrum {
_this.router.get('/', _this.root)
_this.router.get('/utxos/:address', _this.getUtxos)
_this.router.post('/utxos', _this.utxosBulk)
_this.router.get('/tx/data/:address', _this.getTransactionDetails)
_this.router.get('/tx/data/:txid', _this.getTransactionDetails)
_this.router.post('/tx/data', _this.transactionDetailsBulk)
_this.router.post('/tx/broadcast', _this.broadcastTransaction)
_this.router.get('/block/headers/:height', _this.getBlockHeaders)
_this.router.post('/block/headers', _this.blockHeadersBulk)
_this.router.get('/balance/:address', _this.getBalance)
@@ -491,6 +492,92 @@ class Electrum {
}
}
// Returns a promise that resolves to transaction ID of the broadcasted transaction or an error.
// Expects input to be a txHex string, and input validation to have already
// been done by parent, calling function.
async _broadcastTransactionWithElectrum (txHex) {
try {
if (!_this.isReady) {
throw new Error(
'ElectrumX server connection is not ready. Call await connectToServer() first.'
)
}
// Broadcast the transaction hex to the ElectrumX server.
const electrumResponse = await _this.electrumx.request(
'blockchain.transaction.broadcast',
txHex
)
// console.log(
// `electrumResponse: ${JSON.stringify(electrumResponse, null, 2)}`
// )
return electrumResponse
} catch (err) {
// console.log('err: ', err)
// Write out error to error log.
wlogger.error(
'Error in elecrumx.js/_transactionDetailsFromElectrum(): ',
err
)
throw err
}
}
/**
* @api {post} /electrumx/tx/broadcast Broadcast a raw transaction
* @apiName Broadcast a raw transaction
* @apiGroup ElectrumX / Fulcrum
* @apiDescription Broadcast a raw transaction and return the transaction ID on success or error on failure.
*
* @apiExample Example usage:
* curl -X POST "https://api.fullstack.cash/v3/electrumx/tx/broadcast" -H "accept: application/json" -H "Content-Type: application/json" -d '{"txHex":"020000000265d13ef402840c8a51f39779afb7ae4d49e4b0a3c24a3d0e7742038f2c679667010000006441dd1dd72770cadede1a7fd0363574846c48468a398ddfa41a9677c74cac8d2652b682743725a3b08c6c2021a629011e11a264d9036e9d5311e35b5f4937ca7b4e4121020797d8fd4d2fa6fd7cdeabe2526bfea2b90525d6e8ad506ec4ee3c53885aa309ffffffff65d13ef402840c8a51f39779afb7ae4d49e4b0a3c24a3d0e7742038f2c679667000000006441347d7f218c11c04487c1ad8baac28928fb10e5054cd4494b94d078cfa04ccf68e064fb188127ff656c0b98e9ce87f036d183925d0d0860605877d61e90375f774121028a53f95eb631b460854fc836b2e5d31cad16364b4dc3d970babfbdcc3f2e4954ffffffff035ac355000000000017a914189ce02e332548f4804bac65cba68202c9dbf822878dfd0800000000001976a914285bb350881b21ac89724c6fb6dc914d096cd53b88acf9ef3100000000001976a91445f1f1c4a9b9419a5088a3e9c24a293d7a150e6488ac00000000"}'
*
*/
// POST handler for broadcasting a single transaction
async broadcastTransaction (req, res, next) {
try {
const txHex = req.body.txHex
if (typeof txHex !== 'string') {
res.status(400)
return res.json({
success: false,
error: 'request body must be a string.'
})
}
wlogger.debug(
'Executing electrumx/broadcastTransaction with this tx hex: ',
txHex
)
// Get data from ElectrumX server.
const electrumResponse = await _this._broadcastTransactionWithElectrum(txHex)
// console.log(`_utxosFromElectrumx(): ${JSON.stringify(electrumResponse, null, 2)}`)
// Pass the error message if ElectrumX reports an error.
if (electrumResponse instanceof Error) {
res.status(400)
return res.json({
success: false,
error: electrumResponse.message
})
}
res.status(200)
return res.json({
success: true,
txid: electrumResponse
})
} catch (err) {
wlogger.error('Error in electrumx.js/broadcastTransaction().', err)
return _this.errorHandler(err, res)
}
}
// Returns a promise that resolves to block header data for a block height.
// Expects input to be a height number, and input validation to have already
// been done by parent, calling function.
+16 -21
View File
@@ -15,16 +15,11 @@ const config = require('../../../config')
const RouteUtils = require('../../util/route-utils')
const routeUtils = new RouteUtils()
const BCHJS = require('@chris.troutner/bch-js')
const bchjs = new BCHJS()
// Use Blockbook for getting indexer data.
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()
const BCHJS = require('@psf/bch-js')
const restURL = process.env.LOCAL_RESTURL
? process.env.LOCAL_RESTURL
: 'https://api.fullstack.cash/v3/'
const bchjs = new BCHJS({ restURL })
let _this
@@ -36,8 +31,8 @@ class Encryption {
_this.axios = axios
_this.routeUtils = routeUtils
_this.bchjs = bchjs
_this.blockbook = blockbook
_this.rawTransactions = rawTransactions
// _this.blockbook = blockbook
// _this.rawTransactions = rawTransactions
_this.router = router
_this.router.get('/', _this.root)
@@ -115,23 +110,23 @@ class Encryption {
cashAddr
)
// Retrieve the transaction history for this address.
const balance = await _this.blockbook.balanceFromBlockbook(cashAddr)
// console.log(`balance: ${JSON.stringify(balance, null, 2)}`)
const rawTxData = await _this.bchjs.Electrumx.transactions(cashAddr)
// console.log(`rawTxData: ${JSON.stringify(rawTxData, null, 2)}`)
const txHistory = balance.txids
// console.log(`txHistory: ${JSON.stringify(txHistory, null, 2)}`)
// Extract just the TXIDs
const txids = rawTxData.transactions.map((elem) => elem.tx_hash)
// console.log(`txids: ${JSON.stringify(txids, null, 2)}`)
// throw error if there is no transaction history.
if (!txHistory || txHistory.length === 0) {
if (!txids || txids.length === 0) {
throw new Error('No transaction history.')
}
// Loop through the transaction history and search for the public key.
for (let i = 0; i < txHistory.length; i++) {
const thisTx = txHistory[i]
for (let i = 0; i < txids.length; i++) {
const thisTx = txids[i]
const txDetails = await _this.rawTransactions.getRawTransactionsFromNode(
const txDetails = await _this.bchjs.RawTransactions.getRawTransaction(
thisTx,
true
)
+1 -1
View File
@@ -17,7 +17,7 @@ const routeUtils = new RouteUtils()
const util = require('util')
util.inspect.defaultOptions = { depth: 1 }
const BCHJS = require('@chris.troutner/bch-js')
const BCHJS = require('@psf/bch-js')
const bchjs = new BCHJS()
let _this
+1 -1
View File
@@ -11,7 +11,7 @@ const express = require('express')
const router = express.Router()
// const BCHJS = require('@chris.troutner/bch-js')
// const BCHJS = require('@psf/bch-js')
// const bchjs = new BCHJS()
// let _this
+118
View File
@@ -0,0 +1,118 @@
/*
This price route is really just a wrapper for another API price endpoint.
The main reason for hosting this price wrapper is so that the price can
be accessible over Tor. Tor is typically blocked by other REST API servers.
*/
'use strict'
const express = require('express')
const axios = require('axios')
const wlogger = require('../../util/winston-logging')
const util = require('util')
const RouteUtils = require('../../util/route-utils')
const routeUtils = new RouteUtils()
let _this // Global context for 'this' instance of the Class.
class Price {
constructor () {
_this = this
this.axios = axios
this.routeUtils = routeUtils
this.priceUrl = 'https://api.coinbase.com/v2/exchange-rates?currency=BCH'
this.router = express.Router()
this.router.get('/', _this.root)
this.router.get('/usd', _this.getUSD)
this.router.get('/rates', _this.getBCHRate)
}
// DRY error handler.
errorHandler (err, res) {
// Attempt to decode the error message.
const { msg, status } = _this.routeUtils.decodeError(err)
if (msg) {
res.status(status)
return res.json({ error: msg })
}
res.status(500)
return res.json({ error: util.inspect(err) })
}
// Root API endpoint. Simply acknowledges that it exists.
root (req, res, next) {
return res.json({ status: 'price' })
}
/**
* @api {get} /price/usd Get the USD price of BCH
* @apiName Get the USD price of BCH
* @apiGroup Price
* @apiDescription Get the USD price of BCH
*
*
* @apiExample Example usage:
* curl -X GET "https://api.fullstack.cash/v3/price/usd" -H "accept: application/json"
*
*/
async getUSD (req, res, next) {
try {
// Request options
const opt = {
method: 'get',
baseURL: _this.priceUrl,
timeout: 15000
}
const response = await axios.request(opt)
// console.log(`response.data: ${JSON.stringify(response.data, null, 2)}`)
return res.json({ usd: Number(response.data.data.rates.USD) })
} catch (err) {
// Write out error to error log.
wlogger.error('Error in price.js/getUSD().', err)
return _this.errorHandler(err, res)
}
}
/**
* @api {get} /price/usd Get rates for several different currencies
* @apiName Get rates for several different currencies
* @apiGroup Price
* @apiDescription Get rates for several different currencies
*
*
* @apiExample Example usage:
* curl -X GET "https://api.fullstack.cash/v3/price/rates" -H "accept: application/json"
*
*/
// Get rates for several different currencies
async getBCHRate (req, res, next) {
try {
// Request options
const opt = {
method: 'get',
baseURL: _this.priceUrl,
timeout: 15000
}
const response = await axios.request(opt)
// console.log(`response.data: ${JSON.stringify(response.data, null, 2)}`)
return res.json(response.data.data.rates)
} catch (err) {
// Write out error to error log.
wlogger.error('Error in price.js/getUSD().', err)
return _this.errorHandler(err, res)
}
}
}
module.exports = Price
+1 -1
View File
@@ -10,7 +10,7 @@ const wlogger = require('../../util/winston-logging')
const util = require('util')
util.inspect.defaultOptions = { depth: 1 }
const BCHJS = require('@chris.troutner/bch-js')
const BCHJS = require('@psf/bch-js')
const bchjs = new BCHJS()
module.exports = {
+1 -1
View File
@@ -1,6 +1,6 @@
const axios = require('axios')
const SLPSDK = require('@chris.troutner/bch-js')
const SLPSDK = require('@psf/bch-js')
const SLP = new SLPSDK()
class Slpdb {
+155 -8
View File
@@ -13,8 +13,12 @@ const Slpdb = require('./services/slpdb')
// const strftime = require('strftime')
const wlogger = require('../../util/winston-logging')
const BCHJS = require('@chris.troutner/bch-js')
const bchjs = new BCHJS()
// Instantiate a local copy of bch-js using the local REST API server.
const LOCAL_RESTURL = process.env.LOCAL_RESTURL
? process.env.LOCAL_RESTURL
: 'https://api.fullstack.cash/v3/'
const BCHJS = require('@psf/bch-js')
const bchjs = new BCHJS({ restURL: LOCAL_RESTURL })
// Used to convert error messages to strings, to safely pass to users.
const util = require('util')
@@ -48,6 +52,7 @@ class Slp {
constructor () {
_this = this
// Encapsulate external libraries.
_this.axios = axios
_this.routeUtils = routeUtils
_this.BigNumber = BigNumber
@@ -68,6 +73,7 @@ class Slp {
_this.router.post('/convert', _this.convertAddressBulk)
_this.router.post('/validateTxid', _this.validateBulk)
_this.router.get('/validateTxid/:txid', _this.validateSingle)
_this.router.get('/validateTxid2/:txid', _this.validate2Single)
_this.router.get('/txDetails/:txid', _this.txDetails)
_this.router.get('/tokenStats/:tokenId', _this.tokenStats)
_this.router.get(
@@ -79,6 +85,7 @@ class Slp {
_this.txsByAddressSingle
)
_this.router.post('/generateSendOpReturn', _this.generateSendOpReturn)
_this.router.post('/hydrateUtxos', _this.hydrateUtxos)
}
// DRY error handler.
@@ -1058,7 +1065,7 @@ class Slp {
* @api {get} /slp/validateTxid/{txid} Validate single SLP transaction by txid.
* @apiName Validate single SLP transaction by txid.
* @apiGroup SLP
* @apiDescription Validate single SLP transaction by txid.
* @apiDescription Validate single SLP transaction by txid, using SLPDB.
*
*
* @apiExample Example usage:
@@ -1131,11 +1138,66 @@ class Slp {
}
}
// Returns a Boolean if the input TXID is a valid SLP TXID.
// async function isValidSlpTxid (txid) {
// const isValid = await slpValidator.isValidSlpTxid(txid)
// return isValid
// }
/**
* @api {get} /slp/validateTxid2/{txid} Validate single SLP transaction by txid.
* @apiName Validate single SLP transaction by txid.
* @apiGroup SLP
* @apiDescription Validate single SLP transaction by txid, using slp-validate.
* Slower, less efficient method of validating an SLP TXID using the slp-validate
* npm library. This method is independent of SLPDB and can be used as a fall-back
* when SLPDB returns 'null' values.
*
*
* @apiExample Example usage:
* curl -X GET "https://api.fullstack.cash/v3/slp/validateTxid2/f7e5199ef6669ad4d078093b3ad56e355b6ab84567e59ad0f08a5ad0244f783a" -H "accept:application/json"
*
*
*/
async validate2Single (req, res, next) {
try {
const txid = req.params.txid
// Validate input
if (!txid || txid === '') {
res.status(400)
return res.json({ error: 'txid can not be empty' })
}
wlogger.debug(
'Executing slp/validate2Single/:txid with this txid: ',
txid
)
// null by default.
// Default return value.
const result = {
txid: txid,
isValid: null,
msg: ''
}
// Request options
const opt = {
method: 'get',
baseURL: `${process.env.SLP_API_URL}slp/validate/${txid}`,
timeout: 10000 // Exit after 10 seconds.
}
const tokenRes = await _this.axios.request(opt)
// console.log(`tokenRes.data: ${JSON.stringify(tokenRes.data, null, 2)}`)
// console.log(`tokenRes: `, tokenRes)
// Overwrite the default value with the result from slp-api.
result.isValid = tokenRes.data.isValid
res.status(200)
return res.json(result)
} catch (err) {
// console.log('validate2Single error: ', err)
wlogger.error('Error in slp.ts/validate2Single().', err)
return _this.errorHandler(err, res)
}
}
/**
* @api {get} /slp/txDetails/{txid} SLP transaction details.
@@ -1519,6 +1581,91 @@ class Slp {
})
}
}
/**
* @api {post} /slp/hydrateUtxos/ hydrateUtxos
* @apiName SLP hydrateUtxos
* @apiGroup SLP
* @apiDescription Hydrate UTXO data with SLP information.
*
* Expects an array of UTXO objects as input. Returns an array of equal size.
* Returns UTXO data hydrated with token information. If the UTXO does not
* belong to a SLP transaction, it will return an isValid property set to
* false. If the UTXO is part of an SLP transaction, it will return the UTXO
* object with additional SLP information attached. An isValid property will
* be included. If its value is true, the UTXO is a valid SLP UTXO. If the
* value is null, then SLPDB has not yet processed that txid and validity has
* not been confirmed.
*
* @apiExample Example usage:
* curl -X POST "https://api.fullstack.cash/v3/slp/hydrateUtxos" -H "accept:application/json" -H "Content-Type: application/json" -d '{"utxos":[{"utxos":[{"txid": "d56a2b446d8149c39ca7e06163fe8097168c3604915f631bc58777d669135a56","vout": 3, "value": "6816", "height": 606848, "confirmations": 13, "satoshis": 6816}, {"txid": "d56a2b446d8149c39ca7e06163fe8097168c3604915f631bc58777d669135a56","vout": 2, "value": "546", "height": 606848, "confirmations": 13, "satoshis": 546}]}]}'
*
*
*/
async hydrateUtxos (req, res, next) {
try {
const utxos = req.body.utxos
// Validate inputs
if (!Array.isArray(utxos)) {
res.status(422)
return res.json({
error: 'Input must be an array.'
})
}
if (!utxos.length) {
res.status(422)
return res.json({
error: 'Array should not be empty'
})
}
if (utxos.length > 20) {
res.status(422)
return res.json({
error: 'Array too long, max length is 20'
})
}
if (!utxos[0].utxos) {
res.status(422)
return res.json({
error: 'Each element in array should have a utxos property'
})
}
// Loop through each address and query the UTXOs for that element.
for (let i = 0; i < utxos.length; i++) {
const theseUtxos = utxos[i].utxos
// Get SLP token details.
const details = await _this.bchjs.SLP.Utils.tokenUtxoDetails(theseUtxos)
// console.log('details : ', details)
// Replace the original UTXO data with the hydrated data.
utxos[i].utxos = details
}
res.status(200)
return res.json({ slpUtxos: utxos })
} catch (err) {
wlogger.error('Error in slp.js/hydrateUtxos().', err)
console.error('Error in slp.js/hydrateUtxos().', err)
// Decode the error message.
const { msg, status } = routeUtils.decodeError(err)
if (msg) {
res.status(status)
return res.json({ error: msg })
}
res.status(500)
return res.json({
error: 'Error in hydrateUtxos()'
})
}
}
}
module.exports = Slp
+1 -1
View File
@@ -12,7 +12,7 @@ const blockbook = new Blockbook()
const util = require('util')
util.inspect.defaultOptions = { depth: 1 }
const BCHJS = require('@chris.troutner/bch-js')
const BCHJS = require('@psf/bch-js')
const bchjs = new BCHJS()
// const BCHJS_TESTNET = 'https://testnet.bchjs.cash/v3/'
+1 -1
View File
@@ -16,7 +16,7 @@ const router = express.Router()
const util = require('util')
util.inspect.defaultOptions = { depth: 1 }
const BCHJS = require('@chris.troutner/bch-js')
const BCHJS = require('@psf/bch-js')
const bchjs = new BCHJS()
// Connect the route endpoints to their handler functions.
+1 -1
View File
@@ -10,7 +10,7 @@ const wlogger = require('./winston-logging')
const util = require('util')
util.inspect.defaultOptions = { depth: 1 }
const BCHJS = require('@chris.troutner/bch-js')
const BCHJS = require('@psf/bch-js')
const bchjs = new BCHJS()
// let _this
+86
View File
@@ -906,6 +906,92 @@ describe('#ElectrumX Router', () => {
})
})
describe('#_broadcastTransactionWithElectrum', () => {
it('should return error object for invalid formatted transaction', async () => {
const invalidHex = mockData.txDetails.hex.substring(10)
stubMethodForUnitTests(
electrumxRoute.electrumx,
'request',
new Error('Error: the transaction was rejected by network rules.\n\nTX decode failed\n')
)
const result = await electrumxRoute._broadcastTransactionWithElectrum(invalidHex)
assert.instanceOf(result, Error)
assert.include(result.message, 'TX decode failed')
})
it('should return txid for valid transaction', async function () {
// We cannot send an actual broadcast transaction to mainnet
if (process.env.TEST !== 'unit') return this.skip()
const validHex = mockData.txDetails.hex
stubMethodForUnitTests(
electrumxRoute.electrumx,
'request',
mockData.txDetails.hash
)
const result = await electrumxRoute._broadcastTransactionWithElectrum(validHex)
assert.typeOf(result, 'string')
assert.equal(result, mockData.txDetails.hash)
})
})
describe('#broadcastTransaction', () => {
it('should throw an error for a non-string', async () => {
req.body = 456
stubMethodForUnitTests(
electrumxRoute,
'_broadcastTransactionWithElectrum',
new Error('request body must be a string.')
)
const result = await electrumxRoute.broadcastTransaction(req, res)
expectRouteError(res, result, 'request body must be a string.')
})
it('should throw an error object for invalid formatted transaction', async () => {
req.body.txHex = mockData.txDetails.hex.substring(10)
stubMethodForUnitTests(
electrumxRoute,
'_broadcastTransactionWithElectrum',
new Error('Error: the transaction was rejected by network rules.\n\nTX decode failed\n')
)
const result = await electrumxRoute.broadcastTransaction(req, res)
expectRouteError(res, result, 'TX decode failed')
})
it('should return txid for valid transaction', async function () {
// We cannot send an actual broadcast transaction to mainnet
if (process.env.TEST !== 'unit') return this.skip()
req.body.txHex = mockData.txDetails.hex
stubMethodForUnitTests(
electrumxRoute,
'_broadcastTransactionWithElectrum',
mockData.txDetails.hash
)
const result = await electrumxRoute.broadcastTransaction(req, res)
assert.property(result, 'success')
assert.equal(result.success, true)
assert.property(result, 'txid')
assert.equal(result.txid, mockData.txDetails.hash)
})
})
describe('#_balanceFromElectrumx', () => {
it('should throw error for invalid address', async () => {
try {
+9 -9
View File
@@ -78,11 +78,11 @@ describe('#Encryption Router', () => {
// Mock the Insight URL for unit tests.
if (process.env.TEST === 'unit') {
sandbox
.stub(encryptionRoute.blockbook, 'balanceFromBlockbook')
.resolves(mockData.mockBalance)
.stub(encryptionRoute.bchjs.Electrumx, 'transactions')
.resolves(mockData.mockFulcrumTxHistory)
sandbox
.stub(encryptionRoute.rawTransactions, 'getRawTransactionsFromNode')
.resolves(mockData.mockTxDetails)
.stub(encryptionRoute.bchjs.RawTransactions, 'getRawTransaction')
.resolves(mockData.mockTxDetails2)
}
const result = await encryptionRoute.getPublicKey(req, res)
@@ -105,8 +105,8 @@ describe('#Encryption Router', () => {
// Mock the Insight URL for unit tests.
if (process.env.TEST === 'unit') {
sandbox
.stub(encryptionRoute.blockbook, 'balanceFromBlockbook')
.resolves(mockData.mockNoTxHistory)
.stub(encryptionRoute.bchjs.Electrumx, 'transactions')
.resolves(mockData.mockFulcrumNoTxHistory)
}
const result = await encryptionRoute.getPublicKey(req, res)
@@ -126,10 +126,10 @@ describe('#Encryption Router', () => {
// Mock the Insight URL for unit tests.
if (process.env.TEST === 'unit') {
sandbox
.stub(encryptionRoute.blockbook, 'balanceFromBlockbook')
.resolves(mockData.mockNoSendBalance)
.stub(encryptionRoute.bchjs.Electrumx, 'transactions')
.resolves(mockData.mockFulcrumNoSendBalance)
sandbox
.stub(encryptionRoute.rawTransactions, 'getRawTransactionsFromNode')
.stub(encryptionRoute.bchjs.RawTransactions, 'getRawTransaction')
.resolves(mockData.mockNoSendTx)
}
+34
View File
@@ -0,0 +1,34 @@
/*
Integration tests for the price library.
*/
'use strict'
// const assert = require('chai').assert
// const axios = require('axios')
// Used for debugging.
const util = require('util')
util.inspect.defaultOptions = { depth: 1 }
const Price = require('../../../src/routes/v3/price')
const price = new Price()
const { mockReq, mockRes } = require('../mocks/express-mocks')
describe('#price', () => {
let req, res
beforeEach(() => {
// Mock the req and res objects used by Express routes.
req = mockReq
res = mockRes
})
describe('#getUSD', () => {
it('should get the USD price', async () => {
const result = await price.getUSD(req, res)
console.log(`result: ${util.inspect(result)}`)
})
})
})
+76 -75
View File
@@ -16,9 +16,10 @@ util.inspect.defaultOptions = { depth: 1 }
// const SERVER = `http://192.168.0.36:12400/v3/`
const SERVER = 'http://localhost:3000/v3/'
// const SERVER = 'https://api.fullstack.cash/v3/'
const TEST_JWT =
'eyJhbGciOiJFUzI1NiIsInR5cCI6IkpXVCJ9.eyJpZCI6IjVlM2EwNDE1ZWIyOWE5NjJkYTI3MDhiNCIsImFwaUxldmVsIjowLCJyYXRlTGltaXQiOjEwLCJpYXQiOjE1ODA4NjA0NjcsImV4cCI6MTU4MzQ1MjQ2N30.fuY5S-YrF0J11h5uyMjPe7wiVkYRnIyXi4dL9-V-C6pLJm33p0dSq_pSheVVWw78n5kAvL_9kFHngbnmQiOJYQ'
'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpZCI6IjVlODhhY2JmMDIyMWMxMDAxMmFkOTNmZiIsImVtYWlsIjoiY2hyaXMudHJvdXRuZXJAZ21haWwuY29tIiwiYXBpTGV2ZWwiOjQwLCJyYXRlTGltaXQiOjMsImlhdCI6MTYwMDYyODk1MSwiZXhwIjoxNjAzMjIwOTUxfQ.JPXDJQsxJFtCGZjHOd-hRfJuY41Ef_FQ4ET06CtYdNk'
describe('#rate limits', () => {
it('should get control/getNetworkInfo() with no auth', async () => {
@@ -35,7 +36,7 @@ describe('#rate limits', () => {
assert.hasAnyKeys(result.data, ['version'])
})
it('should trigger rate-limit handler if rate limits exceeds 30 request per minute', async () => {
it('should trigger rate-limit handler if rate limits exceeds 5 request per minute', async () => {
try {
// Actual rate limit is 60 per minute X 4 nodes = 240 rpm.
const options = {
@@ -44,7 +45,7 @@ describe('#rate limits', () => {
}
const promises = []
for (let i = 0; i < 30; i++) {
for (let i = 0; i < 5; i++) {
const promise = axios(options)
promises.push(promise)
}
@@ -60,79 +61,79 @@ describe('#rate limits', () => {
}
})
it('should not trigger rate-limit handler if correct pro-tier password is used', async () => {
try {
const username = 'BITBOX'
// it('should not trigger rate-limit handler if correct pro-tier password is used', async () => {
// try {
// const username = 'BITBOX'
//
// // Pro-tier is accessed by using the right password.
// const password = 'BITBOX'
// // const password = "something"
//
// const combined = `${username}:${password}`
// const base64Credential = Buffer.from(combined).toString('base64')
// const readyCredential = `Basic ${base64Credential}`
//
// const options = {
// method: 'GET',
// url: `${SERVER}control/getNetworkInfo`,
// headers: { Authorization: readyCredential }
// }
//
// const promises = []
// for (let i = 0; i < 30; i++) {
// const promise = axios(options)
// promises.push(promise)
// }
//
// await Promise.all(promises)
//
// assert.equal(true, true, 'Not throwing an error is a pass!')
// } catch (err) {
// // console.log(`err.response: ${util.inspect(err.response)}`)
//
// assert.equal(
// true,
// false,
// 'This error handler should not have been triggered. Is the password correct?'
// )
// }
// })
// Pro-tier is accessed by using the right password.
const password = 'BITBOX'
// const password = "something"
const combined = `${username}:${password}`
const base64Credential = Buffer.from(combined).toString('base64')
const readyCredential = `Basic ${base64Credential}`
const options = {
method: 'GET',
url: `${SERVER}control/getNetworkInfo`,
headers: { Authorization: readyCredential }
}
const promises = []
for (let i = 0; i < 30; i++) {
const promise = axios(options)
promises.push(promise)
}
await Promise.all(promises)
assert.equal(true, true, 'Not throwing an error is a pass!')
} catch (err) {
// console.log(`err.response: ${util.inspect(err.response)}`)
assert.equal(
true,
false,
'This error handler should not have been triggered. Is the password correct?'
)
}
})
it('should trigger rate-limit handler if rate limits exceeds pro-tier limit', async () => {
try {
const username = 'BITBOX'
// Pro-tier is accessed by using the right password.
const password = 'BITBOX'
// const password = "something"
const combined = `${username}:${password}`
const base64Credential = Buffer.from(combined).toString('base64')
const readyCredential = `Basic ${base64Credential}`
// Actual rate limit is 60 per minute X 4 nodes = 240 rpm.
const options = {
method: 'GET',
url: `${SERVER}control/getNetworkInfo`,
headers: { Authorization: readyCredential }
}
const promises = []
for (let i = 0; i < 80; i++) {
const promise = axios(options)
promises.push(promise)
}
await Promise.all(promises)
assert.equal(true, false, 'Unexpected result!')
} catch (err) {
// console.log(`err.response: ${util.inspect(err.response)}`)
assert.equal(err.response.status, 429)
assert.include(err.response.data.error, 'Too many requests')
}
})
// it('should trigger rate-limit handler if rate limits exceeds pro-tier limit', async () => {
// try {
// const username = 'BITBOX'
//
// // Pro-tier is accessed by using the right password.
// const password = 'BITBOX'
// // const password = "something"
//
// const combined = `${username}:${password}`
// const base64Credential = Buffer.from(combined).toString('base64')
// const readyCredential = `Basic ${base64Credential}`
//
// // Actual rate limit is 60 per minute X 4 nodes = 240 rpm.
// const options = {
// method: 'GET',
// url: `${SERVER}control/getNetworkInfo`,
// headers: { Authorization: readyCredential }
// }
//
// const promises = []
// for (let i = 0; i < 80; i++) {
// const promise = axios(options)
// promises.push(promise)
// }
//
// await Promise.all(promises)
//
// assert.equal(true, false, 'Unexpected result!')
// } catch (err) {
// // console.log(`err.response: ${util.inspect(err.response)}`)
//
// assert.equal(err.response.status, 429)
// assert.include(err.response.data.error, 'Too many requests')
// }
// })
it('should unlock pro-tier for a valid JWT token', async () => {
try {
+138 -1
View File
@@ -22,6 +22,9 @@ if (!process.env.SLPDB_URL) {
const SLP = require('../../../src/routes/v3/slp')
const slp = new SLP()
const BCHJS = require('@psf/bch-js')
const bchjs = new BCHJS()
const { mockReq, mockRes } = require('../mocks/express-mocks')
describe('#slp', () => {
@@ -102,7 +105,7 @@ describe('#slp', () => {
})
})
describe('generateSendOpReturn', () => {
describe('#generateSendOpReturn', () => {
it('should return OP_RETURN script', async () => {
req.body.tokenUtxos = [
{
@@ -121,4 +124,138 @@ describe('#slp', () => {
assert.isNumber(result.outputs)
})
})
describe('#hydrateUtxos', () => {
it('should return utxo details', async () => {
const utxos = [
{
utxos: [
{
txid:
'd56a2b446d8149c39ca7e06163fe8097168c3604915f631bc58777d669135a56',
vout: 3,
value: '6816',
height: 606848,
confirmations: 13,
satoshis: 6816
},
{
txid:
'd56a2b446d8149c39ca7e06163fe8097168c3604915f631bc58777d669135a56',
vout: 2,
value: '546',
height: 606848,
confirmations: 13,
satoshis: 546
}
]
}
]
req.body.utxos = utxos
const result = await slp.hydrateUtxos(req, res)
// console.log(`result: ${JSON.stringify(result, null, 2)}`)
// Test the general structure of the output.
assert.isArray(result.slpUtxos)
assert.equal(result.slpUtxos.length, 1)
assert.equal(result.slpUtxos[0].utxos.length, 2)
// Test the non-slp UTXO.
assert.property(result.slpUtxos[0].utxos[0], 'txid')
assert.property(result.slpUtxos[0].utxos[0], 'vout')
assert.property(result.slpUtxos[0].utxos[0], 'value')
assert.property(result.slpUtxos[0].utxos[0], 'height')
assert.property(result.slpUtxos[0].utxos[0], 'confirmations')
assert.property(result.slpUtxos[0].utxos[0], 'satoshis')
assert.property(result.slpUtxos[0].utxos[0], 'isValid')
assert.equal(result.slpUtxos[0].utxos[0].isValid, false)
// Test the slp UTXO.
assert.property(result.slpUtxos[0].utxos[1], 'txid')
assert.property(result.slpUtxos[0].utxos[1], 'vout')
assert.property(result.slpUtxos[0].utxos[1], 'value')
assert.property(result.slpUtxos[0].utxos[1], 'height')
assert.property(result.slpUtxos[0].utxos[1], 'confirmations')
assert.property(result.slpUtxos[0].utxos[1], 'satoshis')
assert.property(result.slpUtxos[0].utxos[1], 'isValid')
assert.equal(result.slpUtxos[0].utxos[1].isValid, true)
assert.property(result.slpUtxos[0].utxos[1], 'transactionType')
assert.property(result.slpUtxos[0].utxos[1], 'tokenId')
assert.property(result.slpUtxos[0].utxos[1], 'tokenTicker')
assert.property(result.slpUtxos[0].utxos[1], 'tokenName')
assert.property(result.slpUtxos[0].utxos[1], 'tokenDocumentUrl')
assert.property(result.slpUtxos[0].utxos[1], 'tokenDocumentHash')
assert.property(result.slpUtxos[0].utxos[1], 'decimals')
assert.property(result.slpUtxos[0].utxos[1], 'tokenType')
assert.property(result.slpUtxos[0].utxos[1], 'tokenQty')
})
it('should process data directly from Electrumx', async () => {
const addrs = [
'bitcoincash:qq6mvsm7l92d77zpymmltvaw09p5uzghyuyx7spygg',
'bitcoincash:qpjdrs8qruzh8xvusdfmutjx62awcepnhyperm3g89',
'bitcoincash:qzygn28zpgeemnptkn26xzyuzzfu9l8f9vfvq7kptk'
]
const utxos = await bchjs.Electrumx.utxo(addrs)
// console.log(`utxos: ${JSON.stringify(utxos, null, 2)}`)
req.body.utxos = utxos.utxos
const result = await slp.hydrateUtxos(req, res)
// console.log(`result: ${JSON.stringify(result, null, 2)}`)
// Test the general structure of the output.
assert.isArray(result.slpUtxos)
assert.equal(result.slpUtxos.length, 3)
assert.equal(result.slpUtxos[0].utxos.length, 1)
assert.equal(result.slpUtxos[1].utxos.length, 1)
assert.equal(result.slpUtxos[2].utxos.length, 2)
})
})
describe('#validate2Single', () => {
it('should invalidate a known invalid TXID', async () => {
const txid =
'f7e5199ef6669ad4d078093b3ad56e355b6ab84567e59ad0f08a5ad0244f783a'
req.params.txid = txid
const result = await slp.validate2Single(req, res)
// console.log(`result: ${JSON.stringify(result, null, 2)}`)
assert.equal(result.txid, txid)
assert.equal(result.isValid, false)
})
it('should validate a known valid TXID', async () => {
const txid =
'3a4b628cbcc183ab376d44ce5252325f042268307ffa4a53443e92b6d24fb488'
req.params.txid = txid
const result = await slp.validate2Single(req, res)
// console.log(`result: ${JSON.stringify(result, null, 2)}`)
assert.equal(result.txid, txid)
assert.equal(result.isValid, true)
})
// CT 10-11-2020: This test is valid, but because of the cacheing built
// into slp-validate, it will not consistently pass or fail. To manually
// Run this test, re-start slp-api or find a token txid with a long DAG.
// it('should cancel if validation takes too long', async () => {
// const txid =
// 'eacb1085dfa296fef6d4ae2c0f4529a1bef096dd2325bdcc6dcb5241b3bdb579'
//
// req.params.txid = txid
// const result = await slp.validate2Single(req, res)
// // console.log(`result: ${JSON.stringify(result, null, 2)}`)
//
// assert.isAbove(res.statusCode, 499, 'HTTP status code 503 expected.')
// assert.include(
// result.error,
// 'Could not communicate with full node',
// 'Error message expected'
// )
// })
})
})
+139 -136
View File
@@ -2,126 +2,6 @@
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',
@@ -133,8 +13,10 @@ const mockNoSendTx = {
txid: '681cc1d392975a7e78cbd490b8a940d7eb029c554f2cee332e3a7119d77fb189',
vout: 1,
scriptSig: {
asm: '30450221008a7c86ce3a9f5765573440143a00631bb5afa9a58b24674a7fa794bb11ffffc30220397dd4c672c3e0dbe0c4eb6390b5727c5fdc3a42cf3ae275ac13b44d27bb234f[ALL|FORKID] 02bc9fc26ae2bd9a9a14e9baeda4b4fdd95a00bcbf375732c647488b3ce82a2fd7',
hex: '4830450221008a7c86ce3a9f5765573440143a00631bb5afa9a58b24674a7fa794bb11ffffc30220397dd4c672c3e0dbe0c4eb6390b5727c5fdc3a42cf3ae275ac13b44d27bb234f412102bc9fc26ae2bd9a9a14e9baeda4b4fdd95a00bcbf375732c647488b3ce82a2fd7'
asm:
'30450221008a7c86ce3a9f5765573440143a00631bb5afa9a58b24674a7fa794bb11ffffc30220397dd4c672c3e0dbe0c4eb6390b5727c5fdc3a42cf3ae275ac13b44d27bb234f[ALL|FORKID] 02bc9fc26ae2bd9a9a14e9baeda4b4fdd95a00bcbf375732c647488b3ce82a2fd7',
hex:
'4830450221008a7c86ce3a9f5765573440143a00631bb5afa9a58b24674a7fa794bb11ffffc30220397dd4c672c3e0dbe0c4eb6390b5727c5fdc3a42cf3ae275ac13b44d27bb234f412102bc9fc26ae2bd9a9a14e9baeda4b4fdd95a00bcbf375732c647488b3ce82a2fd7'
},
sequence: 4294967295
}
@@ -144,36 +26,157 @@ const mockNoSendTx = {
value: 0.000006,
n: 0,
scriptPubKey: {
asm: 'OP_DUP OP_HASH160 3c79ba54317c4be9cbfa5aaf357f74b97f3d9696 OP_EQUALVERIFY OP_CHECKSIG',
asm:
'OP_DUP OP_HASH160 3c79ba54317c4be9cbfa5aaf357f74b97f3d9696 OP_EQUALVERIFY OP_CHECKSIG',
hex: '76a9143c79ba54317c4be9cbfa5aaf357f74b97f3d969688ac',
reqSigs: 1,
type: 'pubkeyhash',
addresses: [
'bitcoincash:qq78nwj5x97yh6wtlfd27dtlwjuh70vkjc59h8tgtg'
]
addresses: ['bitcoincash:qq78nwj5x97yh6wtlfd27dtlwjuh70vkjc59h8tgtg']
}
},
{
value: 0.00003664,
n: 1,
scriptPubKey: {
asm: 'OP_DUP OP_HASH160 58502a73888e0a386fc69b44850af4b2a86ac9a6 OP_EQUALVERIFY OP_CHECKSIG',
asm:
'OP_DUP OP_HASH160 58502a73888e0a386fc69b44850af4b2a86ac9a6 OP_EQUALVERIFY OP_CHECKSIG',
hex: '76a91458502a73888e0a386fc69b44850af4b2a86ac9a688ac',
reqSigs: 1,
type: 'pubkeyhash',
addresses: [
'bitcoincash:qpv9q2nn3z8q5wr0c6d5fpg27je2s6kf5cavp4ney0'
]
addresses: ['bitcoincash:qpv9q2nn3z8q5wr0c6d5fpg27je2s6kf5cavp4ney0']
}
}
],
hex: '020000000189b17fd719713a2e33ee2c4f559c02ebd740a9b890d4cb787e5a9792d3c11c68010000006b4830450221008a7c86ce3a9f5765573440143a00631bb5afa9a58b24674a7fa794bb11ffffc30220397dd4c672c3e0dbe0c4eb6390b5727c5fdc3a42cf3ae275ac13b44d27bb234f412102bc9fc26ae2bd9a9a14e9baeda4b4fdd95a00bcbf375732c647488b3ce82a2fd7ffffffff0258020000000000001976a9143c79ba54317c4be9cbfa5aaf357f74b97f3d969688ac500e0000000000001976a91458502a73888e0a386fc69b44850af4b2a86ac9a688ac00000000'
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 = {
mockBalance,
mockTxDetails,
mockNoTxHistory,
mockNoSendBalance,
mockNoSendTx
mockNoSendTx,
mockFulcrumTxHistory,
mockTxDetails2,
mockFulcrumNoTxHistory,
mockFulcrumNoSendBalance
}
+221
View File
@@ -0,0 +1,221 @@
/*
This library contains mocking data for running unit tests on the address route.
*/
'use strict'
const mockCoinbaseFeed = {
data: {
currency: 'BCH',
rates: {
AED: '918.11634',
AFN: '19208.6584998',
ALGO: '823.28722002635056355',
ALL: '26365.4673517',
AMD: '120379.9761886',
ANG: '448.6632494',
AOA: '162813.68075',
ARS: '19392.84240565',
ATOM: '45.3670932026499700525',
AUD: '354.4385981',
AWG: '449.91',
AZN: '425.539875',
BAL: '18.824215193276753072',
BAM: '415.45514235',
BAND: '40.7768732564399590085',
BAT: '1168.9711697428450012',
BBD: '499.9',
BCH: '1.0',
BDT: '21194.1523216',
BGN: '415.36691',
BHD: '94.2501462',
BIF: '483670.78174295',
BMD: '249.95',
BND: '339.227141',
BOB: '1725.8732563',
BRL: '1401.494645',
BSD: '249.95',
BSV: '1.567361207606150942625',
BTC: '0.021275',
BTN: '18323.91073475',
BWP: '2859.81217315',
BYN: '640.5913561',
BYR: '6405913.561',
BZD: '503.8107177',
CAD: '329.6765515',
CDF: '490377.5463717',
CGLD: '121.6805004503079241715',
CHF: '227.5939721',
CLF: '7.13432285',
CLP: '196860.63474705',
CNH: '1669.50978125',
CNY: '1670.21589',
COMP: '2.429765723728978241',
COP: '961722.29431455',
CRC: '150790.4338804',
CUC: '250.01273745',
CVE: '23620.275',
CZK: '5790.09175',
DAI: '247.842348666936355635',
DASH: '3.338230383973288798',
DJF: '44495.8985401',
DKK: '1580.608815',
DOP: '14597.4179324',
DZD: '32214.830745',
EEK: '3652.69206545',
EGP: '3924.839875',
EOS: '96.8423091824874092665',
ERN: '3749.25274945',
ETB: '9313.6508972',
ETC: '45.04821122825989111',
ETH: '0.658777328255340453815',
EUR: '212.33',
FJD: '534.9054975',
FKP: '193.01413945',
GBP: '193.115',
GEL: '807.3385',
GGP: '193.01413945',
GHS: '1453.70845015',
GIP: '193.01413945',
GMD: '12934.9125',
GNF: '2447970.79115335',
GTQ: '1944.576007',
GYD: '52260.2868518',
HKD: '1937.122498',
HNL: '6129.328889',
HRK: '1610.802775',
HTG: '15746.55080985',
HUF: '77512.6058777',
IDR: '3675351.03275',
ILS: '846.1232415',
IMP: '193.01413945',
INR: '18345.655135',
IQD: '298009.02802165',
ISK: '34745.5495',
JEP: '193.01413945',
JMD: '36409.6696094',
JOD: '177.21455',
JPY: '26360.601825',
KES: '27184.562',
KGS: '20308.0755724',
KHR: '1025880.7533059',
KMF: '105029.03124175',
KNC: '274.68542227594924615',
KRW: '284915.23080495',
KWD: '76.4532063',
KYD: '208.2943328',
KZT: '107014.42658325',
LAK: '2310183.71158185',
LBP: '378370.54450325',
LINK: '22.894634759817007489',
LKR: '46090.95971405',
LRC: '1457.8594342373869446',
LRD: '48852.72175115',
LSL: '4122.3008749',
LTC: '5.2062070401999589123',
LTL: '806.05000775',
LVL: '164.03243695',
LYD: '341.5926678',
MAD: '2298.47346335',
MDL: '4228.5806147',
MGA: '982179.7557538',
MKD: '13087.73767885',
MKR: '0.43764476902003812305',
MMK: '322679.7621378',
MNT: '709349.8756452',
MOP: '1995.2583685',
MRO: '89232.15',
MTL: '170.9003131',
MUR: '9974.21650765',
MVR: '3849.23',
MWK: '188665.67006785',
MXN: '5298.265135',
MYR: '1036.517655',
MZN: '18229.3548997',
NAD: '4134.173',
NGN: '95481.03472305',
NIO: '8698.6554209',
NMR: '8.624556609112112958',
NOK: '2334.203066',
NPR: '29318.1806909',
NZD: '379.2351378',
OMG: '74.464123456422320125',
OMR: '96.23549905',
OXT: '1033.27821413807369045',
PAB: '249.95',
PEN: '896.6071427',
PGK: '874.90073485',
PHP: '12136.85639315',
PKR: '40613.9575836',
PLN: '971.640633',
PYG: '1756126.21474795',
QAR: '910.1304375',
REN: '769.6689761354887733',
REP: '18.2912550311013566725',
REPV2: '18.1582473904586151205',
RON: '1035.54285',
RSD: '24943.76025',
RUB: '19402.093805',
RWF: '244237.3512583',
SAR: '937.65668115',
SBD: '2019.805958',
SCR: '4576.6749819',
SEK: '2207.698372',
SGD: '339.57582125',
SHP: '193.01413945',
SLL: '2490751.74625075',
SOS: '145044.39506805',
SRD: '3537.7923',
SSP: '32558.487',
STD: '5250063.0293496',
SVC: '2187.04525345',
SZL: '4118.75383445',
THB: '7800.314625',
TJS: '2579.48624955',
TMT: '874.825',
TND: '688.2998125',
TOP: '579.953986',
TRY: '1969.70598',
TTD: '1696.3476626',
TWD: '7179.16412995',
TZS: '579879.84308155',
UAH: '7090.3476468',
UGX: '935325.80066935',
UMA: '29.276720351390922145',
UNI: '78.76161966283282792',
USD: '249.95',
USDC: '249.95',
UYU: '10724.30446005',
UZS: '2592145.88136715',
VEF: '62109486.17813795',
VES: '113073216.80555545',
VND: '5792476.0820382',
VUV: '28582.2184128',
WST: '655.5403657',
XAF: '139318.05656485',
XAG: '10.233577875',
XAU: '0.1313462255',
XCD: '675.5023725',
XDR: '177.0940741',
XLM: '2935.3219224332811736',
XOF: '139318.05656485',
XPD: '0.1065011955',
XPF: '25344.75028595',
XPT: '0.290981792',
XRP: '1016.6768354687817832',
XTZ: '114.010080507218287957',
YER: '62574.97275195',
YFI: '0.0181099566723181033515',
ZAR: '4130.57372',
ZEC: '3.88966697790227161905',
ZMK: '1313006.15998725',
ZMW: '5048.6210738',
ZRX: '642.804422350408735165',
ZWL: '80483.9'
}
}
}
module.exports = {
mockCoinbaseFeed
}
+16 -8
View File
@@ -352,7 +352,8 @@ const mockTxHistory = [
e: {
h: '7bdd586ebd1e5f3dfd5295ab6e896c48b25855ab77a72c035ce1e7aacc965c2d',
i: 1,
s: 'RzBEAiAsu5o9EnxgZChjxKygxhGLhlfxJJbQfd4PP/vp2od3fAIgPpMqg+Sp4YpiInQZ6weJwELq8LEIrHXJpueBz7yF5gRBIQLsyL3809mRphc/+tPAcTQO5bi0kqZRFKpKIHXE3qN8Rw==',
s:
'RzBEAiAsu5o9EnxgZChjxKygxhGLhlfxJJbQfd4PP/vp2od3fAIgPpMqg+Sp4YpiInQZ6weJwELq8LEIrHXJpueBz7yF5gRBIQLsyL3809mRphc/+tPAcTQO5bi0kqZRFKpKIHXE3qN8Rw==',
a: 'simpleledger:qrrrpqmdkggpnw0czwg0jgcjd7yhu25jy5zxh2gqdq'
}
},
@@ -361,7 +362,8 @@ const mockTxHistory = [
e: {
h: 'df49feff24dc34a10a44a0cbd7c908d964801ec7218512c84574fbce698535f0',
i: 3,
s: 'SDBFAiEAhW3zbKTlPrOXD2E2oEcNof6vCMPGYIg8vOVSE9c8IakCIFLg/gxydG9eL8HMAzkScGElKWJRnfhVMpMHU3evNcrZQSEDRS7F+pSC8OxSldsT4FctJLZBU7f2+FDiE05ae1xqtN0=',
s:
'SDBFAiEAhW3zbKTlPrOXD2E2oEcNof6vCMPGYIg8vOVSE9c8IakCIFLg/gxydG9eL8HMAzkScGElKWJRnfhVMpMHU3evNcrZQSEDRS7F+pSC8OxSldsT4FctJLZBU7f2+FDiE05ae1xqtN0=',
a: 'simpleledger:qpkpeqfslejw5pptzcy25h2jxhsc9k0vts43n26up0'
}
}
@@ -371,7 +373,8 @@ const mockTxHistory = [
e: {
v: 0,
i: 0,
s: 'agRTTFAAAQEEU0VORCA46XxdfTWFosvz+VgMgsozmF+csIRdTcziIMtwn5U4sAgAAAAAAJiWgAgAAAAAJHfU+g=='
s:
'agRTTFAAAQEEU0VORCA46XxdfTWFosvz+VgMgsozmF+csIRdTcziIMtwn5U4sAgAAAAAAJiWgAgAAAAAJHfU+g=='
}
},
{
@@ -402,7 +405,8 @@ const mockTxHistory = [
slp: {
detail: {
decimals: 8,
tokenIdHex: '38e97c5d7d3585a2cbf3f9580c82ca33985f9cb0845d4dcce220cb709f9538b0',
tokenIdHex:
'38e97c5d7d3585a2cbf3f9580c82ca33985f9cb0845d4dcce220cb709f9538b0',
transactionType: 'SEND',
versionType: 1,
documentUri: 'psfoundation.cash',
@@ -439,7 +443,8 @@ const mockTxHistory = [
e: {
h: 'e4e1e1f6b502cbd42f69b919634cea3e37fc8595e6ca800e0a7d8f6dcdfb249e',
i: 3,
s: 'SDBFAiEAjCGRmU28x24LMTwg5XqC+fLf3zGhTYCsSWpmF8Eshq0CIAJhxAKMbwKWC4ASmjWzplpno2ch+hGNUD6HNWOdbd4SQSECeRsZo5Fl29g0A9bfJo1E/WIdowWBsLblyxWnEB7ViFE=',
s:
'SDBFAiEAjCGRmU28x24LMTwg5XqC+fLf3zGhTYCsSWpmF8Eshq0CIAJhxAKMbwKWC4ASmjWzplpno2ch+hGNUD6HNWOdbd4SQSECeRsZo5Fl29g0A9bfJo1E/WIdowWBsLblyxWnEB7ViFE=',
a: 'simpleledger:qpvsg9vl9a5mlf37a7n3yce6pktdctn73qznkmw3s7'
}
},
@@ -448,7 +453,8 @@ const mockTxHistory = [
e: {
h: '438420345dd8b7fb4ea74aaf2e3090f44899bf3c71662243946a441de6dae720',
i: 2,
s: 'SDBFAiEAhmLYL4QY3mvZo3/i0XG9PJAYMruw3MaGOLJ3Z4si7hwCIAaaGwRnj/cZK3L/KKFBoMtItpGRZ10GowNN//vgn6PKQSECeRsZo5Fl29g0A9bfJo1E/WIdowWBsLblyxWnEB7ViFE=',
s:
'SDBFAiEAhmLYL4QY3mvZo3/i0XG9PJAYMruw3MaGOLJ3Z4si7hwCIAaaGwRnj/cZK3L/KKFBoMtItpGRZ10GowNN//vgn6PKQSECeRsZo5Fl29g0A9bfJo1E/WIdowWBsLblyxWnEB7ViFE=',
a: 'simpleledger:qpvsg9vl9a5mlf37a7n3yce6pktdctn73qznkmw3s7'
}
}
@@ -458,7 +464,8 @@ const mockTxHistory = [
e: {
v: 0,
i: 0,
s: 'agRTTFAAAQEEU0VORCCk+1wtoaoGTiUBikP5FlBABx2emEuhkMIip/WQU6+EsggAAAAAAA9CQAgAAAAXQnHEwA=='
s:
'agRTTFAAAQEEU0VORCCk+1wtoaoGTiUBikP5FlBABx2emEuhkMIip/WQU6+EsggAAAAAAA9CQAgAAAAXQnHEwA=='
}
},
{
@@ -489,7 +496,8 @@ const mockTxHistory = [
slp: {
detail: {
decimals: 2,
tokenIdHex: 'a4fb5c2da1aa064e25018a43f9165040071d9e984ba190c222a7f59053af84b2',
tokenIdHex:
'a4fb5c2da1aa064e25018a43f9165040071d9e984ba190c222a7f59053af84b2',
transactionType: 'SEND',
versionType: 1,
documentUri: 'troutsblog.com',
+214
View File
@@ -0,0 +1,214 @@
/*
TESTS FOR THE PRICE.JS LIBRARY
This test file uses the environment variable TEST to switch between unit
and integration tests. By default, TEST is set to 'unit'. Set this variable
to 'integration' to run the tests against BCH mainnet.
*/
'use strict'
const chai = require('chai')
const assert = chai.assert
const sinon = require('sinon')
const Price = require('../../src/routes/v3/price')
let uut
// Mocking data.
const { mockReq, mockRes } = require('./mocks/express-mocks')
const mockData = require('./mocks/price-mock')
// Used for debugging.
const util = require('util')
util.inspect.defaultOptions = { depth: 1 }
describe('#PriceRouter', () => {
let req, res
let sandbox
before(() => {
// Set default environment variables for unit tests.
if (!process.env.TEST) process.env.TEST = 'unit'
})
// Setup the mocks before each test.
beforeEach(() => {
// Mock the req and res objects used by Express routes.
req = mockReq
res = mockRes
// Explicitly reset the parmas and body.
req.params = {}
req.body = {}
req.query = {}
sandbox = sinon.createSandbox()
uut = new Price()
})
afterEach(() => {
// Restore Sandbox
sandbox.restore()
})
after(() => {})
describe('#root', async () => {
// root route handler.
// const root = controlRoute.testableComponents.root
it('should respond to GET for base route', async () => {
const result = uut.root(req, res)
// console.log(`result: ${util.inspect(result)}`)
assert.equal(result.status, 'price', 'Returns static string')
})
})
describe('#getUSD', () => {
// const getNetworkInfo = controlRoute.testableComponents.getNetworkInfo
it('should throw 500 when network issues', async () => {
uut.priceUrl = 'http://fakeurl/api/'
await uut.getUSD(req, res)
// console.log(`result: ${util.inspect(result)}`)
assert.isAbove(
res.statusCode,
499,
'HTTP status code 500 or greater expected.'
)
// console.log(res)
assert.include(
res.output.error,
'Network error: Could not communicate with full node or other external service'
)
})
it('returns proper error when downstream service stalls', async () => {
// Mock the timeout error.
sandbox.stub(uut.axios, 'request').throws({ code: 'ECONNABORTED' })
const result = await uut.getUSD(req, res)
// console.log(`result: ${JSON.stringify(result, null, 2)}`)
assert.isAbove(res.statusCode, 499, 'HTTP status code 503 expected.')
assert.include(
result.error,
'Could not communicate with full node',
'Error message expected'
)
})
it('returns proper error when downstream service is down', async () => {
// Mock the timeout error.
sandbox.stub(uut.axios, 'request').throws({ code: 'ECONNREFUSED' })
const result = await uut.getUSD(req, res)
// console.log(`result: ${JSON.stringify(result, null, 2)}`)
assert.isAbove(res.statusCode, 499, 'HTTP status code 503 expected.')
assert.include(
result.error,
'Could not communicate with full node',
'Error message expected'
)
})
it('should get the USD price of BCH', async () => {
// Mock the RPC call for unit tests.
if (process.env.TEST === 'unit') {
sandbox
.stub(uut.axios, 'request')
.resolves({ data: mockData.mockCoinbaseFeed })
}
const result = await uut.getUSD(req, res)
// console.log(`result: ${util.inspect(result)}`)
assert.isNumber(result.usd)
})
})
describe('#getBCHRate', () => {
it('should throw 500 when network issues', async () => {
uut.priceUrl = 'http://fakeurl/api/'
await uut.getBCHRate(req, res)
// console.log(`result: ${util.inspect(result)}`)
assert.isAbove(
res.statusCode,
499,
'HTTP status code 500 or greater expected.'
)
// console.log(res)
assert.include(
res.output.error,
'Network error: Could not communicate with full node or other external service'
)
})
it('returns proper error when downstream service stalls', async () => {
// Mock the timeout error.
sandbox.stub(uut.axios, 'request').throws({ code: 'ECONNABORTED' })
const result = await uut.getBCHRate(req, res)
// console.log(`result: ${JSON.stringify(result, null, 2)}`)
assert.isAbove(res.statusCode, 499, 'HTTP status code 503 expected.')
assert.include(
result.error,
'Could not communicate with full node',
'Error message expected'
)
})
it('returns proper error when downstream service is down', async () => {
// Mock the timeout error.
sandbox.stub(uut.axios, 'request').throws({ code: 'ECONNREFUSED' })
const result = await uut.getBCHRate(req, res)
// console.log(`result: ${JSON.stringify(result, null, 2)}`)
assert.isAbove(res.statusCode, 499, 'HTTP status code 503 expected.')
assert.include(
result.error,
'Could not communicate with full node',
'Error message expected'
)
})
it('should get several rates for BCH', async () => {
// Mock the RPC call for unit tests.
if (process.env.TEST === 'unit') {
sandbox
.stub(uut.axios, 'request')
.resolves({ data: mockData.mockCoinbaseFeed })
}
const result = await uut.getBCHRate(req, res)
// console.log(`result: ${util.inspect(result)}`)
// assert.isNumber(result)
assert.property(result, 'USD')
assert.property(result, 'CAD')
})
})
describe('#errorHandler', () => {
it('should handle unexpected errors', () => {
sandbox.stub(uut.routeUtils, 'decodeError').returns({ msg: false })
const result = uut.errorHandler(new Error('test error'), res)
// console.log('result: ', result)
assert.isAbove(res.statusCode, 499, 'HTTP status code 503 expected.')
assert.property(result, 'error')
})
})
})
+37 -37
View File
@@ -105,14 +105,14 @@ describe('#route-ratelimits & jwt-auth', () => {
})
describe('#calcPoints', () => {
it('should return 30 points for anonymous user', () => {
it('should return 50 points for anonymous user', () => {
const result = rateLimits.calcPoints()
// console.log(`result: ${result}`)
assert.equal(result, 30)
assert.equal(result, 50)
})
it('should return 10 points for free tier requesting full node access', () => {
it('should return 50 points for free tier requesting full node access', () => {
const jwtInfo = {
apiLevel: 10,
resource: 'blockchain',
@@ -120,10 +120,10 @@ describe('#route-ratelimits & jwt-auth', () => {
}
const result = rateLimits.calcPoints(jwtInfo)
assert.equal(result, 10)
assert.equal(result, 50)
})
it('should return 10 points for free tier requesting indexer access', () => {
it('should return 50 points for free tier requesting indexer access', () => {
const jwtInfo = {
apiLevel: 10,
resource: 'blockbook',
@@ -131,10 +131,10 @@ describe('#route-ratelimits & jwt-auth', () => {
}
const result = rateLimits.calcPoints(jwtInfo)
assert.equal(result, 10)
assert.equal(result, 50)
})
it('should return 10 points for free tier requesting SLPDB access', () => {
it('should return 50 points for free tier requesting SLPDB access', () => {
const jwtInfo = {
apiLevel: 10,
resource: 'slp',
@@ -142,10 +142,10 @@ describe('#route-ratelimits & jwt-auth', () => {
}
const result = rateLimits.calcPoints(jwtInfo)
assert.equal(result, 10)
assert.equal(result, 50)
})
it('should return 1 point for full node tier requesting full node access', () => {
it('should return 10 points for full node tier requesting full node access', () => {
const jwtInfo = {
apiLevel: 20,
resource: 'blockchain',
@@ -153,10 +153,10 @@ describe('#route-ratelimits & jwt-auth', () => {
}
const result = rateLimits.calcPoints(jwtInfo)
assert.equal(result, 1)
assert.equal(result, 10)
})
it('should return 10 points for full-node tier requesting indexer access', () => {
it('should return 50 points for full-node tier requesting indexer access', () => {
const jwtInfo = {
apiLevel: 20,
resource: 'blockbook',
@@ -164,10 +164,10 @@ describe('#route-ratelimits & jwt-auth', () => {
}
const result = rateLimits.calcPoints(jwtInfo)
assert.equal(result, 10)
assert.equal(result, 50)
})
it('should return 10 points for full node tier requesting SLPDB access', () => {
it('should return 50 points for full node tier requesting SLPDB access', () => {
const jwtInfo = {
apiLevel: 20,
resource: 'slp',
@@ -175,10 +175,10 @@ describe('#route-ratelimits & jwt-auth', () => {
}
const result = rateLimits.calcPoints(jwtInfo)
assert.equal(result, 10)
assert.equal(result, 50)
})
it('should return 1 point for indexer tier requesting full node access', () => {
it('should return 10 point for indexer tier requesting full node access', () => {
const jwtInfo = {
apiLevel: 30,
resource: 'blockchain',
@@ -186,32 +186,32 @@ describe('#route-ratelimits & jwt-auth', () => {
}
const result = rateLimits.calcPoints(jwtInfo)
assert.equal(result, 1)
assert.equal(result, 10)
})
it('should return 1 points for indexer tier requesting indexer access', () => {
it('should return 10 points for indexer tier requesting indexer access', () => {
const jwtInfo = {
apiLevel: 30,
resource: 'blockbook',
id: '5e3a0415eb29a962da2708b4'
}
const result = rateLimits.calcPoints(jwtInfo)
assert.equal(result, 1)
})
it('should return 10 points for indexer tier requesting SLPDB access', () => {
const jwtInfo = {
apiLevel: 30,
resource: 'slp',
id: '5e3a0415eb29a962da2708b4'
}
const result = rateLimits.calcPoints(jwtInfo)
assert.equal(result, 10)
})
it('should return 1 point for SLP tier requesting full node access', () => {
it('should return 50 points for indexer tier requesting SLPDB access', () => {
const jwtInfo = {
apiLevel: 30,
resource: 'slp',
id: '5e3a0415eb29a962da2708b4'
}
const result = rateLimits.calcPoints(jwtInfo)
assert.equal(result, 50)
})
it('should return 10 point for SLP tier requesting full node access', () => {
const jwtInfo = {
apiLevel: 40,
resource: 'blockchain',
@@ -219,10 +219,10 @@ describe('#route-ratelimits & jwt-auth', () => {
}
const result = rateLimits.calcPoints(jwtInfo)
assert.equal(result, 1)
assert.equal(result, 10)
})
it('should return 1 points for SLP tier requesting indexer access', () => {
it('should return 10 points for SLP tier requesting indexer access', () => {
const jwtInfo = {
apiLevel: 40,
resource: 'blockbook',
@@ -230,10 +230,10 @@ describe('#route-ratelimits & jwt-auth', () => {
}
const result = rateLimits.calcPoints(jwtInfo)
assert.equal(result, 1)
assert.equal(result, 10)
})
it('should return 1 points for SLP tier requesting SLPDB access', () => {
it('should return 10 points for SLP tier requesting SLPDB access', () => {
const jwtInfo = {
apiLevel: 40,
resource: 'slp',
@@ -241,7 +241,7 @@ describe('#route-ratelimits & jwt-auth', () => {
}
const result = rateLimits.calcPoints(jwtInfo)
assert.equal(result, 1)
assert.equal(result, 10)
})
})
@@ -319,7 +319,7 @@ describe('#route-ratelimits & jwt-auth', () => {
)
})
it('should trigger rate-limit for free tier after 10 RPM', async () => {
it('should trigger rate-limit for free tier after 20 RPM', async () => {
// Create a new instance of the rate limit so we start with zeroed tracking.
rateLimits = new RateLimits()
@@ -338,7 +338,7 @@ describe('#route-ratelimits & jwt-auth', () => {
// Mock the call to the jwt library.
sandbox.stub(rateLimits.jwt, 'verify').returns(jwtInfo)
for (let i = 0; i < 12; i++) {
for (let i = 0; i < 22; i++) {
next.reset() // reset the stubbed next() function.
await rateLimits.rateLimitByResource(req, res, next)
@@ -442,7 +442,7 @@ describe('#route-ratelimits & jwt-auth', () => {
// Issues with token secret should treat incoming requests as anonymous
// calls with 30 points or 3 RPM.
assert.equal(res.locals.pointsToConsume, 30)
assert.equal(res.locals.pointsToConsume, 50)
})
})
})
+15 -4
View File
@@ -975,6 +975,7 @@ describe('#Raw-Transactions', () => {
// Restore the saved URL.
process.env.RPC_BASEURL = savedUrl2
})
it('should submit hex encoded transaction', async () => {
// This is a difficult test to run as transaction hex is invalid after a
// block confirmation. So the unit tests simulates what the output 'should'
@@ -1003,8 +1004,13 @@ describe('#Raw-Transactions', () => {
// Integration test
} else {
assert.hasAllKeys(result, ['error'])
assert.include(result.error, 'Missing inputs')
if (process.env.ISBCHN) {
assert.hasAllKeys(result, ['error'])
assert.include(result.error, 'Missing inputs')
} else {
assert.hasAllKeys(result, ['error'])
assert.include(result.error, 'bad-txns-inputs-missingorspent')
}
}
})
})
@@ -1158,8 +1164,13 @@ describe('#Raw-Transactions', () => {
// Integration test
} else {
assert.hasAllKeys(result, ['error'])
assert.include(result.error, 'Missing inputs')
if (process.env.ISBCHN) {
assert.hasAllKeys(result, ['error'])
assert.include(result.error, 'Missing inputs')
} else {
assert.hasAllKeys(result, ['error'])
assert.include(result.error, 'bad-txns-inputs-missingorspent')
}
}
})
})
+250
View File
@@ -362,6 +362,7 @@ describe('#SLP', () => {
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' })
@@ -462,6 +463,80 @@ describe('#SLP', () => {
})
})
describe('#validate2Single', () => {
it('should throw 400 if txid is empty', async () => {
req.params.txid = ''
const result = await slpRoute.validate2Single(req, res)
// console.log(`result: ${util.inspect(result)}`)
assert.hasAllKeys(result, ['error'])
assert.include(result.error, 'txid can not be empty')
})
it('should invalidate a known invalid TXID', async () => {
if (process.env.TEST === 'unit') {
// Mock to prevent live network connection.
sandbox
.stub(slpRoute.axios, 'request')
.resolves({ data: { isValid: false } })
}
const txid =
'f7e5199ef6669ad4d078093b3ad56e355b6ab84567e59ad0f08a5ad0244f783a'
req.params.txid = txid
const result = await slpRoute.validate2Single(req, res)
// console.log(`result: ${JSON.stringify(result, null, 2)}`)
assert.equal(result.txid, txid)
assert.equal(result.isValid, false)
})
it('should validate a known valid TXID', async () => {
if (process.env.TEST === 'unit') {
// Mock to prevent live network connection.
sandbox
.stub(slpRoute.axios, 'request')
.resolves({ data: { isValid: true } })
}
const txid =
'3a4b628cbcc183ab376d44ce5252325f042268307ffa4a53443e92b6d24fb488'
req.params.txid = txid
const result = await slpRoute.validate2Single(req, res)
// console.log(`result: ${JSON.stringify(result, null, 2)}`)
assert.equal(result.txid, txid)
assert.equal(result.isValid, true)
})
// This test can only be run as a mocked unit test. It's too inconsistent
// to run as an integration test, due to the caching built into slp-validate.
if (process.env.TEST === 'unit') {
it('should cancel if validation takes too long', async () => {
// Mock the timeout error.
sandbox.stub(slpRoute.axios, 'request').throws({
code: 'ECONNABORTED'
})
const txid =
'eacb1085dfa296fef6d4ae2c0f4529a1bef096dd2325bdcc6dcb5241b3bdb579'
req.params.txid = txid
const result = await slpRoute.validate2Single(req, res)
// console.log(`result: ${JSON.stringify(result, null, 2)}`)
assert.isAbove(res.statusCode, 499, 'HTTP status code 503 expected.')
assert.include(
result.error,
'Could not communicate with full node',
'Error message expected'
)
})
}
})
describe('tokenStats()', () => {
it('should throw 400 if tokenID is empty', async () => {
req.params.tokenId = ''
@@ -888,6 +963,181 @@ describe('#SLP', () => {
assert.isNumber(result.outputs)
})
})
describe('#hydrateUtxos', () => {
it('should throw error if input is not an array.', async () => {
req.body.utxos = 'test'
const result = await slpRoute.hydrateUtxos(req, res)
// console.log(`result: `, result)
assert.hasAllKeys(result, ['error'])
assert.include(result.error, 'Input must be an array')
})
it('should throw error if Array is empty', async () => {
req.body.utxos = []
const result = await slpRoute.hydrateUtxos(req, res)
assert.hasAllKeys(result, ['error'])
assert.include(result.error, 'Array should not be empty')
})
it('should throw error if Array is too long', async () => {
const utxo = {
txid:
'bd158c564dd4ef54305b14f44f8e94c44b649f246dab14bcb42fb0d0078b8a90',
vout: 3,
amount: 0.00002015,
satoshis: 2015,
height: 594892,
confirmations: 5
}
const utxos = []
// Populate array with 21 utxos
for (let i = 0; i < 21; i++) {
utxos.push(utxo)
}
req.body.utxos = utxos
const result = await slpRoute.hydrateUtxos(req, res)
assert.hasAllKeys(result, ['error'])
assert.include(result.error, 'Array too long, max length is 20')
})
it('should return utxo details', async () => {
const utxos = [
{
utxos: [
{
txid:
'd56a2b446d8149c39ca7e06163fe8097168c3604915f631bc58777d669135a56',
vout: 3,
value: '6816',
height: 606848,
confirmations: 13,
satoshis: 6816
},
{
txid:
'd56a2b446d8149c39ca7e06163fe8097168c3604915f631bc58777d669135a56',
vout: 2,
value: '546',
height: 606848,
confirmations: 13,
satoshis: 546
}
]
}
]
// Mock the external network call.
sandbox.stub(slpRoute.bchjs.SLP.Utils, 'tokenUtxoDetails').resolves([
{
txid:
'd56a2b446d8149c39ca7e06163fe8097168c3604915f631bc58777d669135a56',
vout: 3,
value: '6816',
height: 606848,
confirmations: 13,
satoshis: 6816,
isValid: false
},
{
txid:
'd56a2b446d8149c39ca7e06163fe8097168c3604915f631bc58777d669135a56',
vout: 2,
value: '546',
height: 606848,
confirmations: 13,
satoshis: 546,
utxoType: 'token',
transactionType: 'send',
tokenId:
'dd84ca78db4d617221b58eabc6667af8fe2f7eadbfcc213d35be9f1b419beb8d',
tokenTicker: 'TAP',
tokenName: 'Thoughts and Prayers',
tokenDocumentUrl: '',
tokenDocumentHash: '',
decimals: 0,
tokenType: 1,
tokenQty: 5,
isValid: true
}
])
req.body.utxos = utxos
const result = await slpRoute.hydrateUtxos(req, res)
// console.log(`result: ${JSON.stringify(result, null, 2)}`)
// Test the general structure of the output.
assert.isArray(result.slpUtxos)
assert.equal(result.slpUtxos.length, 1)
assert.equal(result.slpUtxos[0].utxos.length, 2)
// Test the non-slp UTXO.
assert.property(result.slpUtxos[0].utxos[0], 'txid')
assert.property(result.slpUtxos[0].utxos[0], 'vout')
assert.property(result.slpUtxos[0].utxos[0], 'value')
assert.property(result.slpUtxos[0].utxos[0], 'height')
assert.property(result.slpUtxos[0].utxos[0], 'confirmations')
assert.property(result.slpUtxos[0].utxos[0], 'satoshis')
assert.property(result.slpUtxos[0].utxos[0], 'isValid')
assert.equal(result.slpUtxos[0].utxos[0].isValid, false)
// Test the slp UTXO.
assert.property(result.slpUtxos[0].utxos[1], 'txid')
assert.property(result.slpUtxos[0].utxos[1], 'vout')
assert.property(result.slpUtxos[0].utxos[1], 'value')
assert.property(result.slpUtxos[0].utxos[1], 'height')
assert.property(result.slpUtxos[0].utxos[1], 'confirmations')
assert.property(result.slpUtxos[0].utxos[1], 'satoshis')
assert.property(result.slpUtxos[0].utxos[1], 'isValid')
assert.equal(result.slpUtxos[0].utxos[1].isValid, true)
assert.property(result.slpUtxos[0].utxos[1], 'transactionType')
assert.property(result.slpUtxos[0].utxos[1], 'tokenId')
assert.property(result.slpUtxos[0].utxos[1], 'tokenTicker')
assert.property(result.slpUtxos[0].utxos[1], 'tokenName')
assert.property(result.slpUtxos[0].utxos[1], 'tokenDocumentUrl')
assert.property(result.slpUtxos[0].utxos[1], 'tokenDocumentHash')
assert.property(result.slpUtxos[0].utxos[1], 'decimals')
assert.property(result.slpUtxos[0].utxos[1], 'tokenType')
assert.property(result.slpUtxos[0].utxos[1], 'tokenQty')
})
it('should throw error for missing properties', async () => {
const utxos = [
{
height: 639443,
tx_hash:
'30707fffb9b295a06a68d217f49c198e9e1dbe1edc3874a0928ca1905f1709df',
tx_pos: 0,
value: 6000
},
{
height: 639443,
tx_hash:
'8962566e413501224d178a02effc89be5ac0d8e4195f617415d443dc4c38fe50',
tx_pos: 1,
value: 546
}
]
req.body.utxos = utxos
const result = await slpRoute.hydrateUtxos(req, res)
assert.hasAllKeys(result, ['error'])
assert.include(
result.error,
'Each element in array should have a utxos property'
)
})
})
})
/*