mirror of
https://github.com/fullstack-cash/bch-api.git
synced 2026-09-22 09:12:05 -07:00
Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
9cd06630ea | ||
|
|
6b281e2842 | ||
|
|
2e143d5a9e | ||
|
|
eb817da044 | ||
|
|
1c88aa7d72 | ||
|
|
a9e976cc24 | ||
|
|
5644b53cf8 | ||
|
|
236637f0e0 | ||
|
|
fb1bb0b7ed | ||
|
|
8a91f3ae3a | ||
|
|
80dc296dee | ||
|
|
f01c82631b | ||
|
|
ef2a681edb | ||
|
|
44ce5f2aa9 | ||
|
|
f52a04a337 | ||
|
|
9aa3e5b52a | ||
|
|
71452f4834 | ||
|
|
96f1cb6ad4 | ||
|
|
36dd64db22 | ||
|
|
05de04a569 | ||
|
|
c661799095 | ||
|
|
4f416d6423 | ||
|
|
58dc4da92c | ||
|
|
7b8710d3da | ||
|
|
7976909dbb | ||
|
|
24b85e410d | ||
|
|
ae13d2c42e | ||
|
|
bf0fc2437a | ||
|
|
88d3dd12a1 | ||
|
|
cb8b4a5586 | ||
|
|
ec0ef24508 | ||
|
|
5a16e69719 | ||
|
|
154dbe8151 | ||
|
|
785d3059c3 | ||
|
|
21b19b59c9 | ||
|
|
588feff5db | ||
|
|
9fafc6fba3 | ||
|
|
8412da15db | ||
|
|
54c299e7fe | ||
|
|
a06399cfda | ||
|
|
e49c488e06 | ||
|
|
be07ffab33 | ||
|
|
41a5b24faf | ||
|
|
2172d0feff | ||
|
|
c4664ca173 | ||
|
|
11a7915cb8 | ||
|
|
06b944f36a | ||
|
|
9a8886121d | ||
|
|
89c6d48dfd | ||
|
|
4525b99f33 | ||
|
|
3d04507948 | ||
|
|
7e3a447973 | ||
|
|
bdde3a271d | ||
|
|
9450c3969b | ||
|
|
23d66da792 | ||
|
|
cb430c20e2 | ||
|
|
58ce0e0604 | ||
|
|
61485dfe99 | ||
|
|
ab4c80b674 | ||
|
|
dbd5dd53c2 | ||
|
|
e2fa671964 | ||
|
|
02818da317 | ||
|
|
9804ea8c3a | ||
|
|
4bfe198a62 | ||
|
|
453cbacf9d | ||
|
|
42d88a149a | ||
|
|
2e97430856 | ||
|
|
60bf587031 |
@@ -16,6 +16,7 @@ start-local-testnet.sh
|
||||
test-fullstack-main.sh
|
||||
start-fullstack-main.sh
|
||||
start-fullstack-test.sh
|
||||
start-fullstack-testnet.sh
|
||||
start-free-main.sh
|
||||
start-local-mainnet.sh.save
|
||||
start-local-testnet.sh.save
|
||||
@@ -27,6 +28,7 @@ test-fullstack-abc.sh
|
||||
test-fullstack-bchn.sh
|
||||
start-fullstack-abc.sh
|
||||
start-fullstack-bchn.sh
|
||||
start-ss-main.sh
|
||||
|
||||
coverage
|
||||
start-my-infra
|
||||
|
||||
+1
-1
@@ -1,6 +1,6 @@
|
||||
MIT License
|
||||
|
||||
Copyright (c) 2020 Chris Troutner
|
||||
Copyright (c) 2021 Permissionless Software Foundation
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
|
||||
@@ -1,6 +1,9 @@
|
||||
# bch-api
|
||||
|
||||
This is a node.js JavaScript REST API server using Express.js. The purpose of this code is to create a REST API server that provides a common interface for working with a Bitcoin Cash full node and various indexers. See [this article](https://troutsblog.com/research/bitcoin-cash/how-to-bch-full-stack-developer) to learn about the 'Cash Stack'. Visit [FullStack.cash](https://fullstack.cash), sign up for a free account, and use this REST API right away with the [bch-js](https://github.com/Permissionless-Software-Foundation/bch-js) JavaScript library.
|
||||
[](https://github.com/Permissionless-Software-Foundation/bch-js/blob/master/LICENSE.md)
|
||||
[](https://github.com/feross/standard)
|
||||
|
||||
This is a REST API server, written in node.js JavaScript, using [Express.js](https://expressjs.com/) framework. The purpose of this code is to create a REST API server that provides a common interface for working with a Bitcoin Cash full node and various indexers. See [this article](https://psfoundation.cash/blog/cash-stack) to learn about the 'Cash Stack'. Visit [FullStack.cash](https://fullstack.cash), sign up for a free account, and use this REST API right away with the [bch-js](https://github.com/Permissionless-Software-Foundation/bch-js) JavaScript library.
|
||||
|
||||
This repository is intended to be paired with [bch-js](https://github.com/Permissionless-Software-Foundation/bch-js), an npm JavaScript library for building Bitcoin Cash apps.
|
||||
|
||||
@@ -20,7 +23,7 @@ The following features set this repository apart from rest.bitcoin.com:
|
||||
- Fine grain access is controlled with a JWT token using
|
||||
[this back end auth server](https://github.com/Permissionless-Software-Foundation/jwt-bch-api) and [this front end](https://github.com/Permissionless-Software-Foundation/jwt-bch-frontend). Can be used to monetize access to the REST API.
|
||||
- Typescript removed and ES8 JavaScript used instead.
|
||||
- npm audit run on all dependencies.
|
||||
- `npm audit fix` frequently run to fix dependencies.
|
||||
|
||||
## Live Demo
|
||||
You can test a live demo of the REST API by running the
|
||||
@@ -31,17 +34,17 @@ You can bootstrap your own REST API server by downloading and installing the inf
|
||||
### Configure bch-js
|
||||
The live servers can be used by [bch-js](https://github.com/Permissionless-Software-Foundation/bch-js) by settings the `restURL` config property to one of these servers:
|
||||
|
||||
- BCHN Mainnet REST API server: https://bchn.fullstack.cash/v3/
|
||||
- ABC Mainnet REST API server: https://abc.fullstack.cash/v3/
|
||||
- Testnet3 REST API server: https://testnet3.fullstack.cash/v3/
|
||||
- BCHN Mainnet REST API server: https://bchn.fullstack.cash/v4/
|
||||
- ABC Mainnet REST API server: https://abc.fullstack.cash/v4/
|
||||
- Testnet3 REST API server: https://testnet3.fullstack.cash/v4/
|
||||
- Check server status: https://metrics.fullstack.cash
|
||||
|
||||
## Installation
|
||||
There are two installation paths, depending if you want a *development* or
|
||||
*production* environment. You'll also need to set up the underlying infrastructure
|
||||
described on [this page](https://troutsblog.com/research/bitcoin-cash/how-to-bch-full-stack-developer).
|
||||
described on [this page](https://psfoundation.cash/blog/cash-stack).
|
||||
|
||||
This code targets the Ubuntu 18.04 LTS Linux OS.
|
||||
This code targets the Ubuntu 18.04 LTS Linux OS or higher.
|
||||
|
||||
### Development
|
||||
This is a standard node.js project. The installation is as follows:
|
||||
@@ -86,7 +89,8 @@ Have questions? Need help? Join our community support
|
||||
## IPFS
|
||||
Copies of this repository will occasionally be uploaded and hosted on [IPFS](https://ipfs.io):
|
||||
|
||||
- v1.15.4: QmSAerFmDJm6KQp2HC8jsRR5MdL5X39hoUsJYvWNDvrD8m
|
||||
- v1.25.1: [QmXFzFJHenymhReDc9oGAxbpmvK2fJYnisQ2pJn3gGyKKX](https://ipfs.io/ipfs/QmXFzFJHenymhReDc9oGAxbpmvK2fJYnisQ2pJn3gGyKKX)
|
||||
|
||||
## License
|
||||
[MIT](./LICENSE.md)
|
||||
a
|
||||
|
||||
+12
-1
@@ -1,11 +1,22 @@
|
||||
/*
|
||||
Common configuration settings.
|
||||
Default settings in this file can be overridden by an environment variable.
|
||||
*/
|
||||
|
||||
const config = {
|
||||
// This is the same secret used by jwt-bch-api
|
||||
apiTokenSecret: process.env.TOKENSECRET
|
||||
? process.env.TOKENSECRET
|
||||
: 'secret-jwt-token'
|
||||
: 'secret-jwt-token',
|
||||
|
||||
// Rate Limits
|
||||
anonRateLimit: process.env.ANON_RATE_LIMIT ? Number(process.env.ANON_RATE_LIMIT) : 50,
|
||||
whitelistRateLimit: process.env.WHITELIST_RATE_LIMIT
|
||||
? Number(process.env.WHITELIST_RATE_LIMIT)
|
||||
: 10,
|
||||
whitelistDomains: process.env.WHITELIST_DOMAINS
|
||||
? process.env.WHITELIST_DOMAINS.split(',')
|
||||
: ['fullstack.cash', 'psfoundation.cash']
|
||||
}
|
||||
|
||||
module.exports = config
|
||||
|
||||
@@ -1,21 +1,29 @@
|
||||
#!/bin/bash
|
||||
|
||||
# test
|
||||
# Docker containers for the below infrastructure is described here:
|
||||
# https://psfoundation.cash/blog/cash-stack
|
||||
# Pre-synced databases can be downloaded on the CashStrap page:
|
||||
# https://fullstack.cash/cashstrap
|
||||
|
||||
# Which network are you using?
|
||||
export NETWORK=mainnet
|
||||
#export NETWORK=testnet
|
||||
|
||||
# Full node
|
||||
export RPC_BASEURL=http://172.17.0.1:8332/
|
||||
export RPC_IP=172.17.0.1:8332
|
||||
export RPC_BASEURL=http://$RPC_IP/
|
||||
export RPC_USERNAME=bitcoin
|
||||
export RPC_PASSWORD=password
|
||||
export NETWORK=mainnet
|
||||
|
||||
# SLPDB
|
||||
export SLPDB_PASS_GP=somelongpassword
|
||||
export SLPDB_URL=http://172.17.0.1:12300/
|
||||
export SLPDB_PASS=somelongpassword
|
||||
|
||||
# Blockbook Indexer
|
||||
export BLOCKBOOK_URL=https://172.17.0.1:9131/
|
||||
# Allow node.js to make network calls to https using self-signed certificate.
|
||||
export NODE_TLS_REJECT_UNAUTHORIZED=0
|
||||
# Use the same address as SLPDB_URL if you don't have a separate whitelist server.
|
||||
export SLPDB_PASS_WL=somelongpassword
|
||||
export SLPDB_WHITELIST_URL=http://172.17.0.1:12300/
|
||||
# slp-api alternative SLP validator using slp-validate:
|
||||
# https://github.com/Permissionless-Software-Foundation/slp-api
|
||||
export SLP_API_URL=http://10.0.0.5:5001/
|
||||
|
||||
# Mainnet Fulcrum / ElectrumX
|
||||
export FULCRUM_URL=172.17.0.1
|
||||
@@ -26,12 +34,31 @@ export REDIS_PORT=6379
|
||||
export REDIS_HOST=172.17.0.1
|
||||
|
||||
# JWT Token Secret
|
||||
# This is used to verify JWT tokens generated with jwt-bch-api:
|
||||
# https://github.com/Permissionless-Software-Foundation/jwt-bch-api
|
||||
export TOKENSECRET=somelongsecretvalue
|
||||
|
||||
# So that bch-api can call bch-js locally.
|
||||
export LOCAL_RESTURL=http://127.0.0.1:3000/v3/
|
||||
export LOCAL_RESTURL=http://127.0.0.1:3000/v4/
|
||||
|
||||
# slp-api alternative SLP validator.
|
||||
export SLP_API_URL=http://10.0.0.5:5001/
|
||||
# Basic Authentication password
|
||||
export PRO_PASS=somerandomepassword:someotherrandompassword:aThirdPassword
|
||||
|
||||
# Whitelisted domains. Automatically give pro-tier rate limit access to apps
|
||||
# that originate froma domain on the whitelist.
|
||||
export WHITELIST_DOMAINS=fullstack.cash,psfoundation.cash,torlist.cash
|
||||
|
||||
# Rate Limits. Numbers are divided into 1000. e.g. 1000 / 50 = 20 RPM for ANON.
|
||||
# Requests use the ANON rate limit if they fail to pass in a JWT token.
|
||||
# ANON = 20 requests per minute (RPM)
|
||||
export ANON_RATE_LIMIT=50
|
||||
# 10 = 100 RPM
|
||||
export WHITELIST_RATE_LIMIT=10
|
||||
|
||||
# Set logging parameters
|
||||
#1m means no more than 1 megabyte
|
||||
export LOG_MAX_SIZE=1m
|
||||
#5d means store no more than 5 days
|
||||
export LOG_MAX_FILES=5d
|
||||
|
||||
npm start
|
||||
|
||||
@@ -1,22 +1,32 @@
|
||||
#!/bin/bash
|
||||
|
||||
# Docker containers for the below infrastructure is described here:
|
||||
# https://psfoundation.cash/blog/cash-stack
|
||||
# Pre-synced databases can be downloaded on the CashStrap page:
|
||||
# https://fullstack.cash/cashstrap
|
||||
|
||||
# Which network are you using?
|
||||
#export NETWORK=mainnet
|
||||
export NETWORK=testnet
|
||||
|
||||
# Full node
|
||||
export RPC_BASEURL=http://172.17.0.1:18332/
|
||||
export RPC_IP=172.17.0.1:18332
|
||||
export RPC_BASEURL=http://$RPC_IP/
|
||||
export RPC_USERNAME=bitcoin
|
||||
export RPC_PASSWORD=password
|
||||
export NETWORK=testnet
|
||||
|
||||
# SLPDB
|
||||
export SLPDB_URL=http://172.17.0.1:13300/
|
||||
|
||||
# Blockbook Indexer
|
||||
export BLOCKBOOK_URL=https://172.17.0.1:19131/
|
||||
# Allow node.js to make network calls to https using self-signed certificate.
|
||||
export NODE_TLS_REJECT_UNAUTHORIZED=0
|
||||
export SLPDB_PASS=somelongpassword
|
||||
# Use the same address as SLPDB_URL if you don't have a separate whitelist server.
|
||||
export SLPDB_WHITELIST_URL=http://172.17.0.1:13300/
|
||||
# slp-api alternative SLP validator using slp-validate:
|
||||
# https://github.com/Permissionless-Software-Foundation/slp-api
|
||||
export SLP_API_URL=http://10.0.0.5:5001/
|
||||
|
||||
# Mainnet Fulcrum / ElectrumX
|
||||
export FULCRUM_URL=172.17.0.1
|
||||
export FULCRUM_PORT=50002
|
||||
export FULCRUM_PORT=60002
|
||||
|
||||
# Redis DB
|
||||
export REDIS_PORT=6380
|
||||
@@ -26,9 +36,20 @@ export REDIS_HOST=172.17.0.1
|
||||
export TOKENSECRET=somelongsecretvalue
|
||||
|
||||
# So that bch-api can call bch-js locally.
|
||||
export LOCAL_RESTURL=http://127.0.0.1:3000/v3/
|
||||
export LOCAL_RESTURL=http://127.0.0.1:3000/v4/
|
||||
|
||||
# slp-api alternative SLP validator.
|
||||
export SLP_API_URL=http://10.0.0.5:5001/
|
||||
# Basic Authentication password (optional)
|
||||
export PRO_PASS=somerandomepassword:someotherrandompassword:aThirdPassword
|
||||
|
||||
# Whitelisted domains. Automatically give pro-tier rate limit access to apps
|
||||
# that originate froma domain on the whitelist.
|
||||
export WHITELIST_DOMAINS=fullstack.cash,psfoundation.cash,torlist.cash
|
||||
|
||||
# Rate Limits. Numbers are divided into 1000. e.g. 1000 / 50 = 20 RPM for ANON.
|
||||
# Requests use the ANON rate limit if they fail to pass in a JWT token.
|
||||
# ANON = 20 requests per minute (RPM)
|
||||
export ANON_RATE_LIMIT=50
|
||||
# 10 = 100 RPM
|
||||
export WHITELIST_RATE_LIMIT=10
|
||||
|
||||
npm start
|
||||
|
||||
Generated
+22096
-5371
File diff suppressed because it is too large
Load Diff
+10
-17
@@ -17,21 +17,22 @@
|
||||
"test": "npm run lint && npm run test-v4",
|
||||
"lint": "standard --env mocha --fix",
|
||||
"test-v4": "export NETWORK=mainnet && nyc --reporter=text mocha --timeout 60000 test/v4/",
|
||||
"test:temp": "export NETWORK=mainnet && export TEST=integration && mocha --timeout 25000 test/v4/integration/price.js",
|
||||
"test:integration": "mocha test/v4/integration",
|
||||
"test:integration:slpdb": "mocha --timeout 25000 -g '#validate2Single' test/v4/integration/slp*.js",
|
||||
"coverage": "nyc report --reporter=text-lcov | coveralls",
|
||||
"coverage:report": "export NETWORK=mainnet && nyc --reporter=html mocha --timeout 25000 test/v4/",
|
||||
"docs": "./node_modules/.bin/apidoc -i src/routes/v4 -o docs"
|
||||
"docs": "./node_modules/.bin/apidoc -i src/routes/v4 -o docs",
|
||||
"test:temp1": "export NETWORK=mainnet && export TEST=integration && mocha --exit --timeout 25000 -g '#hydrateUtxosWL' test/v4/integration/",
|
||||
"test:temp2": "mocha test/v4/rate-limits.js"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=10.15.1"
|
||||
},
|
||||
"dependencies": {
|
||||
"@psf/bch-js": "^4.0.0",
|
||||
"apidoc": "^0.23.0",
|
||||
"axios": "^0.19.0",
|
||||
"bitcore-lib-cash": "^8.20.3",
|
||||
"@psf/bch-js": "^4.15.7",
|
||||
"apidoc": "^0.26.0",
|
||||
"axios": "^0.21.1",
|
||||
"bitcore-lib-cash": "^8.23.1",
|
||||
"body-parser": "^1.18.3",
|
||||
"cookie-parser": "~1.4.3",
|
||||
"cors": "^2.8.3",
|
||||
@@ -44,7 +45,6 @@
|
||||
"helmet": "^3.21.2",
|
||||
"ioredis": "^4.14.1",
|
||||
"jsonwebtoken": "^8.5.1",
|
||||
"level": "^6.0.0",
|
||||
"mkdirp": "^1.0.0",
|
||||
"mocha": "^7.1.1",
|
||||
"morgan": "^1.9.1",
|
||||
@@ -52,8 +52,6 @@
|
||||
"passport": "^0.4.0",
|
||||
"passport-anonymous": "^1.0.1",
|
||||
"passport-http": "^0.3.0",
|
||||
"pg": "^8.0.0",
|
||||
"pg-hstore": "^2.3.2",
|
||||
"rate-limiter-flexible": "^2.0.0",
|
||||
"winston": "^3.2.1",
|
||||
"winston-daily-rotate-file": "^4.0.0"
|
||||
@@ -68,17 +66,12 @@
|
||||
"eslint-plugin-prettier": "^3.1.0",
|
||||
"eslint-plugin-standard": "^4.0.0",
|
||||
"fs-extra": "^9.0.0",
|
||||
"nock": "^12.0.0",
|
||||
"node-mocks-http": "^1.7.0",
|
||||
"nock": "^13.0.5",
|
||||
"nyc": "^15.0.0",
|
||||
"prettier": "^2.0.0",
|
||||
"proxyquire": "^2.1.0",
|
||||
"request": "^2.88.0",
|
||||
"request-promise": "^4.2.2",
|
||||
"semantic-release": "^17.3.0",
|
||||
"semantic-release": "^17.3.9",
|
||||
"sinon": "^9.0.0",
|
||||
"standard": "^14.3.1",
|
||||
"typescript": "^3.1.4"
|
||||
"standard": "^14.3.1"
|
||||
},
|
||||
"release": {
|
||||
"publish": [
|
||||
|
||||
+2
-2
@@ -262,11 +262,11 @@ function onError (error) {
|
||||
case 'EACCES':
|
||||
console.error(`${bind} requires elevated privileges`)
|
||||
process.exit(1)
|
||||
break
|
||||
// break
|
||||
case 'EADDRINUSE':
|
||||
console.error(`${bind} is already in use`)
|
||||
process.exit(1)
|
||||
break
|
||||
// break
|
||||
default:
|
||||
throw error
|
||||
}
|
||||
|
||||
@@ -12,12 +12,17 @@
|
||||
|
||||
'use strict'
|
||||
|
||||
// Public npm libraries.
|
||||
const jwt = require('jsonwebtoken')
|
||||
|
||||
// local libraries.
|
||||
const wlogger = require('../util/winston-logging')
|
||||
const config = require('../../config')
|
||||
|
||||
const ANON_LIMITS = 50
|
||||
const ANON_LIMITS = config.anonRateLimit
|
||||
const WHITELIST_RATE_LIMIT = config.whitelistRateLimit
|
||||
const WHITELIST_DOMAINS = config.whitelistDomains
|
||||
const INTERNAL_RATE_LIMIT = 1
|
||||
|
||||
// Redis
|
||||
const redisOptions = {
|
||||
@@ -108,6 +113,27 @@ class RateLimits {
|
||||
err
|
||||
)
|
||||
}
|
||||
//
|
||||
} else if (req.body && req.body.usrObj) {
|
||||
// Same as above, but this code path is activated from internal calls to
|
||||
// bch-js, like hydrateUtxo() which passes the user object from the
|
||||
// original API call.
|
||||
|
||||
try {
|
||||
decoded = _this.jwt.verify(
|
||||
req.body.usrObj.jwtToken,
|
||||
_this.config.apiTokenSecret
|
||||
)
|
||||
// console.log(`decoded: ${JSON.stringify(decoded, null, 2)}`)
|
||||
|
||||
userId = decoded.id
|
||||
} catch (err) {
|
||||
// This handler will be triggered if the JWT token does not match the
|
||||
// token secret.
|
||||
wlogger.error(
|
||||
'Error in route-ratelimit.js trying to decode JWT token in usrObj'
|
||||
)
|
||||
}
|
||||
} else {
|
||||
wlogger.debug('No JWT token found!')
|
||||
}
|
||||
@@ -124,9 +150,27 @@ class RateLimits {
|
||||
const resource = _this.getResource(req.url)
|
||||
wlogger.debug(`resource: ${resource}`)
|
||||
|
||||
// Key will be the JWT ID if it exists, otherwise the IP address of the caller.
|
||||
let key = userId || req.ip
|
||||
res.locals.key = key // Feedback for tests.
|
||||
|
||||
// For internal calls that make a lot of internal calls, like
|
||||
// hydrateUtxoDetails(), the origin of the caller will be passed in
|
||||
// via the POST body.
|
||||
const keyIsLocal =
|
||||
key.includes('172.17.0.1') || key.includes('127.0.0.1')
|
||||
if (req.body && req.body.usrObj && keyIsLocal) {
|
||||
// key = req.body.ip
|
||||
console.log(
|
||||
`route-ratelimit usrObj: ${JSON.stringify(
|
||||
req.body.usrObj,
|
||||
null,
|
||||
2
|
||||
)}`
|
||||
)
|
||||
}
|
||||
console.log(`key: ${key}`)
|
||||
|
||||
// const pointsToConsume = userId ? 1 : 30
|
||||
decoded.resource = resource
|
||||
let pointsToConsume = _this.calcPoints(decoded)
|
||||
@@ -144,13 +188,11 @@ class RateLimits {
|
||||
|
||||
// If the request originates from one of the approved wallet apps, then
|
||||
// apply paid-access rate limits.
|
||||
if (
|
||||
origin &&
|
||||
(origin.toString().indexOf('fullstack.cash') > -1 ||
|
||||
origin.toString().indexOf('splitbch.com') > -1 ||
|
||||
origin.toString().indexOf('slp-api') > -1)
|
||||
) {
|
||||
pointsToConsume = 10
|
||||
// console.log(`origin: ${JSON.stringify(origin, null, 2)}`)
|
||||
// console.log(`whitelist: ${JSON.stringify(WHITELIST_DOMAINS, null, 2)}`)
|
||||
const isInWhitelist = _this.isInWhitelist(origin)
|
||||
if (isInWhitelist) {
|
||||
pointsToConsume = WHITELIST_RATE_LIMIT
|
||||
res.locals.pointsToConsume = pointsToConsume // Feedback for tests.
|
||||
}
|
||||
|
||||
@@ -161,7 +203,7 @@ class RateLimits {
|
||||
// Do not comment out this line.
|
||||
key.toString().indexOf('172.17.') > -1
|
||||
) {
|
||||
pointsToConsume = 1
|
||||
pointsToConsume = INTERNAL_RATE_LIMIT
|
||||
res.locals.pointsToConsume = pointsToConsume // Feedback for tests.
|
||||
}
|
||||
|
||||
@@ -263,6 +305,33 @@ class RateLimits {
|
||||
throw err
|
||||
}
|
||||
}
|
||||
|
||||
// Returns a boolean if the origin of the request matches a domain in the
|
||||
// whitelist.
|
||||
isInWhitelist (origin) {
|
||||
try {
|
||||
const retVal = false // Default value.
|
||||
|
||||
if (!origin) return false
|
||||
|
||||
// console.log(`WHITELIST_DOMAINS: ${JSON.stringify(WHITELIST_DOMAINS, null, 2)}`)
|
||||
|
||||
for (let i = 0; i < WHITELIST_DOMAINS.length; i++) {
|
||||
const thisDomain = WHITELIST_DOMAINS[i]
|
||||
|
||||
if (origin.toString().indexOf(thisDomain) > -1) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
return retVal
|
||||
} catch (err) {
|
||||
wlogger.error(
|
||||
'Error in route-ratelimit.js/isInWhitelist(). Returning false by default.'
|
||||
)
|
||||
return false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = RateLimits
|
||||
|
||||
@@ -135,7 +135,7 @@ class Electrum {
|
||||
const { msg, status } = _this.routeUtils.decodeError(err)
|
||||
if (msg) {
|
||||
res.status(status)
|
||||
return res.json({ error: msg })
|
||||
return res.json({ success: false, error: msg })
|
||||
}
|
||||
|
||||
// Handle error patterns specific to this route.
|
||||
|
||||
@@ -45,7 +45,7 @@ class Encryption {
|
||||
const { msg, status } = _this.routeUtils.decodeError(err)
|
||||
if (msg) {
|
||||
res.status(status)
|
||||
return res.json({ error: msg })
|
||||
return res.json({ success: false, error: msg })
|
||||
}
|
||||
|
||||
// Handle error patterns specific to this route.
|
||||
|
||||
@@ -175,8 +175,17 @@ function decodeError (err) {
|
||||
}
|
||||
}
|
||||
|
||||
// Handle general Error objects.
|
||||
if (err.message) {
|
||||
return {
|
||||
message: err.message,
|
||||
status: 422
|
||||
}
|
||||
}
|
||||
|
||||
return { msg: false, status: 500 }
|
||||
} catch (err) {
|
||||
console.error('unhandled error in route-utils.js/decodeError(): ', err)
|
||||
wlogger.error('unhandled error in route-utils.js/decodeError(): ', err)
|
||||
return { msg: false, status: 500 }
|
||||
}
|
||||
|
||||
+191
-16
@@ -17,7 +17,9 @@ const wlogger = require('../../util/winston-logging')
|
||||
const LOCAL_RESTURL = process.env.LOCAL_RESTURL
|
||||
? process.env.LOCAL_RESTURL
|
||||
: 'https://api.fullstack.cash/v4/'
|
||||
|
||||
const BCHJS = require('@psf/bch-js')
|
||||
// const BCHJS = require('../../../../bch-js')
|
||||
const bchjs = new BCHJS({ restURL: LOCAL_RESTURL })
|
||||
|
||||
// Used to convert error messages to strings, to safely pass to users.
|
||||
@@ -33,7 +35,10 @@ util.inspect.defaultOptions = { depth: 5 }
|
||||
|
||||
// Determine the Access password for a private instance of SLPDB.
|
||||
// https://gist.github.com/christroutner/fc717ca704dec3dded8b52fae387eab2
|
||||
const SLPDB_PASS = process.env.SLPDB_PASS ? process.env.SLPDB_PASS : 'BITBOX'
|
||||
// Password for General Purpose (GP) SLPDB.
|
||||
const SLPDB_PASS_GP = process.env.SLPDB_PASS_GP ? process.env.SLPDB_PASS_GP : 'BITBOX'
|
||||
// Password for Whitelist (WL) SLPDB.
|
||||
const SLPDB_PASS_WL = process.env.SLPDB_PASS_WL ? process.env.SLPDB_PASS_WL : 'BITBOX'
|
||||
|
||||
// const rawtransactions = require('./full-node/rawtransactions')
|
||||
const RawTransactions = require('./full-node/rawtransactions')
|
||||
@@ -41,9 +46,9 @@ const rawTransactions = new RawTransactions()
|
||||
|
||||
// Setup REST and TREST URLs used by slpjs
|
||||
// Dev note: this allows for unit tests to mock the URL.
|
||||
if (!process.env.REST_URL) process.env.REST_URL = 'https://rest.bitcoin.com/v2/'
|
||||
if (!process.env.REST_URL) process.env.REST_URL = 'https://bchn.fullstack.cash/v4/'
|
||||
if (!process.env.TREST_URL) {
|
||||
process.env.TREST_URL = 'https://trest.bitcoin.com/v2/'
|
||||
process.env.TREST_URL = 'https://testnet.fullstack.cash/v4/'
|
||||
}
|
||||
|
||||
let _this
|
||||
@@ -89,6 +94,8 @@ class Slp {
|
||||
)
|
||||
_this.router.post('/generateSendOpReturn', _this.generateSendOpReturn)
|
||||
_this.router.post('/hydrateUtxos', _this.hydrateUtxos)
|
||||
_this.router.post('/hydrateUtxosWL', _this.hydrateUtxosWL)
|
||||
_this.router.get('/status', _this.getStatus)
|
||||
}
|
||||
|
||||
// DRY error handler.
|
||||
@@ -430,7 +437,7 @@ class Slp {
|
||||
const b64 = Buffer.from(s).toString('base64')
|
||||
const url = `${process.env.SLPDB_URL}q/${b64}`
|
||||
|
||||
const options = _this.generateCredentials()
|
||||
const options = _this.generateCredentialsGP()
|
||||
// Request options
|
||||
const opt = {
|
||||
method: 'get',
|
||||
@@ -581,7 +588,7 @@ class Slp {
|
||||
}
|
||||
}
|
||||
|
||||
const options = _this.generateCredentials()
|
||||
const options = _this.generateCredentialsGP()
|
||||
|
||||
// Collect an array of promises, one for each request to slpserve.
|
||||
// This is a nested array of promises.
|
||||
@@ -803,7 +810,7 @@ class Slp {
|
||||
const b64 = Buffer.from(s).toString('base64')
|
||||
const url = `${process.env.SLPDB_URL}q/${b64}`
|
||||
|
||||
const options = _this.generateCredentials()
|
||||
const options = _this.generateCredentialsGP()
|
||||
const opt = {
|
||||
method: 'get',
|
||||
baseURL: url,
|
||||
@@ -992,7 +999,7 @@ class Slp {
|
||||
const url = `${process.env.SLPDB_URL}q/${b64}`
|
||||
// console.log('url: ', url)
|
||||
|
||||
const options = _this.generateCredentials()
|
||||
const options = _this.generateCredentialsGP()
|
||||
|
||||
// Get data from SLPDB.
|
||||
const opt = {
|
||||
@@ -1126,7 +1133,7 @@ class Slp {
|
||||
}
|
||||
}
|
||||
|
||||
const options = _this.generateCredentials()
|
||||
const options = _this.generateCredentialsGP()
|
||||
|
||||
const s = JSON.stringify(query)
|
||||
const b64 = Buffer.from(s).toString('base64')
|
||||
@@ -1304,6 +1311,7 @@ class Slp {
|
||||
async validate3Single (req, res, next) {
|
||||
try {
|
||||
const txid = req.params.txid
|
||||
// console.log('validate3Single txid: ', txid)
|
||||
|
||||
// Validate input
|
||||
if (!txid || txid === '') {
|
||||
@@ -1325,7 +1333,7 @@ class Slp {
|
||||
}
|
||||
}
|
||||
|
||||
const options = _this.generateCredentials()
|
||||
const options = _this.generateCredentialsWL()
|
||||
|
||||
const s = JSON.stringify(query)
|
||||
const b64 = Buffer.from(s).toString('base64')
|
||||
@@ -1385,6 +1393,7 @@ class Slp {
|
||||
async validate3Bulk (req, res, next) {
|
||||
try {
|
||||
const txids = req.body.txids
|
||||
// console.log(`validate3Bulk txids: `, txids)
|
||||
|
||||
// Reject if txids is not an array.
|
||||
if (!Array.isArray(txids)) {
|
||||
@@ -1418,7 +1427,7 @@ class Slp {
|
||||
const url = `${process.env.SLPDB_WHITELIST_URL}q/${b64}`
|
||||
// console.log('url: ', url)
|
||||
|
||||
const options = _this.generateCredentials()
|
||||
const options = _this.generateCredentialsWL()
|
||||
|
||||
// Get data from SLPDB.
|
||||
const opt = {
|
||||
@@ -1561,7 +1570,7 @@ class Slp {
|
||||
const b64 = Buffer.from(s).toString('base64')
|
||||
const url = `${process.env.SLPDB_URL}q/${b64}`
|
||||
|
||||
const options = _this.generateCredentials()
|
||||
const options = _this.generateCredentialsGP()
|
||||
const opt = {
|
||||
method: 'get',
|
||||
baseURL: url,
|
||||
@@ -1721,11 +1730,32 @@ class Slp {
|
||||
}
|
||||
|
||||
// Generates a Basic Authorization header for slpserve.
|
||||
generateCredentials () {
|
||||
generateCredentialsGP () {
|
||||
// Generate the Basic Authentication header for a private instance of SLPDB.
|
||||
const username = 'BITBOX'
|
||||
const password = SLPDB_PASS
|
||||
const password = SLPDB_PASS_GP
|
||||
const combined = `${username}:${password}`
|
||||
// console.log(`combined: ${combined}`)
|
||||
var base64Credential = Buffer.from(combined).toString('base64')
|
||||
var readyCredential = `Basic ${base64Credential}`
|
||||
|
||||
const options = {
|
||||
headers: {
|
||||
authorization: readyCredential
|
||||
},
|
||||
timeout: 15000
|
||||
}
|
||||
|
||||
return options
|
||||
}
|
||||
|
||||
// Generates a Basic Authorization header for slpserve.
|
||||
generateCredentialsWL () {
|
||||
// Generate the Basic Authentication header for a private instance of SLPDB.
|
||||
const username = 'BITBOX'
|
||||
const password = SLPDB_PASS_WL
|
||||
const combined = `${username}:${password}`
|
||||
// console.log(`combined: ${combined}`)
|
||||
var base64Credential = Buffer.from(combined).toString('base64')
|
||||
var readyCredential = `Basic ${base64Credential}`
|
||||
|
||||
@@ -1895,6 +1925,8 @@ class Slp {
|
||||
})
|
||||
}
|
||||
|
||||
// console.log('sendQty: ', sendQty)
|
||||
// console.log(`tokenUtxos: `, tokenUtxos)
|
||||
const opReturn = await _this.bchjs.SLP.TokenType1.generateSendOpReturn(
|
||||
tokenUtxos,
|
||||
sendQty
|
||||
@@ -1906,6 +1938,7 @@ class Slp {
|
||||
res.status(200)
|
||||
return res.json({ script, outputs: opReturn.outputs })
|
||||
} catch (err) {
|
||||
console.log('err: ', err)
|
||||
wlogger.error('Error in slp.js/generateSendOpReturn().', err)
|
||||
|
||||
// Decode the error message.
|
||||
@@ -1946,6 +1979,17 @@ class Slp {
|
||||
try {
|
||||
const utxos = req.body.utxos
|
||||
|
||||
// console.log('req: ', req)
|
||||
console.log(`req._remoteAddress: ${req._remoteAddress}`)
|
||||
// const ip = req._remoteAddress
|
||||
|
||||
const usrObj = {
|
||||
ip: req._remoteAddress,
|
||||
jwtToken: req.locals.jwtToken,
|
||||
proLimit: req.locals.proLimit,
|
||||
apiLevel: req.locals.apiLevel
|
||||
}
|
||||
|
||||
// Validate inputs
|
||||
if (!Array.isArray(utxos)) {
|
||||
res.status(422)
|
||||
@@ -1980,8 +2024,8 @@ class Slp {
|
||||
const theseUtxos = utxos[i].utxos
|
||||
|
||||
// Get SLP token details.
|
||||
const details = await _this.bchjs.SLP.Utils.tokenUtxoDetails(theseUtxos)
|
||||
// console.log('details : ', details)
|
||||
const details = await _this.bchjs.SLP.Utils.tokenUtxoDetails(theseUtxos, usrObj)
|
||||
// console.log('details: ', details)
|
||||
|
||||
// Replace the original UTXO data with the hydrated data.
|
||||
utxos[i].utxos = details
|
||||
@@ -1995,9 +2039,11 @@ class Slp {
|
||||
|
||||
// Decode the error message.
|
||||
const { msg, status } = routeUtils.decodeError(err)
|
||||
console.log('msg: ', msg)
|
||||
console.log('status: ', status)
|
||||
if (msg) {
|
||||
res.status(status)
|
||||
return res.json({ error: msg })
|
||||
return res.json({ error: msg, message: msg, success: false })
|
||||
}
|
||||
|
||||
res.status(500)
|
||||
@@ -2006,6 +2052,135 @@ class Slp {
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @api {post} /slp/hydrateUtxosWL/ hydrateUtxosWL
|
||||
* @apiName SLP hydrateUtxosWL
|
||||
* @apiGroup SLP
|
||||
* @apiDescription Hydrate UTXO data with SLP information, using only the whitelist SLPDB.
|
||||
*
|
||||
* This call is identical to `hydrateUtxos`, except it will only use the
|
||||
* filtered SLPDB with a whitelist. This results in faster performance, more
|
||||
* reliable uptime, but more frequent `isValid: null` values. Some use-cases
|
||||
* prioritize the speed and reliability over acceptance of a wide range of
|
||||
* SLP tokens.
|
||||
*
|
||||
* @apiExample Example usage:
|
||||
* curl -X POST "https://api.fullstack.cash/v4/slp/hydrateUtxosWL" -H "accept:application/json" -H "Content-Type: application/json" -d '{"utxos":[{"utxos":[{"txid": "d56a2b446d8149c39ca7e06163fe8097168c3604915f631bc58777d669135a56","vout": 3, "value": "6816", "height": 606848, "confirmations": 13, "satoshis": 6816}, {"txid": "d56a2b446d8149c39ca7e06163fe8097168c3604915f631bc58777d669135a56","vout": 2, "value": "546", "height": 606848, "confirmations": 13, "satoshis": 546}]}]}'
|
||||
*
|
||||
*
|
||||
*/
|
||||
async hydrateUtxosWL (req, res, next) {
|
||||
try {
|
||||
const utxos = req.body.utxos
|
||||
|
||||
// Validate inputs
|
||||
if (!Array.isArray(utxos)) {
|
||||
res.status(422)
|
||||
return res.json({
|
||||
error: 'Input must be an array.'
|
||||
})
|
||||
}
|
||||
|
||||
if (!utxos.length) {
|
||||
res.status(422)
|
||||
return res.json({
|
||||
error: 'Array should not be empty'
|
||||
})
|
||||
}
|
||||
|
||||
if (utxos.length > 20) {
|
||||
res.status(422)
|
||||
return res.json({
|
||||
error: 'Array too long, max length is 20'
|
||||
})
|
||||
}
|
||||
|
||||
if (!utxos[0].utxos) {
|
||||
res.status(422)
|
||||
return res.json({
|
||||
error: 'Each element in array should have a utxos property'
|
||||
})
|
||||
}
|
||||
|
||||
// Loop through each address and query the UTXOs for that element.
|
||||
for (let i = 0; i < utxos.length; i++) {
|
||||
const theseUtxos = utxos[i].utxos
|
||||
// console.log(`theseUtxos: ${JSON.stringify(theseUtxos, null, 2)}`)
|
||||
|
||||
// Get SLP token details.
|
||||
const details = await _this.bchjs.SLP.Utils.tokenUtxoDetailsWL(theseUtxos)
|
||||
// console.log('details : ', details)
|
||||
|
||||
// Replace the original UTXO data with the hydrated data.
|
||||
utxos[i].utxos = details
|
||||
}
|
||||
|
||||
res.status(200)
|
||||
return res.json({ slpUtxos: utxos })
|
||||
} catch (err) {
|
||||
wlogger.error('Error in slp.js/hydrateUtxosWL().', err)
|
||||
console.error('Error in slp.js/hydrateUtxosWL().', err)
|
||||
|
||||
// Decode the error message.
|
||||
const { msg, status } = routeUtils.decodeError(err)
|
||||
console.log('msg: ', msg)
|
||||
console.log('status: ', status)
|
||||
if (msg) {
|
||||
res.status(status)
|
||||
return res.json({ error: msg, message: msg, success: false })
|
||||
}
|
||||
|
||||
res.status(500)
|
||||
return res.json({
|
||||
error: 'Error in hydrateUtxosWL()',
|
||||
message: 'Error in hydrateUtxosWL()'
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @api {get} /slp/status Get the health status of SLPDB
|
||||
* @apiName Get the health status of SLPDB
|
||||
* @apiGroup SLP
|
||||
* @apiDescription Get the health status of SLPDB
|
||||
*
|
||||
*
|
||||
* @apiExample Example usage:
|
||||
* curl -X GET "https://api.fullstack.cash/v4/slp/status" -H "accept:application/json" -H "Content-Type: application/json"
|
||||
*
|
||||
*/
|
||||
async getStatus (req, res, next) {
|
||||
try {
|
||||
const query = {
|
||||
v: 3,
|
||||
q: {
|
||||
db: ['s'],
|
||||
find: { context: 'SLPDB' },
|
||||
limit: 10
|
||||
}
|
||||
}
|
||||
|
||||
const s = JSON.stringify(query)
|
||||
const b64 = Buffer.from(s).toString('base64')
|
||||
const url = `${process.env.SLPDB_URL}q/${b64}`
|
||||
// Request options
|
||||
const opt = {
|
||||
method: 'get',
|
||||
baseURL: url
|
||||
}
|
||||
const tokenRes = await _this.axios.request(opt)
|
||||
|
||||
const status = tokenRes.data.s[0]
|
||||
|
||||
res.status(200)
|
||||
return res.json(status)
|
||||
} catch (err) {
|
||||
// console.log(err)
|
||||
wlogger.error('Error in slp.js/getStatus().', err)
|
||||
return _this.errorHandler(err, res)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = Slp
|
||||
|
||||
@@ -154,8 +154,17 @@ class RouteUtils {
|
||||
}
|
||||
}
|
||||
|
||||
// Handle general Error objects.
|
||||
if (err.message) {
|
||||
return {
|
||||
msg: err.message,
|
||||
status: 422
|
||||
}
|
||||
}
|
||||
|
||||
return { msg: false, status: 500 }
|
||||
} catch (err) {
|
||||
console.error('unhandled error in route-utils.js/decodeError(): ', err)
|
||||
wlogger.error('unhandled error in route-utils.js/decodeError(): ', err)
|
||||
return { msg: false, status: 500 }
|
||||
}
|
||||
|
||||
@@ -11,13 +11,19 @@ require('winston-daily-rotate-file')
|
||||
|
||||
var NETWORK = process.env.NETWORK
|
||||
|
||||
// Default 1 Megabyte
|
||||
var LOG_MAX_SIZE = process.env.LOG_MAX_SIZE ? process.env.LOG_MAX_SIZE : '1m'
|
||||
|
||||
// Default 5 days.
|
||||
var LOG_MAX_FILES = process.env.LOG_MAX_FILES ? process.env.LOG_MAX_FILES : '5d'
|
||||
|
||||
// Configure daily-rotation transport.
|
||||
var transport = new winston.transports.DailyRotateFile({
|
||||
filename: `${__dirname}/../../logs/rest-${NETWORK}-%DATE%.log`,
|
||||
datePattern: 'YYYY-MM-DD',
|
||||
zippedArchive: false,
|
||||
maxSize: '1m',
|
||||
maxFiles: '5d',
|
||||
maxSize: LOG_MAX_SIZE,
|
||||
maxFiles: LOG_MAX_FILES,
|
||||
format: winston.format.combine(
|
||||
winston.format.timestamp(),
|
||||
winston.format.json()
|
||||
|
||||
+40
-13
@@ -1,37 +1,64 @@
|
||||
#!/bin/bash
|
||||
|
||||
# Full node
|
||||
export RPC_BASEURL=http://<full node ip>:8332/
|
||||
export RPC_USERNAME=<RPC username>
|
||||
export RPC_PASSWORD=<RPC password>
|
||||
# Docker containers for the below infrastructure is described here:
|
||||
# https://psfoundation.cash/blog/cash-stack
|
||||
# Pre-synced databases can be downloaded on the CashStrap page:
|
||||
# https://fullstack.cash/cashstrap
|
||||
|
||||
# Which network are you using?
|
||||
export NETWORK=mainnet
|
||||
#export NETWORK=testnet
|
||||
|
||||
# Full node
|
||||
export RPC_IP=<ip>:8332
|
||||
export RPC_BASEURL=http://$RPC_IP/
|
||||
export RPC_USERNAME=bitcoin
|
||||
export RPC_PASSWORD=password
|
||||
|
||||
# SLPDB
|
||||
export SLPDB_PASS_GP=somelongpassword
|
||||
export SLPDB_URL=http://<SLPDB IP>:12300/
|
||||
export SLPDB_PASS=somelongpassword
|
||||
|
||||
# Use the same address as SLPDB_URL if you don't have a separate whitelist server.
|
||||
export SLPDB_PASS_WL=somelongpassword
|
||||
export SLPDB_WHITELIST_URL=http://<SLPDB IP>:12300/
|
||||
|
||||
# slp-api alternative SLP validator using slp-validate:
|
||||
# https://github.com/Permissionless-Software-Foundation/slp-api
|
||||
export SLP_API_URL=http://10.0.0.5:5001/
|
||||
|
||||
# Blockbook
|
||||
export BLOCKBOOK_URL=https://<Blockbook IP>:9131/
|
||||
# Allow node.js to make network calls to https using self-signed certificate.
|
||||
export NODE_TLS_REJECT_UNAUTHORIZED=0
|
||||
|
||||
# Mainnet Fulcrum / ElectrumX
|
||||
export FULCRUM_URL=192.168.0.6
|
||||
export FULCRUM_PORT=50002
|
||||
|
||||
# Redis DB - Used for rate limiting - customize to your own Redis installation.
|
||||
export REDIS_PORT=6379
|
||||
export REDIS_HOST=172.17.0.1
|
||||
|
||||
# JWT Token Secret
|
||||
# This is used to verify JWT tokens generated with jwt-bch-api:
|
||||
# https://github.com/Permissionless-Software-Foundation/jwt-bch-api
|
||||
export TOKENSECRET=somelongpassword
|
||||
|
||||
# So that bch-api can call bch-js locally.
|
||||
export LOCAL_RESTURL=http://127.0.0.1:3000/v3/
|
||||
export LOCAL_RESTURL=http://127.0.0.1:3000/v4/
|
||||
|
||||
# Basic Authentication password
|
||||
export PRO_PASS=somerandomepassword:someotherrandompassword:aThirdPassword
|
||||
|
||||
# Whitelisted domains. Automatically give pro-tier rate limit access to apps
|
||||
# that originate froma domain on the whitelist.
|
||||
export WHITELIST_DOMAINS=fullstack.cash,psfoundation.cash,torlist.cash
|
||||
|
||||
# Rate Limits. Numbers are divided into 1000. e.g. 1000 / 50 = 20 RPM for ANON.
|
||||
# Requests use the ANON rate limit if they fail to pass in a JWT token.
|
||||
# ANON = 20 requests per minute (RPM)
|
||||
export ANON_RATE_LIMIT=50
|
||||
# 10 = 100 RPM
|
||||
export WHITELIST_RATE_LIMIT=10
|
||||
|
||||
# Set logging parameters
|
||||
#1m means no more than 1 megabyte
|
||||
export LOG_MAX_SIZE=1m
|
||||
#5d means store no more than 5 days
|
||||
export LOG_MAX_FILES=5d
|
||||
|
||||
npm start
|
||||
|
||||
@@ -199,11 +199,11 @@ describe('#Electrumx', () => {
|
||||
})
|
||||
|
||||
describe('#getUtxos', () => {
|
||||
it('should throw 400 if address is empty', async () => {
|
||||
it('should throw 422 if address is empty', async () => {
|
||||
const result = await electrumxRoute.getUtxos(req, res)
|
||||
// console.log(`result: ${util.inspect(result)}`)
|
||||
|
||||
assert.equal(res.statusCode, 400, 'Expect 400 status code')
|
||||
assert.equal(res.statusCode, 422, 'Expect 422 status code')
|
||||
|
||||
assert.property(result, 'error')
|
||||
assert.include(result.error, 'Unsupported address format')
|
||||
@@ -233,7 +233,7 @@ describe('#Electrumx', () => {
|
||||
const result = await electrumxRoute.getUtxos(req, res)
|
||||
// console.log(`result: ${util.inspect(result)}`)
|
||||
|
||||
assert.equal(res.statusCode, 400, 'Expect 400 status code')
|
||||
assert.equal(res.statusCode, 422, 'Expect 422 status code')
|
||||
|
||||
assert.property(result, 'error')
|
||||
assert.include(result.error, 'Unsupported address format')
|
||||
@@ -1063,7 +1063,7 @@ describe('#Electrumx', () => {
|
||||
const result = await electrumxRoute.getBalance(req, res)
|
||||
// console.log(`result: ${util.inspect(result)}`)
|
||||
|
||||
assert.equal(res.statusCode, 400, 'Expect 400 status code')
|
||||
assert.equal(res.statusCode, 422, 'Expect 422 status code')
|
||||
|
||||
assert.property(result, 'error')
|
||||
assert.include(result.error, 'Unsupported address format')
|
||||
@@ -1093,7 +1093,7 @@ describe('#Electrumx', () => {
|
||||
const result = await electrumxRoute.getBalance(req, res)
|
||||
// console.log(`result: ${util.inspect(result)}`)
|
||||
|
||||
assert.equal(res.statusCode, 400, 'Expect 400 status code')
|
||||
assert.equal(res.statusCode, 422, 'Expect 422 status code')
|
||||
|
||||
assert.property(result, 'error')
|
||||
assert.include(result.error, 'Unsupported address format')
|
||||
@@ -1350,7 +1350,7 @@ describe('#Electrumx', () => {
|
||||
const result = await electrumxRoute.getTransactions(req, res)
|
||||
// console.log(`result: ${util.inspect(result)}`)
|
||||
|
||||
assert.equal(res.statusCode, 400, 'Expect 400 status code')
|
||||
assert.equal(res.statusCode, 422, 'Expect 422 status code')
|
||||
|
||||
assert.property(result, 'error')
|
||||
assert.include(result.error, 'Unsupported address format')
|
||||
@@ -1380,7 +1380,7 @@ describe('#Electrumx', () => {
|
||||
const result = await electrumxRoute.getTransactions(req, res)
|
||||
// console.log(`result: ${util.inspect(result)}`)
|
||||
|
||||
assert.equal(res.statusCode, 400, 'Expect 400 status code')
|
||||
assert.equal(res.statusCode, 422, 'Expect 422 status code')
|
||||
|
||||
assert.property(result, 'error')
|
||||
assert.include(result.error, 'Unsupported address format')
|
||||
@@ -1645,7 +1645,7 @@ describe('#Electrumx', () => {
|
||||
const result = await electrumxRoute.getMempool(req, res)
|
||||
// console.log(`result: ${util.inspect(result)}`)
|
||||
|
||||
assert.equal(res.statusCode, 400, 'Expect 400 status code')
|
||||
assert.equal(res.statusCode, 422, 'Expect 422 status code')
|
||||
|
||||
assert.property(result, 'error')
|
||||
assert.include(result.error, 'Unsupported address format')
|
||||
@@ -1675,7 +1675,7 @@ describe('#Electrumx', () => {
|
||||
const result = await electrumxRoute.getMempool(req, res)
|
||||
// console.log(`result: ${util.inspect(result)}`)
|
||||
|
||||
assert.equal(res.statusCode, 400, 'Expect 400 status code')
|
||||
assert.equal(res.statusCode, 422, 'Expect 422 status code')
|
||||
|
||||
assert.property(result, 'error')
|
||||
assert.include(result.error, 'Unsupported address format')
|
||||
|
||||
@@ -7,21 +7,22 @@
|
||||
TODO:
|
||||
-/rawtransactions/sendRawTransaction is more appropropriate for an e2e test,
|
||||
so it is omitted here.
|
||||
- Replace request-promise with axios.
|
||||
*/
|
||||
|
||||
'use strict'
|
||||
|
||||
const chai = require('chai')
|
||||
const assert = chai.assert
|
||||
// const chai = require('chai')
|
||||
// const assert = chai.assert
|
||||
// const rawtransactions = require('../../../dist/routes/v2/rawtransactions')
|
||||
const rp = require('request-promise')
|
||||
// const rp = require('request-promise')
|
||||
|
||||
// Used for debugging.
|
||||
const util = require('util')
|
||||
util.inspect.defaultOptions = { depth: 1 }
|
||||
|
||||
// const mockData = require('../mocks/raw-transactions-mocks')
|
||||
|
||||
/*
|
||||
describe('#Raw-Transactions', () => {
|
||||
describe('#root', () => {
|
||||
it('should return root', async () => {
|
||||
@@ -189,3 +190,4 @@ describe('#Raw-Transactions', () => {
|
||||
})
|
||||
})
|
||||
})
|
||||
*/
|
||||
|
||||
@@ -110,8 +110,8 @@ describe('#slp', () => {
|
||||
req.body.tokenUtxos = [
|
||||
{
|
||||
tokenId:
|
||||
'0a321bff9761f28e06a268b14711274bb77617410a16807bd0437ef234a072b1',
|
||||
decimals: 0,
|
||||
'38e97c5d7d3585a2cbf3f9580c82ca33985f9cb0845d4dcce220cb709f9538b0',
|
||||
decimals: 8,
|
||||
tokenQty: 2
|
||||
}
|
||||
]
|
||||
@@ -214,6 +214,39 @@ describe('#slp', () => {
|
||||
})
|
||||
})
|
||||
|
||||
describe('#hydrateUtxosWL', () => {
|
||||
it('should return utxo details', async () => {
|
||||
const utxos = [
|
||||
{
|
||||
utxos: [
|
||||
{
|
||||
tx_hash:
|
||||
'89b3f0c84efe8b01b24e2d7ac08636de5781f31dbb84478e3de868ca0a7ed93a',
|
||||
tx_pos: 1,
|
||||
value: 546
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
|
||||
req.body.utxos = utxos
|
||||
const result = await slp.hydrateUtxosWL(req, res)
|
||||
// console.log(`result: ${JSON.stringify(result, null, 2)}`)
|
||||
|
||||
// Test the general structure of the output.
|
||||
assert.isArray(result.slpUtxos)
|
||||
assert.equal(result.slpUtxos.length, 1)
|
||||
assert.equal(result.slpUtxos[0].utxos.length, 1)
|
||||
|
||||
// Test the non-slp UTXO.
|
||||
assert.property(result.slpUtxos[0].utxos[0], 'tx_hash')
|
||||
assert.property(result.slpUtxos[0].utxos[0], 'tx_pos')
|
||||
assert.property(result.slpUtxos[0].utxos[0], 'value')
|
||||
assert.property(result.slpUtxos[0].utxos[0], 'isValid')
|
||||
assert.equal(result.slpUtxos[0].utxos[0].isValid, true)
|
||||
})
|
||||
})
|
||||
|
||||
describe('#validate2Single', () => {
|
||||
it('should invalidate a known invalid TXID', async () => {
|
||||
const txid =
|
||||
@@ -416,4 +449,13 @@ describe('#slp', () => {
|
||||
}
|
||||
})
|
||||
})
|
||||
|
||||
describe('#getStatus', () => {
|
||||
it('should get the SLPDB status', async () => {
|
||||
const result = await slp.getStatus(req, res)
|
||||
// console.log(`result: ${JSON.stringify(result, null, 2)}`)
|
||||
|
||||
assert.property(result, 'bchBlockHeight')
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
+407
-1
@@ -614,6 +614,411 @@ const mockValidate3Bulk = {
|
||||
u: []
|
||||
}
|
||||
|
||||
const mockStatus = {
|
||||
_id: '5fe408b7cd2bd2000f7fdd0a',
|
||||
version: '1.0.0-beta-rc13',
|
||||
versionHash: 'bb4a805610b9e4d67c1595b716df36190c75e8b1',
|
||||
deplVersionHash: null,
|
||||
startCmd: 'node index run',
|
||||
context: 'SLPDB',
|
||||
lastStatusUpdate: {
|
||||
utc: 'Thu, 24 Dec 2020 03:19:19 GMT',
|
||||
unix: 1608779959
|
||||
},
|
||||
lastIncomingTxnZmq: {
|
||||
utc: 'Thu, 24 Dec 2020 03:19:16 GMT',
|
||||
unix: 1608779956
|
||||
},
|
||||
lastIncomingBlockZmq: {
|
||||
utc: 'Thu, 24 Dec 2020 03:00:27 GMT',
|
||||
unix: 1608778827
|
||||
},
|
||||
lastOutgoingTxnZmq: null,
|
||||
lastOutgoingBlockZmq: null,
|
||||
state: 'RUNNING',
|
||||
stateHistory: [
|
||||
{
|
||||
utc: 'Mon, 07 Dec 2020 15:26:23 GMT',
|
||||
state: 'STARTUP_BLOCK_SYNC'
|
||||
},
|
||||
{
|
||||
utc: 'Mon, 07 Dec 2020 15:52:20 GMT',
|
||||
state: 'RUNNING'
|
||||
},
|
||||
{
|
||||
utc: 'Tue, 08 Dec 2020 14:42:29 GMT',
|
||||
state: 'PRE_STARTUP'
|
||||
},
|
||||
{
|
||||
utc: 'Tue, 08 Dec 2020 14:42:29 GMT',
|
||||
state: 'STARTUP_BLOCK_SYNC'
|
||||
},
|
||||
{
|
||||
utc: 'Tue, 08 Dec 2020 14:59:53 GMT',
|
||||
state: 'RUNNING'
|
||||
},
|
||||
{
|
||||
utc: 'Tue, 08 Dec 2020 19:35:36 GMT',
|
||||
state: 'EXITED_ON_ERROR'
|
||||
},
|
||||
{
|
||||
utc: 'Tue, 08 Dec 2020 19:35:36 GMT',
|
||||
state: 'EXITED_ON_ERROR'
|
||||
},
|
||||
{
|
||||
utc: 'Tue, 08 Dec 2020 19:35:49 GMT',
|
||||
state: 'PRE_STARTUP'
|
||||
},
|
||||
{
|
||||
utc: 'Tue, 08 Dec 2020 19:35:49 GMT',
|
||||
state: 'STARTUP_BLOCK_SYNC'
|
||||
},
|
||||
{
|
||||
utc: 'Tue, 08 Dec 2020 19:53:04 GMT',
|
||||
state: 'RUNNING'
|
||||
}
|
||||
],
|
||||
network: 'mainnet',
|
||||
bchBlockHeight: 667203,
|
||||
bchBlockHash:
|
||||
'0000000000000000035f9c26b9ef3db37eba8c9351218c67c92d80444b867858',
|
||||
slpProcessedBlockHeight: 667203,
|
||||
mempoolInfoBch: {
|
||||
loaded: true,
|
||||
size: 441,
|
||||
bytes: 192651,
|
||||
usage: 663056,
|
||||
maxmempool: 300000000,
|
||||
mempoolminfee: 0.00001,
|
||||
minrelaytxfee: 0.00001
|
||||
},
|
||||
mempoolSizeSlp: 68,
|
||||
tokensCount: 77879,
|
||||
pastStackTraces: [
|
||||
'[Tue, 08 Dec 2020 19:35:36 GMT] MongoServerSelectionError: connection <monitor> to 172.17.0.1:12301 timed out\n at Timeout._onTimeout (/home/safeuser/SLPDB/node_modules/mongodb/lib/core/sdam/topology.js:438:30)\n at listOnTimeout (internal/timers.js:554:17)\n at processTimers (internal/timers.js:497:7)',
|
||||
'[Tue, 08 Dec 2020 19:35:36 GMT] MongoServerSelectionError: connection <monitor> to 172.17.0.1:12301 timed out\n at Timeout._onTimeout (/home/safeuser/SLPDB/node_modules/mongodb/lib/core/sdam/topology.js:438:30)\n at listOnTimeout (internal/timers.js:554:17)\n at processTimers (internal/timers.js:497:7)'
|
||||
],
|
||||
doubleSpends: [
|
||||
{
|
||||
txo:
|
||||
'3e377f67e9d065f02e47633d847cdb04b0679d51baccdc0feea5297529e330b5:820',
|
||||
details: {
|
||||
originalTxid:
|
||||
'dc148149d25a546fffe0b207c62b192811c288481df5f43f1f76f67b6004f98c',
|
||||
current:
|
||||
'de12db60e253c378eeebb1df85153168a998db8267ec54b38589cc31348b2b8d',
|
||||
time: {
|
||||
utc: 'Sat, 19 Dec 2020 02:25:37 GMT',
|
||||
unix: 1608344737
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
txo: '4b7e94fc59a685bce31ee9444d20ebb0ae0478111a8bfa7ec9b5c381e2657191:2',
|
||||
details: {
|
||||
originalTxid:
|
||||
'a4e00c025c07a770874ecc6b2d1004fe3c561f2aceb0a8b95996e26709df1a57',
|
||||
current:
|
||||
'33ccab4a971331897bb3ff3978366e3575c54d83ae1a35984e50d93cffe66b95',
|
||||
time: {
|
||||
utc: 'Sat, 19 Dec 2020 02:33:28 GMT',
|
||||
unix: 1608345208
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
txo: 'bd5ac3d02652038cc888922e50c9b57db0dfdbb5b26bb28a2e85fd28493e85e1:2',
|
||||
details: {
|
||||
originalTxid:
|
||||
'53032355ab142ac050f1e37445ecb19ad22c5331185df7b3173e88563194ba18',
|
||||
current:
|
||||
'0b3ab983c5abb1ceb6f60035ff869e159e27ab5d2a4173d0d6ce027348d4dbdd',
|
||||
time: {
|
||||
utc: 'Sat, 19 Dec 2020 04:21:27 GMT',
|
||||
unix: 1608351687
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
txo:
|
||||
'8d91432598943cc22b93d10df6808abccba4baebdf2273b375ce3616a2001c07:573',
|
||||
details: {
|
||||
originalTxid:
|
||||
'53032355ab142ac050f1e37445ecb19ad22c5331185df7b3173e88563194ba18',
|
||||
current:
|
||||
'7ddc241b62625ddc1d134ae17e74bf2b6f63a4c28e3c3a4f11d833270bdebcdb',
|
||||
time: {
|
||||
utc: 'Sat, 19 Dec 2020 04:26:12 GMT',
|
||||
unix: 1608351972
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
txo: 'ff3a9268952408217f60f1689aa1137e929e6572413e429554996ec3e9b223b5:2',
|
||||
details: {
|
||||
originalTxid:
|
||||
'342adc882eff630aa7b50098adaace3626c1e60acbdf937e41832c4508bbdb4b',
|
||||
current:
|
||||
'01538a27e7e164bd52a9fd3df36cd724846f44a4c26aea8764ee982486b580ff',
|
||||
time: {
|
||||
utc: 'Sat, 19 Dec 2020 12:58:36 GMT',
|
||||
unix: 1608382716
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
txo:
|
||||
'9f7de7ea735d46fbb9214353fec0b250f48a24340e0909eecd83f53817b6aaa3:314',
|
||||
details: {
|
||||
originalTxid:
|
||||
'342adc882eff630aa7b50098adaace3626c1e60acbdf937e41832c4508bbdb4b',
|
||||
current:
|
||||
'01538a27e7e164bd52a9fd3df36cd724846f44a4c26aea8764ee982486b580ff',
|
||||
time: {
|
||||
utc: 'Sat, 19 Dec 2020 12:58:36 GMT',
|
||||
unix: 1608382716
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
txo: 'ff28ad4063faa250797fa39c823b919d157ecae82b8bf68199eb0b9babbb0c12:2',
|
||||
details: {
|
||||
originalTxid:
|
||||
'eced12b6e07e4d830edf6d3244055ba22e953065070985755c9e7f9ea7626d1d',
|
||||
current:
|
||||
'512073e40e033106c45af47acfd05092b0206ac276bda459f76dfa019a0e257c',
|
||||
time: {
|
||||
utc: 'Sat, 19 Dec 2020 12:58:38 GMT',
|
||||
unix: 1608382718
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
txo:
|
||||
'9f7de7ea735d46fbb9214353fec0b250f48a24340e0909eecd83f53817b6aaa3:316',
|
||||
details: {
|
||||
originalTxid:
|
||||
'53cffad1ffd9de20355f89ee0310cd39876c21e2d2a9d5b09548286de1e78565',
|
||||
current:
|
||||
'a988c9f46f97de27eccd4f50e1bba751cdc9c49a6cac31de2f4dbf3763fb4ab3',
|
||||
time: {
|
||||
utc: 'Sat, 19 Dec 2020 12:58:45 GMT',
|
||||
unix: 1608382725
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
txo:
|
||||
'1a57786c472034b2d3d944a3cad69447c30ed5de46bd708ddead33f946ea2578:10',
|
||||
details: {
|
||||
originalTxid:
|
||||
'eced12b6e07e4d830edf6d3244055ba22e953065070985755c9e7f9ea7626d1d',
|
||||
current:
|
||||
'8ef975bd11d82c3dfb6645cc15eeaecebde23423b6506daa5145d3b20c02fab5',
|
||||
time: {
|
||||
utc: 'Sat, 19 Dec 2020 13:01:30 GMT',
|
||||
unix: 1608382890
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
txo:
|
||||
'9f7de7ea735d46fbb9214353fec0b250f48a24340e0909eecd83f53817b6aaa3:317',
|
||||
details: {
|
||||
originalTxid:
|
||||
'5bf0b00b41a9cc1068d8573f0fb65e875743ce3ea5a3e521fff960da9223d508',
|
||||
current:
|
||||
'4f3bf7452f5409964cdb9f68a493b2c1f89dd16fc6eb1cc6d2b1dc753c7bfb59',
|
||||
time: {
|
||||
utc: 'Sat, 19 Dec 2020 13:04:46 GMT',
|
||||
unix: 1608383086
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
txo:
|
||||
'1a57786c472034b2d3d944a3cad69447c30ed5de46bd708ddead33f946ea2578:11',
|
||||
details: {
|
||||
originalTxid:
|
||||
'f1973c96dabf923f7f1e65630366921da9a806316f3e4f2478cbd754d52f6c70',
|
||||
current:
|
||||
'76493aaa8bdcb21bb45452250f9518674ea94b3e70fb890252278c9f8bfe3cef',
|
||||
time: {
|
||||
utc: 'Sat, 19 Dec 2020 13:06:31 GMT',
|
||||
unix: 1608383191
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
txo:
|
||||
'1a57786c472034b2d3d944a3cad69447c30ed5de46bd708ddead33f946ea2578:12',
|
||||
details: {
|
||||
originalTxid:
|
||||
'15b22f6c97a3390c13ae79a61eb9beb34ab87d2245bf8e63d91d7b2477ccdf00',
|
||||
current:
|
||||
'6e529eeecd268b1763467152ff76a427c80a26750229de3ad0edbfb2b40ee43a',
|
||||
time: {
|
||||
utc: 'Sat, 19 Dec 2020 13:07:37 GMT',
|
||||
unix: 1608383257
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
txo: 'c75126b129584314d7657ce7140f4aef64ff410053a3d9017a13307b778d4e16:2',
|
||||
details: {
|
||||
originalTxid:
|
||||
'cf2234e2a71a5ae7cf525c2ced332c16bde86734d841649913b4ce698a43506d',
|
||||
current:
|
||||
'f98a430d9772962353cb15a58d447d3de056abde41730b92d9301d7510d1d04c',
|
||||
time: {
|
||||
utc: 'Sat, 19 Dec 2020 14:52:28 GMT',
|
||||
unix: 1608389548
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
txo:
|
||||
'9f7de7ea735d46fbb9214353fec0b250f48a24340e0909eecd83f53817b6aaa3:343',
|
||||
details: {
|
||||
originalTxid:
|
||||
'cf2234e2a71a5ae7cf525c2ced332c16bde86734d841649913b4ce698a43506d',
|
||||
current:
|
||||
'f98a430d9772962353cb15a58d447d3de056abde41730b92d9301d7510d1d04c',
|
||||
time: {
|
||||
utc: 'Sat, 19 Dec 2020 14:52:28 GMT',
|
||||
unix: 1608389548
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
txo:
|
||||
'22fa91e7961388ec071254c65cccf824d7923a0291af0967394fc7bffb6185c1:25',
|
||||
details: {
|
||||
originalTxid:
|
||||
'5ed601fb5b2c6fee6eb1298671471108cf656f5d19186db484c2a6711f0867c8',
|
||||
current:
|
||||
'a30535841e874fe7ddefe6285bc44e5f932d1866c343b3298448985c428049a1',
|
||||
time: {
|
||||
utc: 'Sat, 19 Dec 2020 18:41:13 GMT',
|
||||
unix: 1608403273
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
txo:
|
||||
'22fa91e7961388ec071254c65cccf824d7923a0291af0967394fc7bffb6185c1:26',
|
||||
details: {
|
||||
originalTxid:
|
||||
'a7015340be3ff27b1773208bf7c4501811e9adfb11363c11a7ac080946fb1cd2',
|
||||
current:
|
||||
'b9959428becca1c0850409a960153a55fa8cacac3ad5c7b0ba67c57e46814baf',
|
||||
time: {
|
||||
utc: 'Sat, 19 Dec 2020 18:42:13 GMT',
|
||||
unix: 1608403333
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
txo:
|
||||
'22fa91e7961388ec071254c65cccf824d7923a0291af0967394fc7bffb6185c1:27',
|
||||
details: {
|
||||
originalTxid:
|
||||
'72435b1d8234d5ca00c6653beec318c3d40f4e902f0fec892f769125bdfc0987',
|
||||
current:
|
||||
'43277c2fffa2a7ff632fd2bdd0a2be950523816c42012fa00efa860ade428127',
|
||||
time: {
|
||||
utc: 'Sat, 19 Dec 2020 18:53:26 GMT',
|
||||
unix: 1608404006
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
txo: 'b46d46d2d8b081cf4dd3fbf4bb1d36c8f22954e92f999a07c730be52006303ee:2',
|
||||
details: {
|
||||
originalTxid:
|
||||
'93aca660517a8ea847ca5bb39dfa1b26ae72f2eabfedc7cb3ef6e804c293c697',
|
||||
current:
|
||||
'377c89f649de123c52ef23a1f0ec844aa6ac032c4ec1242b59ca15e1b64a883d',
|
||||
time: {
|
||||
utc: 'Sat, 19 Dec 2020 20:33:14 GMT',
|
||||
unix: 1608409994
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
txo:
|
||||
'9f7de7ea735d46fbb9214353fec0b250f48a24340e0909eecd83f53817b6aaa3:409',
|
||||
details: {
|
||||
originalTxid:
|
||||
'4f355dfeed4a71ec0fd10688ddb3c03ea10db7629e844adfb033a859f12dc87f',
|
||||
current:
|
||||
'377c89f649de123c52ef23a1f0ec844aa6ac032c4ec1242b59ca15e1b64a883d',
|
||||
time: {
|
||||
utc: 'Sat, 19 Dec 2020 20:33:14 GMT',
|
||||
unix: 1608409994
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
txo: '4bd177144edbf495f614b839c5828ec6c2d21b4a5a478b6324d681b7ecdce976:2',
|
||||
details: {
|
||||
originalTxid:
|
||||
'4f355dfeed4a71ec0fd10688ddb3c03ea10db7629e844adfb033a859f12dc87f',
|
||||
current:
|
||||
'9c480d23e99fc3a31558ffb8a402bc819bf0906ca21351749db4d5fceca263b6',
|
||||
time: {
|
||||
utc: 'Sat, 19 Dec 2020 20:33:16 GMT',
|
||||
unix: 1608409996
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
txo:
|
||||
'190c84d8cb06fe750fcbf0bd11c62e23fa60cd4f7c970a1079ac746e270982be:155',
|
||||
details: {
|
||||
originalTxid:
|
||||
'93aca660517a8ea847ca5bb39dfa1b26ae72f2eabfedc7cb3ef6e804c293c697',
|
||||
current:
|
||||
'75e3759d5167395aa1dca327b9b042eb1b86e893bdffc66c2480e8194c2cdc61',
|
||||
time: {
|
||||
utc: 'Sat, 19 Dec 2020 20:45:04 GMT',
|
||||
unix: 1608410704
|
||||
}
|
||||
}
|
||||
}
|
||||
],
|
||||
reorgs: [],
|
||||
mongoDbStats: {
|
||||
db: 'slpdb',
|
||||
collections: 5,
|
||||
views: 0,
|
||||
objects: 2495075,
|
||||
avgObjSize: 3188.4621913168944,
|
||||
dataSize: 7586.910535812378,
|
||||
storageSize: 3353.39453125,
|
||||
indexes: 71,
|
||||
indexSize: 1490.08203125,
|
||||
totalSize: 4843.4765625,
|
||||
scaleFactor: 1048576,
|
||||
fsUsedSize: 74352.5859375,
|
||||
fsTotalSize: 153676.98046875,
|
||||
ok: 1
|
||||
},
|
||||
publicUrl: 'fullstack--bchn-02',
|
||||
telemetryHash: null,
|
||||
system: {
|
||||
loadAvg1: 0.04,
|
||||
loadAvg5: 0.03,
|
||||
loadAvg15: 0,
|
||||
platform: 'linux',
|
||||
cpuCount: 8,
|
||||
freeMem: 5832.546875,
|
||||
totalMem: 31360.8828125,
|
||||
uptime: 1603692,
|
||||
processUptime: 1323812.053445836
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
mockList,
|
||||
mockSingleToken,
|
||||
@@ -632,5 +1037,6 @@ module.exports = {
|
||||
mockTxHistory,
|
||||
mockPsfToken,
|
||||
mockValidateBulk,
|
||||
mockValidate3Bulk
|
||||
mockValidate3Bulk,
|
||||
mockStatus
|
||||
}
|
||||
|
||||
+41
-13
@@ -425,24 +425,52 @@ describe('#route-ratelimits & jwt-auth', () => {
|
||||
)
|
||||
})
|
||||
|
||||
it('should handle misconfigured token secret', async () => {
|
||||
// Create a new instance of the rate limit so we start with zeroed tracking.
|
||||
rateLimits = new RateLimits()
|
||||
// CT 2/24/21 This test may have been invalidated by the interal IP address
|
||||
// passing that I implemented to get hydrateUtxos() working properly.
|
||||
// I'm commenting this out until I can study the side effects of this change,
|
||||
// and why exactly this test is breaking.
|
||||
// it('should handle misconfigured token secret', async () => {
|
||||
// // Create a new instance of the rate limit so we start with zeroed tracking.
|
||||
// rateLimits = new RateLimits()
|
||||
//
|
||||
// req.baseUrl = '/v4'
|
||||
// req.path = '/control/getNetworkInfo'
|
||||
// req.url = req.path
|
||||
// req.method = 'GET'
|
||||
//
|
||||
// req.locals.jwtToken = 'some-token'
|
||||
//
|
||||
// next.reset() // reset the stubbed next() function.
|
||||
//
|
||||
// await rateLimits.rateLimitByResource(req, res, next)
|
||||
//
|
||||
// // Issues with token secret should treat incoming requests as anonymous
|
||||
// // calls with 50 points, or 20 RPM.
|
||||
// assert.equal(res.locals.pointsToConsume, 50)
|
||||
// })
|
||||
})
|
||||
|
||||
req.baseUrl = '/v4'
|
||||
req.path = '/control/getNetworkInfo'
|
||||
req.url = req.path
|
||||
req.method = 'GET'
|
||||
describe('#isInWhitelist', () => {
|
||||
it('should return false when no argument is passed in', () => {
|
||||
const result = rateLimits.isInWhitelist()
|
||||
|
||||
req.locals.jwtToken = 'some-token'
|
||||
assert.equal(result, false)
|
||||
})
|
||||
|
||||
next.reset() // reset the stubbed next() function.
|
||||
it('should return false when origin is not in the whitelist', () => {
|
||||
const origin = 'blah.com'
|
||||
|
||||
await rateLimits.rateLimitByResource(req, res, next)
|
||||
const result = rateLimits.isInWhitelist(origin)
|
||||
|
||||
// Issues with token secret should treat incoming requests as anonymous
|
||||
// calls with 30 points or 3 RPM.
|
||||
assert.equal(res.locals.pointsToConsume, 50)
|
||||
assert.equal(result, false)
|
||||
})
|
||||
|
||||
it('should return true when origin is in the whitelist', () => {
|
||||
const origin = 'message.fullstack.cash'
|
||||
|
||||
const result = rateLimits.isInWhitelist(origin)
|
||||
|
||||
assert.equal(result, true)
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
+206
-3
@@ -192,6 +192,7 @@ describe('#SLP', () => {
|
||||
'Error message expected'
|
||||
)
|
||||
})
|
||||
|
||||
it('should get token balance for an address', async () => {
|
||||
// Mock the RPC call for unit tests.
|
||||
if (process.env.TEST === 'unit') {
|
||||
@@ -1286,7 +1287,7 @@ describe('#SLP', () => {
|
||||
})
|
||||
})
|
||||
|
||||
describe('generateSendOpReturn()', () => {
|
||||
describe('#generateSendOpReturn()', () => {
|
||||
const generateSendOpReturn = slpRoute.generateSendOpReturn
|
||||
// Validate tokenUtxos input
|
||||
it('should throw 400 if tokenUtxos is missing', async () => {
|
||||
@@ -1362,8 +1363,8 @@ describe('#SLP', () => {
|
||||
req.body.tokenUtxos = [
|
||||
{
|
||||
tokenId:
|
||||
'0a321bff9761f28e06a268b14711274bb77617410a16807bd0437ef234a072b1',
|
||||
decimals: 0,
|
||||
'38e97c5d7d3585a2cbf3f9580c82ca33985f9cb0845d4dcce220cb709f9538b0',
|
||||
decimals: 8,
|
||||
tokenQty: 2
|
||||
}
|
||||
]
|
||||
@@ -1557,6 +1558,208 @@ describe('#SLP', () => {
|
||||
)
|
||||
})
|
||||
})
|
||||
|
||||
describe('#hydrateUtxosWL', () => {
|
||||
it('should throw error if input is not an array.', async () => {
|
||||
req.body.utxos = 'test'
|
||||
|
||||
const result = await slpRoute.hydrateUtxosWL(req, res)
|
||||
// console.log(`result: `, result)
|
||||
|
||||
assert.hasAllKeys(result, ['error'])
|
||||
assert.include(result.error, 'Input must be an array')
|
||||
})
|
||||
|
||||
it('should throw error if Array is empty', async () => {
|
||||
req.body.utxos = []
|
||||
|
||||
const result = await slpRoute.hydrateUtxosWL(req, res)
|
||||
|
||||
assert.hasAllKeys(result, ['error'])
|
||||
assert.include(result.error, 'Array should not be empty')
|
||||
})
|
||||
|
||||
it('should throw error if Array is too long', async () => {
|
||||
const utxo = {
|
||||
txid:
|
||||
'bd158c564dd4ef54305b14f44f8e94c44b649f246dab14bcb42fb0d0078b8a90',
|
||||
vout: 3,
|
||||
amount: 0.00002015,
|
||||
satoshis: 2015,
|
||||
height: 594892,
|
||||
confirmations: 5
|
||||
}
|
||||
|
||||
const utxos = []
|
||||
|
||||
// Populate array with 21 utxos
|
||||
for (let i = 0; i < 21; i++) {
|
||||
utxos.push(utxo)
|
||||
}
|
||||
|
||||
req.body.utxos = utxos
|
||||
|
||||
const result = await slpRoute.hydrateUtxosWL(req, res)
|
||||
|
||||
assert.hasAllKeys(result, ['error'])
|
||||
assert.include(result.error, 'Array too long, max length is 20')
|
||||
})
|
||||
|
||||
it('should return utxo details', async () => {
|
||||
const utxos = [
|
||||
{
|
||||
utxos: [
|
||||
{
|
||||
txid:
|
||||
'd56a2b446d8149c39ca7e06163fe8097168c3604915f631bc58777d669135a56',
|
||||
vout: 3,
|
||||
value: '6816',
|
||||
height: 606848,
|
||||
confirmations: 13,
|
||||
satoshis: 6816
|
||||
},
|
||||
{
|
||||
txid:
|
||||
'd56a2b446d8149c39ca7e06163fe8097168c3604915f631bc58777d669135a56',
|
||||
vout: 2,
|
||||
value: '546',
|
||||
height: 606848,
|
||||
confirmations: 13,
|
||||
satoshis: 546
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
|
||||
// Mock the external network call.
|
||||
sandbox.stub(slpRoute.bchjs.SLP.Utils, 'tokenUtxoDetailsWL').resolves([
|
||||
{
|
||||
txid:
|
||||
'd56a2b446d8149c39ca7e06163fe8097168c3604915f631bc58777d669135a56',
|
||||
vout: 3,
|
||||
value: '6816',
|
||||
height: 606848,
|
||||
confirmations: 13,
|
||||
satoshis: 6816,
|
||||
isValid: false
|
||||
},
|
||||
{
|
||||
txid:
|
||||
'd56a2b446d8149c39ca7e06163fe8097168c3604915f631bc58777d669135a56',
|
||||
vout: 2,
|
||||
value: '546',
|
||||
height: 606848,
|
||||
confirmations: 13,
|
||||
satoshis: 546,
|
||||
utxoType: 'token',
|
||||
transactionType: 'send',
|
||||
tokenId:
|
||||
'dd84ca78db4d617221b58eabc6667af8fe2f7eadbfcc213d35be9f1b419beb8d',
|
||||
tokenTicker: 'TAP',
|
||||
tokenName: 'Thoughts and Prayers',
|
||||
tokenDocumentUrl: '',
|
||||
tokenDocumentHash: '',
|
||||
decimals: 0,
|
||||
tokenType: 1,
|
||||
tokenQty: 5,
|
||||
isValid: true
|
||||
}
|
||||
])
|
||||
|
||||
req.body.utxos = utxos
|
||||
const result = await slpRoute.hydrateUtxosWL(req, res)
|
||||
// console.log(`result: ${JSON.stringify(result, null, 2)}`)
|
||||
|
||||
// Test the general structure of the output.
|
||||
assert.isArray(result.slpUtxos)
|
||||
assert.equal(result.slpUtxos.length, 1)
|
||||
assert.equal(result.slpUtxos[0].utxos.length, 2)
|
||||
|
||||
// Test the non-slp UTXO.
|
||||
assert.property(result.slpUtxos[0].utxos[0], 'txid')
|
||||
assert.property(result.slpUtxos[0].utxos[0], 'vout')
|
||||
assert.property(result.slpUtxos[0].utxos[0], 'value')
|
||||
assert.property(result.slpUtxos[0].utxos[0], 'height')
|
||||
assert.property(result.slpUtxos[0].utxos[0], 'confirmations')
|
||||
assert.property(result.slpUtxos[0].utxos[0], 'satoshis')
|
||||
assert.property(result.slpUtxos[0].utxos[0], 'isValid')
|
||||
assert.equal(result.slpUtxos[0].utxos[0].isValid, false)
|
||||
|
||||
// Test the slp UTXO.
|
||||
assert.property(result.slpUtxos[0].utxos[1], 'txid')
|
||||
assert.property(result.slpUtxos[0].utxos[1], 'vout')
|
||||
assert.property(result.slpUtxos[0].utxos[1], 'value')
|
||||
assert.property(result.slpUtxos[0].utxos[1], 'height')
|
||||
assert.property(result.slpUtxos[0].utxos[1], 'confirmations')
|
||||
assert.property(result.slpUtxos[0].utxos[1], 'satoshis')
|
||||
assert.property(result.slpUtxos[0].utxos[1], 'isValid')
|
||||
assert.equal(result.slpUtxos[0].utxos[1].isValid, true)
|
||||
assert.property(result.slpUtxos[0].utxos[1], 'transactionType')
|
||||
assert.property(result.slpUtxos[0].utxos[1], 'tokenId')
|
||||
assert.property(result.slpUtxos[0].utxos[1], 'tokenTicker')
|
||||
assert.property(result.slpUtxos[0].utxos[1], 'tokenName')
|
||||
assert.property(result.slpUtxos[0].utxos[1], 'tokenDocumentUrl')
|
||||
assert.property(result.slpUtxos[0].utxos[1], 'tokenDocumentHash')
|
||||
assert.property(result.slpUtxos[0].utxos[1], 'decimals')
|
||||
assert.property(result.slpUtxos[0].utxos[1], 'tokenType')
|
||||
assert.property(result.slpUtxos[0].utxos[1], 'tokenQty')
|
||||
})
|
||||
|
||||
it('should throw error for missing properties', async () => {
|
||||
const utxos = [
|
||||
{
|
||||
height: 639443,
|
||||
tx_hash:
|
||||
'30707fffb9b295a06a68d217f49c198e9e1dbe1edc3874a0928ca1905f1709df',
|
||||
tx_pos: 0,
|
||||
value: 6000
|
||||
},
|
||||
{
|
||||
height: 639443,
|
||||
tx_hash:
|
||||
'8962566e413501224d178a02effc89be5ac0d8e4195f617415d443dc4c38fe50',
|
||||
tx_pos: 1,
|
||||
value: 546
|
||||
}
|
||||
]
|
||||
|
||||
req.body.utxos = utxos
|
||||
const result = await slpRoute.hydrateUtxosWL(req, res)
|
||||
|
||||
assert.hasAllKeys(result, ['error'])
|
||||
assert.include(
|
||||
result.error,
|
||||
'Each element in array should have a utxos property'
|
||||
)
|
||||
})
|
||||
})
|
||||
|
||||
describe('#getStatus', () => {
|
||||
it('should get the SLPDB status', async () => {
|
||||
if (process.env.TEST === 'unit') {
|
||||
// Mock to prevent live network connection.
|
||||
sandbox
|
||||
.stub(slpRoute.axios, 'request')
|
||||
.resolves({ data: { s: [mockData.mockStatus] } })
|
||||
}
|
||||
|
||||
const result = await slpRoute.getStatus(req, res)
|
||||
// console.log(`result: ${JSON.stringify(result, null, 2)}`)
|
||||
|
||||
assert.property(result, 'bchBlockHeight')
|
||||
})
|
||||
|
||||
it('returns proper error when downstream service is down', async () => {
|
||||
// Mock the timeout error.
|
||||
sandbox.stub(slpRoute.axios, 'request').throws({ code: 'ECONNREFUSED' })
|
||||
|
||||
slpRoute.getStatus(req, res)
|
||||
// const result = slpRoute.getStatus(req, res)
|
||||
// console.log(`result: ${JSON.stringify(result, null, 2)}`)
|
||||
|
||||
assert.isAbove(res.statusCode, 499, 'HTTP status code 503 expected.')
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
/*
|
||||
|
||||
Reference in New Issue
Block a user