Compare commits

..
18 Commits
Author SHA1 Message Date
Chris Troutner 88d3dd12a1 Merge pull request #91 from Permissionless-Software-Foundation/ct-unstable
fix(bch-js): Bumping to v4.11.1
2021-02-09 15:17:24 -08:00
Chris Troutner cb8b4a5586 fix(bch-js): Bumping to v4.11.1 2021-02-09 15:15:35 -08:00
Chris Troutner ec0ef24508 fix(comments): Commenting out debug info 2021-02-09 15:07:52 -08:00
Chris Troutner 5a16e69719 Merge pull request #90 from Permissionless-Software-Foundation/ct-unstable
feat(hydrateUtxosWL): SLPDB with whitelist filter used to hydrate UTXOs
2021-02-09 12:20:11 -08:00
Chris Troutner 154dbe8151 feat(hydrateUtxosWL): SLPDB with whitelist filter used to hydrate UTXOs 2021-02-09 12:18:52 -08:00
Chris Troutner 785d3059c3 Merge pull request #89 from Permissionless-Software-Foundation/joey
fix(logging): Adding env vars for configuring logging
2021-01-13 09:27:55 -08:00
Chris Troutner 21b19b59c9 fix(logging): Adding env vars for configuring logging 2021-01-13 09:26:03 -08:00
Chris Troutner 588feff5db Added minor comments 2021-01-13 09:24:28 -08:00
Chris Troutner 9fafc6fba3 Merge pull request #88 from josephroyking/logging-env-var
Add logging storage params as env variables
2021-01-13 09:19:34 -08:00
Joey King 8412da15db Add logging storage params as env variables 2021-01-13 07:51:08 -06:00
Chris Troutner 54c299e7fe Merge pull request #87 from Permissionless-Software-Foundation/ct-unstable
Updating README, shell scripts, and dependencies.
2021-01-11 16:37:10 -08:00
Chris Troutner a06399cfda fix(dependencies): Removed unneeded dependencies. 2021-01-11 16:35:41 -08:00
Chris Troutner e49c488e06 Removing unneeded dependencies 2021-01-11 16:33:49 -08:00
Chris Troutner be07ffab33 Removing unneeded dependencies 2021-01-11 16:29:46 -08:00
Chris Troutner 41a5b24faf Updating README and shell script examples 2021-01-11 16:22:27 -08:00
Chris Troutner 2172d0feff Updating example shell script 2021-01-11 15:33:45 -08:00
Chris Troutner c4664ca173 Merge pull request #86 from Permissionless-Software-Foundation/ct-unstable
fix(bch-js): Bumped to v4.5.4
2021-01-10 15:40:28 -08:00
Chris Troutner 11a7915cb8 fix(bch-js): Bumped to v4.5.4 2021-01-10 15:37:15 -08:00
13 changed files with 20117 additions and 617 deletions
+1 -1
View File
@@ -1,6 +1,6 @@
MIT License 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 Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal of this software and associated documentation files (the "Software"), to deal
+11 -8
View File
@@ -1,6 +1,9 @@
# bch-api # 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. [![License](https://img.shields.io/npm/l/@psf/bch-js)](https://github.com/Permissionless-Software-Foundation/bch-js/blob/master/LICENSE.md)
[![js-standard-style](https://img.shields.io/badge/javascript-standard%20code%20style-green.svg?style=flat-square)](https://github.com/feross/standard)
This is a REST API server, written in node.js JavaScript, using [Express.js](https://expressjs.com/) framework. The purpose of this code is to create a REST API server that provides a common interface for working with a Bitcoin Cash full node and various indexers. See [this article](https://psfoundation.cash/blog/cash-stack) to learn about the 'Cash Stack'. Visit [FullStack.cash](https://fullstack.cash), sign up for a free account, and use this REST API right away with the [bch-js](https://github.com/Permissionless-Software-Foundation/bch-js) JavaScript library.
This repository is intended to be paired with [bch-js](https://github.com/Permissionless-Software-Foundation/bch-js), an npm JavaScript library for building Bitcoin Cash apps. This 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 - Fine grain access is controlled with a JWT token using
[this back end auth server](https://github.com/Permissionless-Software-Foundation/jwt-bch-api) and [this front end](https://github.com/Permissionless-Software-Foundation/jwt-bch-frontend). Can be used to monetize access to the REST API. [this back end auth server](https://github.com/Permissionless-Software-Foundation/jwt-bch-api) and [this front end](https://github.com/Permissionless-Software-Foundation/jwt-bch-frontend). Can be used to monetize access to the REST API.
- Typescript removed and ES8 JavaScript used instead. - Typescript removed and ES8 JavaScript used instead.
- npm audit run on all dependencies. - `npm audit fix` frequently run to fix dependencies.
## Live Demo ## Live Demo
You can test a live demo of the REST API by running the You can test a live demo of the REST API by running the
@@ -31,17 +34,17 @@ You can bootstrap your own REST API server by downloading and installing the inf
### Configure bch-js ### 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: 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/ - BCHN Mainnet REST API server: https://bchn.fullstack.cash/v4/
- ABC Mainnet REST API server: https://abc.fullstack.cash/v3/ - ABC Mainnet REST API server: https://abc.fullstack.cash/v4/
- Testnet3 REST API server: https://testnet3.fullstack.cash/v3/ - Testnet3 REST API server: https://testnet3.fullstack.cash/v4/
- Check server status: https://metrics.fullstack.cash - Check server status: https://metrics.fullstack.cash
## Installation ## Installation
There are two installation paths, depending if you want a *development* or There are two installation paths, depending if you want a *development* or
*production* environment. You'll also need to set up the underlying infrastructure *production* environment. You'll also need to set up the underlying infrastructure
described on [this page](https://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 ### Development
This is a standard node.js project. The installation is as follows: This is a standard node.js project. The installation is as follows:
@@ -86,7 +89,7 @@ Have questions? Need help? Join our community support
## IPFS ## IPFS
Copies of this repository will occasionally be uploaded and hosted on [IPFS](https://ipfs.io): 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 ## License
[MIT](./LICENSE.md) [MIT](./LICENSE.md)
+2 -2
View File
@@ -10,9 +10,9 @@ const config = {
: 'secret-jwt-token', : 'secret-jwt-token',
// Rate Limits // Rate Limits
anonRateLimit: process.env.ANON_RATE_LIMIT ? process.env.ANON_RATE_LIMIT : 50, anonRateLimit: process.env.ANON_RATE_LIMIT ? Number(process.env.ANON_RATE_LIMIT) : 50,
whitelistRateLimit: process.env.WHITELIST_RATE_LIMIT whitelistRateLimit: process.env.WHITELIST_RATE_LIMIT
? process.env.WHITELIST_RATE_LIMIT ? Number(process.env.WHITELIST_RATE_LIMIT)
: 10, : 10,
whitelistDomains: process.env.WHITELIST_DOMAINS whitelistDomains: process.env.WHITELIST_DOMAINS
? process.env.WHITELIST_DOMAINS.split(',') ? process.env.WHITELIST_DOMAINS.split(',')
+31 -11
View File
@@ -1,21 +1,28 @@
#!/bin/bash #!/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 # 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_USERNAME=bitcoin
export RPC_PASSWORD=password export RPC_PASSWORD=password
export NETWORK=mainnet
# SLPDB # SLPDB
export SLPDB_URL=http://172.17.0.1:12300/ export SLPDB_URL=http://172.17.0.1:12300/
export SLPDB_PASS=somelongpassword export SLPDB_PASS=somelongpassword
# Use the same address as SLPDB_URL if you don't have a separate whitelist server.
# Blockbook Indexer export SLPDB_WHITELIST_URL=http://172.17.0.1:12300/
export BLOCKBOOK_URL=https://172.17.0.1:9131/ # slp-api alternative SLP validator using slp-validate:
# Allow node.js to make network calls to https using self-signed certificate. # https://github.com/Permissionless-Software-Foundation/slp-api
export NODE_TLS_REJECT_UNAUTHORIZED=0 export SLP_API_URL=http://10.0.0.5:5001/
# Mainnet Fulcrum / ElectrumX # Mainnet Fulcrum / ElectrumX
export FULCRUM_URL=172.17.0.1 export FULCRUM_URL=172.17.0.1
@@ -26,12 +33,25 @@ export REDIS_PORT=6379
export REDIS_HOST=172.17.0.1 export REDIS_HOST=172.17.0.1
# JWT Token Secret # JWT Token Secret
# This is used to verify JWT tokens generated with jwt-bch-api:
# https://github.com/Permissionless-Software-Foundation/jwt-bch-api
export TOKENSECRET=somelongsecretvalue export TOKENSECRET=somelongsecretvalue
# So that bch-api can call bch-js locally. # 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. # Basic Authentication password
export SLP_API_URL=http://10.0.0.5:5001/ export PRO_PASS=somerandomepassword:someotherrandompassword:aThirdPassword
# Whitelisted domains. Automatically give pro-tier rate limit access to apps
# that originate froma domain on the whitelist.
export WHITELIST_DOMAINS=fullstack.cash,psfoundation.cash,torlist.cash
# Rate Limits. Numbers are divided into 1000. e.g. 1000 / 50 = 20 RPM for ANON.
# Requests use the ANON rate limit if they fail to pass in a JWT token.
# ANON = 20 requests per minute (RPM)
export ANON_RATE_LIMIT=50
# 10 = 100 RPM
export WHITELIST_RATE_LIMIT=10
npm start npm start
+32 -11
View File
@@ -1,22 +1,32 @@
#!/bin/bash #!/bin/bash
# Docker containers for the below infrastructure is described here:
# https://psfoundation.cash/blog/cash-stack
# Pre-synced databases can be downloaded on the CashStrap page:
# https://fullstack.cash/cashstrap
# Which network are you using?
#export NETWORK=mainnet
export NETWORK=testnet
# Full node # Full node
export RPC_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_USERNAME=bitcoin
export RPC_PASSWORD=password export RPC_PASSWORD=password
export NETWORK=testnet
# SLPDB # SLPDB
export SLPDB_URL=http://172.17.0.1:13300/ export SLPDB_URL=http://172.17.0.1:13300/
export SLPDB_PASS=somelongpassword
# Blockbook Indexer # Use the same address as SLPDB_URL if you don't have a separate whitelist server.
export BLOCKBOOK_URL=https://172.17.0.1:19131/ export SLPDB_WHITELIST_URL=http://172.17.0.1:13300/
# Allow node.js to make network calls to https using self-signed certificate. # slp-api alternative SLP validator using slp-validate:
export NODE_TLS_REJECT_UNAUTHORIZED=0 # https://github.com/Permissionless-Software-Foundation/slp-api
export SLP_API_URL=http://10.0.0.5:5001/
# Mainnet Fulcrum / ElectrumX # Mainnet Fulcrum / ElectrumX
export FULCRUM_URL=172.17.0.1 export FULCRUM_URL=172.17.0.1
export FULCRUM_PORT=50002 export FULCRUM_PORT=60002
# Redis DB # Redis DB
export REDIS_PORT=6380 export REDIS_PORT=6380
@@ -26,9 +36,20 @@ export REDIS_HOST=172.17.0.1
export TOKENSECRET=somelongsecretvalue export TOKENSECRET=somelongsecretvalue
# So that bch-api can call bch-js locally. # 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. # Basic Authentication password (optional)
export SLP_API_URL=http://10.0.0.5:5001/ export PRO_PASS=somerandomepassword:someotherrandompassword:aThirdPassword
# Whitelisted domains. Automatically give pro-tier rate limit access to apps
# that originate froma domain on the whitelist.
export WHITELIST_DOMAINS=fullstack.cash,psfoundation.cash,torlist.cash
# Rate Limits. Numbers are divided into 1000. e.g. 1000 / 50 = 20 RPM for ANON.
# Requests use the ANON rate limit if they fail to pass in a JWT token.
# ANON = 20 requests per minute (RPM)
export ANON_RATE_LIMIT=50
# 10 = 100 RPM
export WHITELIST_RATE_LIMIT=10
npm start npm start
+19682 -551
View File
File diff suppressed because it is too large Load Diff
+6 -14
View File
@@ -22,15 +22,15 @@
"coverage": "nyc report --reporter=text-lcov | coveralls", "coverage": "nyc report --reporter=text-lcov | coveralls",
"coverage:report": "export NETWORK=mainnet && nyc --reporter=html mocha --timeout 25000 test/v4/", "coverage:report": "export NETWORK=mainnet && nyc --reporter=html mocha --timeout 25000 test/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 --timeout 25000 -g '#generateSendOpReturn()' test/v4/integration/", "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" "test:temp2": "mocha test/v4/rate-limits.js"
}, },
"engines": { "engines": {
"node": ">=10.15.1" "node": ">=10.15.1"
}, },
"dependencies": { "dependencies": {
"@psf/bch-js": "^4.5.2", "@psf/bch-js": "^4.11.1",
"apidoc": "^0.23.0", "apidoc": "^0.26.0",
"axios": "^0.21.1", "axios": "^0.21.1",
"bitcore-lib-cash": "^8.23.1", "bitcore-lib-cash": "^8.23.1",
"body-parser": "^1.18.3", "body-parser": "^1.18.3",
@@ -45,7 +45,6 @@
"helmet": "^3.21.2", "helmet": "^3.21.2",
"ioredis": "^4.14.1", "ioredis": "^4.14.1",
"jsonwebtoken": "^8.5.1", "jsonwebtoken": "^8.5.1",
"level": "^6.0.0",
"mkdirp": "^1.0.0", "mkdirp": "^1.0.0",
"mocha": "^7.1.1", "mocha": "^7.1.1",
"morgan": "^1.9.1", "morgan": "^1.9.1",
@@ -53,8 +52,6 @@
"passport": "^0.4.0", "passport": "^0.4.0",
"passport-anonymous": "^1.0.1", "passport-anonymous": "^1.0.1",
"passport-http": "^0.3.0", "passport-http": "^0.3.0",
"pg": "^8.0.0",
"pg-hstore": "^2.3.2",
"rate-limiter-flexible": "^2.0.0", "rate-limiter-flexible": "^2.0.0",
"winston": "^3.2.1", "winston": "^3.2.1",
"winston-daily-rotate-file": "^4.0.0" "winston-daily-rotate-file": "^4.0.0"
@@ -69,17 +66,12 @@
"eslint-plugin-prettier": "^3.1.0", "eslint-plugin-prettier": "^3.1.0",
"eslint-plugin-standard": "^4.0.0", "eslint-plugin-standard": "^4.0.0",
"fs-extra": "^9.0.0", "fs-extra": "^9.0.0",
"nock": "^12.0.0", "nock": "^13.0.5",
"node-mocks-http": "^1.7.0",
"nyc": "^15.0.0", "nyc": "^15.0.0",
"prettier": "^2.0.0", "prettier": "^2.0.0",
"proxyquire": "^2.1.0", "semantic-release": "^17.3.1",
"request": "^2.88.0",
"request-promise": "^4.2.2",
"semantic-release": "^17.3.0",
"sinon": "^9.0.0", "sinon": "^9.0.0",
"standard": "^14.3.1", "standard": "^14.3.1"
"typescript": "^3.1.4"
}, },
"release": { "release": {
"publish": [ "publish": [
+92 -1
View File
@@ -17,7 +17,9 @@ const wlogger = require('../../util/winston-logging')
const LOCAL_RESTURL = process.env.LOCAL_RESTURL const LOCAL_RESTURL = process.env.LOCAL_RESTURL
? process.env.LOCAL_RESTURL ? process.env.LOCAL_RESTURL
: 'https://api.fullstack.cash/v4/' : 'https://api.fullstack.cash/v4/'
const BCHJS = require('@psf/bch-js') const BCHJS = require('@psf/bch-js')
// const BCHJS = require('../../../../bch-js')
const bchjs = new BCHJS({ restURL: LOCAL_RESTURL }) const bchjs = new BCHJS({ restURL: LOCAL_RESTURL })
// Used to convert error messages to strings, to safely pass to users. // Used to convert error messages to strings, to safely pass to users.
@@ -89,6 +91,7 @@ class Slp {
) )
_this.router.post('/generateSendOpReturn', _this.generateSendOpReturn) _this.router.post('/generateSendOpReturn', _this.generateSendOpReturn)
_this.router.post('/hydrateUtxos', _this.hydrateUtxos) _this.router.post('/hydrateUtxos', _this.hydrateUtxos)
_this.router.post('/hydrateUtxosWL', _this.hydrateUtxosWL)
_this.router.get('/status', _this.getStatus) _this.router.get('/status', _this.getStatus)
} }
@@ -1305,6 +1308,7 @@ class Slp {
async validate3Single (req, res, next) { async validate3Single (req, res, next) {
try { try {
const txid = req.params.txid const txid = req.params.txid
// console.log('validate3Single txid: ', txid)
// Validate input // Validate input
if (!txid || txid === '') { if (!txid || txid === '') {
@@ -1386,6 +1390,7 @@ class Slp {
async validate3Bulk (req, res, next) { async validate3Bulk (req, res, next) {
try { try {
const txids = req.body.txids const txids = req.body.txids
// console.log(`validate3Bulk txids: `, txids)
// Reject if txids is not an array. // Reject if txids is not an array.
if (!Array.isArray(txids)) { if (!Array.isArray(txids)) {
@@ -1985,7 +1990,7 @@ class Slp {
// Get SLP token details. // Get SLP token details.
const details = await _this.bchjs.SLP.Utils.tokenUtxoDetails(theseUtxos) const details = await _this.bchjs.SLP.Utils.tokenUtxoDetails(theseUtxos)
// console.log('details : ', details) // console.log('details: ', details)
// Replace the original UTXO data with the hydrated data. // Replace the original UTXO data with the hydrated data.
utxos[i].utxos = details utxos[i].utxos = details
@@ -2013,6 +2018,92 @@ 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 * @api {get} /slp/status Get the health status of SLPDB
* @apiName Get the health status of SLPDB * @apiName Get the health status of SLPDB
+8 -2
View File
@@ -11,13 +11,19 @@ require('winston-daily-rotate-file')
var NETWORK = process.env.NETWORK var NETWORK = process.env.NETWORK
// Default 1 Megabyte
var LOG_MAX_SIZE = process.env.LOG_MAX_SIZE ? process.env.LOG_MAX_SIZE : '1m'
// Default 5 days.
var LOG_MAX_FILES = process.env.LOG_MAX_FILES ? process.env.LOG_MAX_FILES : '5d'
// Configure daily-rotation transport. // Configure daily-rotation transport.
var transport = new winston.transports.DailyRotateFile({ var transport = new winston.transports.DailyRotateFile({
filename: `${__dirname}/../../logs/rest-${NETWORK}-%DATE%.log`, filename: `${__dirname}/../../logs/rest-${NETWORK}-%DATE%.log`,
datePattern: 'YYYY-MM-DD', datePattern: 'YYYY-MM-DD',
zippedArchive: false, zippedArchive: false,
maxSize: '1m', maxSize: LOG_MAX_SIZE,
maxFiles: '5d', maxFiles: LOG_MAX_FILES,
format: winston.format.combine( format: winston.format.combine(
winston.format.timestamp(), winston.format.timestamp(),
winston.format.json() winston.format.json()
+38 -12
View File
@@ -1,37 +1,63 @@
#!/bin/bash #!/bin/bash
# Full node # Docker containers for the below infrastructure is described here:
export RPC_BASEURL=http://<full node ip>:8332/ # https://psfoundation.cash/blog/cash-stack
export RPC_USERNAME=<RPC username> # Pre-synced databases can be downloaded on the CashStrap page:
export RPC_PASSWORD=<RPC password> # https://fullstack.cash/cashstrap
# Which network are you using?
export NETWORK=mainnet 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 # SLPDB
export SLPDB_URL=http://<SLPDB IP>:12300/ export SLPDB_URL=http://<SLPDB IP>:12300/
export SLPDB_PASS=somelongpassword export SLPDB_PASS=somelongpassword
# Use the same address as SLPDB_URL if you don't have a separate whitelist server. # Use the same address as SLPDB_URL if you don't have a separate whitelist server.
export SLPDB_WHITELIST_URL=http://<SLPDB IP>:12300/ export SLPDB_WHITELIST_URL=http://<SLPDB IP>:12300/
# slp-api alternative SLP validator using slp-validate: # slp-api alternative SLP validator using slp-validate:
# https://github.com/Permissionless-Software-Foundation/slp-api # https://github.com/Permissionless-Software-Foundation/slp-api
export SLP_API_URL=http://10.0.0.5:5001/ 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 # Mainnet Fulcrum / ElectrumX
export FULCRUM_URL=192.168.0.6 export FULCRUM_URL=192.168.0.6
export FULCRUM_PORT=50002 export FULCRUM_PORT=50002
# Redis DB - Used for rate limiting - customize to your own Redis installation.
export REDIS_PORT=6379
export REDIS_HOST=172.17.0.1
# JWT Token Secret
# This is used to verify JWT tokens generated with jwt-bch-api:
# https://github.com/Permissionless-Software-Foundation/jwt-bch-api
export TOKENSECRET=somelongpassword export TOKENSECRET=somelongpassword
# So that bch-api can call bch-js locally. # 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 # Basic Authentication password
export PRO_PASS=somerandomepassword:someotherrandompassword:aThirdPassword export PRO_PASS=somerandomepassword:someotherrandompassword:aThirdPassword
# Whitelisted domains. Automatically give pro-tier rate limit access to apps
# that originate froma domain on the whitelist.
export WHITELIST_DOMAINS=fullstack.cash,psfoundation.cash,torlist.cash
# Rate Limits. Numbers are divided into 1000. e.g. 1000 / 50 = 20 RPM for ANON.
# Requests use the ANON rate limit if they fail to pass in a JWT token.
# ANON = 20 requests per minute (RPM)
export ANON_RATE_LIMIT=50
# 10 = 100 RPM
export WHITELIST_RATE_LIMIT=10
# Set logging parameters
#1m means no more than 1 megabyte
export LOG_MAX_SIZE=1m
#5d means store no more than 5 days
export LOG_MAX_FILES=5d
npm start npm start
+6 -4
View File
@@ -7,21 +7,22 @@
TODO: TODO:
-/rawtransactions/sendRawTransaction is more appropropriate for an e2e test, -/rawtransactions/sendRawTransaction is more appropropriate for an e2e test,
so it is omitted here. so it is omitted here.
- Replace request-promise with axios.
*/ */
'use strict' 'use strict'
const chai = require('chai') // const chai = require('chai')
const assert = chai.assert // const assert = chai.assert
// const rawtransactions = require('../../../dist/routes/v2/rawtransactions') // const rawtransactions = require('../../../dist/routes/v2/rawtransactions')
const rp = require('request-promise') // const rp = require('request-promise')
// Used for debugging. // Used for debugging.
const util = require('util') const util = require('util')
util.inspect.defaultOptions = { depth: 1 } util.inspect.defaultOptions = { depth: 1 }
// const mockData = require('../mocks/raw-transactions-mocks') // const mockData = require('../mocks/raw-transactions-mocks')
/*
describe('#Raw-Transactions', () => { describe('#Raw-Transactions', () => {
describe('#root', () => { describe('#root', () => {
it('should return root', async () => { it('should return root', async () => {
@@ -189,3 +190,4 @@ describe('#Raw-Transactions', () => {
}) })
}) })
}) })
*/
+33
View File
@@ -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', () => { describe('#validate2Single', () => {
it('should invalidate a known invalid TXID', async () => { it('should invalidate a known invalid TXID', async () => {
const txid = const txid =
+175
View File
@@ -1559,6 +1559,181 @@ 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', () => { describe('#getStatus', () => {
it('should get the SLPDB status', async () => { it('should get the SLPDB status', async () => {
if (process.env.TEST === 'unit') { if (process.env.TEST === 'unit') {