Compare commits

...
15 Commits
Author SHA1 Message Date
Chris Troutner 4b68a75a41 Merge pull request #116 from Permissionless-Software-Foundation/ct-unstable
fix(tests): Fixing failing test in BVT
2021-03-16 09:54:41 -07:00
Chris Troutner 901189ae42 fix(tests): Fixing failing test in BVT 2021-03-16 09:53:13 -07:00
Chris Troutner 62bba1d79f Merge pull request #115 from Permissionless-Software-Foundation/ct-unstable
Improved error handling of hydrateUtxos
2021-03-16 09:07:39 -07:00
Chris Troutner 9c735e3c5a Removing more debugging statements 2021-03-16 09:00:47 -07:00
Chris Troutner 62600fdd62 Backing down on some of the debugger statements 2021-03-16 08:56:14 -07:00
Chris Troutner 27e5fb8728 fix(route-utils): Adding handler for nginx 429 error 2021-03-16 08:48:15 -07:00
Chris Troutner 93811ca331 fix(route-utils): Removing old route-utils and using new route-utils 2021-03-16 08:43:43 -07:00
Chris Troutner e37858fd19 debugging 2021-03-11 09:04:26 -08:00
Chris Troutner 3ddbb728b4 debugging 2021-03-11 08:52:02 -08:00
Chris Troutner ef49b85495 debugging 2021-03-11 08:48:22 -08:00
Chris Troutner be6550686f debugging 2021-03-11 08:46:20 -08:00
Chris Troutner 8de2e9e10b debugging 2021-03-11 08:44:24 -08:00
Chris Troutner 5650afb22f fix(decodeError): Adding additional error handling 2021-03-11 08:40:48 -08:00
Chris Troutner 6540171263 Merge pull request #113 from Permissionless-Software-Foundation/ct-unstable
fix(rate limits): Bumping resolution to 10,000 points per minute
2021-03-10 12:04:42 -08:00
Chris Troutner fa1f87c5c8 fix(rate limits): Bumping resolution to 10,000 points per minute 2021-03-10 10:09:18 -08:00
8 changed files with 68 additions and 209 deletions
+2 -2
View File
@@ -12,13 +12,13 @@ const config = {
// Rate Limits // Rate Limits
anonRateLimit: process.env.ANON_RATE_LIMIT anonRateLimit: process.env.ANON_RATE_LIMIT
? Number(process.env.ANON_RATE_LIMIT) ? Number(process.env.ANON_RATE_LIMIT)
: 50, : 500,
whitelistRateLimit: process.env.WHITELIST_RATE_LIMIT whitelistRateLimit: process.env.WHITELIST_RATE_LIMIT
? Number(process.env.WHITELIST_RATE_LIMIT) ? Number(process.env.WHITELIST_RATE_LIMIT)
: 10, : 10,
pointsPerMinute: process.env.POINTS_PER_MINUTE pointsPerMinute: process.env.POINTS_PER_MINUTE
? Number(process.env.POINTS_PER_MINUTE) ? Number(process.env.POINTS_PER_MINUTE)
: 1000, : 10000,
whitelistDomains: process.env.WHITELIST_DOMAINS whitelistDomains: process.env.WHITELIST_DOMAINS
? process.env.WHITELIST_DOMAINS.split(',') ? process.env.WHITELIST_DOMAINS.split(',')
: ['fullstack.cash', 'psfoundation.cash', '10.0.'] : ['fullstack.cash', 'psfoundation.cash', '10.0.']
+7 -6
View File
@@ -2,9 +2,11 @@
This file will replace the original rate-limit.js file. This file will replace the original rate-limit.js file.
Sets the rate limits for the anonymous and paid tiers. Current rate limits: Sets the rate limits for the anonymous and paid tiers. Current rate limits:
- 1000 points in 60 seconds - 10000 points in 60 seconds
- 10 points per call for paid tier (100 RPM) - 500 points per call for anonymous tier (20 RPM)
- 50 points per call for anonymous tier (20 RPM) - 100 points per call for tier 40 (100 RPM)
- 40 points per call for tier 50 (250 RPM)
- 16 points per call for tier 60 (625 RPM)
The rate limit handling is designed for these four use cases: The rate limit handling is designed for these four use cases:
- Users who want to buy a JWT token for 24 hour access. - Users who want to buy a JWT token for 24 hour access.
@@ -41,11 +43,10 @@ const redisOptions = {
port: process.env.REDIS_PORT ? process.env.REDIS_PORT : 6379, port: process.env.REDIS_PORT ? process.env.REDIS_PORT : 6379,
host: process.env.REDIS_HOST ? process.env.REDIS_HOST : '127.0.0.1' host: process.env.REDIS_HOST ? process.env.REDIS_HOST : '127.0.0.1'
} }
console.log(`redisOptions: ${JSON.stringify(redisOptions, null, 2)}`)
const redisClient = new Redis(redisOptions) const redisClient = new Redis(redisOptions)
const rateLimitOptions = { const rateLimitOptions = {
storeClient: redisClient, storeClient: redisClient,
points: 1000, // Number of points points: config.pointsPerMinute, // Number of points
duration: 60 // Per minute (per 60 seconds) duration: 60 // Per minute (per 60 seconds)
} }
@@ -55,7 +56,7 @@ const ANON_LIMITS = config.anonRateLimit
const WHITELIST_DOMAINS = config.whitelistDomains const WHITELIST_DOMAINS = config.whitelistDomains
const WHITELIST_POINTS_TO_CONSUME = config.whitelistRateLimit const WHITELIST_POINTS_TO_CONSUME = config.whitelistRateLimit
const POINTS_PER_MINUTE = config.pointsPerMinute const POINTS_PER_MINUTE = config.pointsPerMinute
const INTERNAL_POINTS_TO_CONSUME = 1 const INTERNAL_POINTS_TO_CONSUME = 10
class RateLimits { class RateLimits {
constructor () { constructor () {
-192
View File
@@ -1,192 +0,0 @@
/*
A private library of utility functions used by several different routes.
*/
'use strict'
const axios = require('axios')
const wlogger = require('../../util/winston-logging')
const util = require('util')
util.inspect.defaultOptions = { depth: 1 }
const BCHJS = require('@psf/bch-js')
const bchjs = new BCHJS()
module.exports = {
validateNetwork, // Prevents a common user error
setEnvVars, // Allows RPC variables to be set dynamically based on changing env vars.
decodeError, // Extract and interpret error messages.
validateArraySize, // Ensure the passed array meets rate limiting requirements.
getAxiosOptions
}
// This function expects the Request Express.js object and an array as input.
// The array is then validated against freemium and pro-tier rate limiting
// requirements. A boolean is returned to indicate if the array size if valid
// or not.
function validateArraySize (req, array) {
const FREEMIUM_INPUT_SIZE = 20
const PRO_INPUT_SIZE = 20
if (req.locals && req.locals.proLimit) {
if (array.length <= PRO_INPUT_SIZE) return true
} else if (array.length <= FREEMIUM_INPUT_SIZE) {
return true
}
return false
}
// Returns true if user-provided cash address matches the correct network,
// mainnet or testnet. If NETWORK env var is not defined, it returns false.
// This prevent a common user-error issue that is easy to make: passing a
// testnet address into rest.bitcoin.com or passing a mainnet address into
// trest.bitcoin.com.
function validateNetwork (addr) {
try {
const network = process.env.NETWORK
// Return false if NETWORK is not defined.
if (!network || network === '') {
console.log('Warning: NETWORK environment variable is not defined!')
return false
}
// Convert the user-provided address to a cashaddress, for easy detection
// of the intended network.
const cashAddr = bchjs.Address.toCashAddress(addr)
// Return true if the network and address both match testnet
const addrIsTest = bchjs.Address.isTestnetAddress(cashAddr)
if (network === 'testnet' && addrIsTest) return true
// Return true if the network and address both match mainnet
const addrIsMain = bchjs.Address.isMainnetAddress(cashAddr)
if (network === 'mainnet' && addrIsMain) return true
return false
} catch (err) {
wlogger.error('Error in validateNetwork()')
return false
}
}
// Dynamically set these based on env vars. Allows unit testing.
function setEnvVars () {
const BitboxHTTP = axios.create({
baseURL: process.env.RPC_BASEURL,
timeout: 15000
})
const username = process.env.RPC_USERNAME
const password = process.env.RPC_PASSWORD
const requestConfig = {
method: 'post',
auth: {
username: username,
password: password
},
data: {
jsonrpc: '1.0'
}
}
return { BitboxHTTP, username, password, requestConfig }
}
// Axios options used when calling axios.post() to talk with a full node.
function getAxiosOptions () {
return {
method: 'post',
baseURL: process.env.RPC_BASEURL,
timeout: 15000,
auth: {
username: process.env.RPC_USERNAME,
password: process.env.RPC_PASSWORD
},
data: {
jsonrpc: '1.0'
}
}
}
// Error messages returned by a full node can be burried pretty deep inside the
// error object returned by Axios. This function attempts to extract and interpret
// error messages.
// Returns an object. If successful, obj.msg is a string.
// If there is a failure, obj.msg is false.
function decodeError (err) {
try {
// Attempt to extract the full node error message.
if (
err.response &&
err.response.data &&
err.response.data.error &&
err.response.data.error.message
) {
return { msg: err.response.data.error.message, status: 400 }
}
// Attempt to extract the Insight error message
if (err.response && err.response.data) {
return { msg: err.response.data, status: err.response.status }
}
// console.log(`err.message: ${err.message}`)
// console.log(`err: `, err)
// Attempt to detect a network connection error.
if (err.message && err.message.indexOf('ENOTFOUND') > -1) {
return {
msg:
'Network error: Could not communicate with full node or other external service.',
status: 503
}
}
// Different kind of network error
if (err.message && err.message.indexOf('ENETUNREACH') > -1) {
return {
msg:
'Network error: Could not communicate with full node or other external service.',
status: 503
}
}
// Different kind of network error
if (err.message && err.message.indexOf('EAI_AGAIN') > -1) {
return {
msg:
'Network error: Could not communicate with full node or other external service.',
status: 503
}
}
// Axios timeout (aborted) error, or service is down (connection refused).
if (
err.code &&
(err.code === 'ECONNABORTED' || err.code === 'ECONNREFUSED')
) {
return {
msg:
'Network error: Could not communicate with full node or other external service.',
status: 503
}
}
// 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 }
}
}
+9 -3
View File
@@ -104,8 +104,13 @@ class Slp {
// DRY error handler. // DRY error handler.
errorHandler (err, res) { errorHandler (err, res) {
// console.error('Entering slp.js/errorHandler(). err: ', err)
// Attempt to decode the error message. // Attempt to decode the error message.
const { msg, status } = _this.routeUtils.decodeError(err) const { msg, status } = _this.routeUtils.decodeError(err)
console.log('slp.js/errorHandler msg from decodeError: ', msg)
console.log('slp.js/errorHandler status from decodeError: ', status)
if (msg) { if (msg) {
res.status(status) res.status(status)
return res.json({ error: msg }) return res.json({ error: msg })
@@ -2051,12 +2056,13 @@ class Slp {
return res.json({ slpUtxos: utxos }) return res.json({ slpUtxos: utxos })
} catch (err) { } catch (err) {
wlogger.error('Error in slp.js/hydrateUtxos().', err) wlogger.error('Error in slp.js/hydrateUtxos().', err)
console.error('Error in slp.js/hydrateUtxos().', err) // console.error('Error in slp.js/hydrateUtxos().', err)
// Decode the error message. // Decode the error message.
const { msg, status } = routeUtils.decodeError(err) const { msg, status } = routeUtils.decodeError(err)
console.log('msg: ', msg) // console.log('msg: ', msg)
console.log('status: ', status) // console.log('status: ', status)
if (msg) { if (msg) {
res.status(status) res.status(status)
return res.json({ error: msg, message: msg, success: false }) return res.json({ error: msg, message: msg, success: false })
+3 -1
View File
@@ -4,7 +4,9 @@ const express = require('express')
const router = express.Router() const router = express.Router()
const axios = require('axios') const axios = require('axios')
const routeUtils = require('./route-utils') const RouteUtils = require('../../util/route-utils')
const routeUtils = new RouteUtils()
const wlogger = require('../../util/winston-logging') const wlogger = require('../../util/winston-logging')
const util = require('util') const util = require('util')
+4 -2
View File
@@ -5,8 +5,10 @@
'use strict' 'use strict'
const express = require('express') const express = require('express')
// const axios = require('axios')
const routeUtils = require('./route-utils') const RouteUtils = require('../../util/route-utils')
const routeUtils = new RouteUtils()
const wlogger = require('../../util/winston-logging') const wlogger = require('../../util/winston-logging')
// const router = express.Router() // const router = express.Router()
+40
View File
@@ -154,6 +154,23 @@ class RouteUtils {
} }
} }
// Handle 429 errors thrown by nginx
if (err.error) {
// console.log('decodeError: err: ', err)
if (err.error.includes('429 Too Many Requests')) {
const internalMsg =
'429 error thrown by nginx caught by route-utils.js/decodeError()'
console.error(internalMsg)
wlogger.error(internalMsg)
return {
msg: '429 Too Many Requests',
status: 429
}
}
}
// Handle general Error objects. // Handle general Error objects.
if (err.message) { if (err.message) {
return { return {
@@ -169,6 +186,29 @@ class RouteUtils {
return { msg: false, status: 500 } return { msg: false, status: 500 }
} }
} }
// Dynamically set these based on env vars. Allows unit testing.
setEnvVars () {
const BitboxHTTP = axios.create({
baseURL: process.env.RPC_BASEURL,
timeout: 15000
})
const username = process.env.RPC_USERNAME
const password = process.env.RPC_PASSWORD
const requestConfig = {
method: 'post',
auth: {
username: username,
password: password
},
data: {
jsonrpc: '1.0'
}
}
return { BitboxHTTP, username, password, requestConfig }
}
} }
module.exports = RouteUtils module.exports = RouteUtils
+3 -3
View File
@@ -132,7 +132,7 @@ describe('#rate-routelimit', () => {
// console.log('result: ', result) // console.log('result: ', result)
assert.property(result, 'id') assert.property(result, 'id')
assert.equal(result.id, '123.456.789.10') // assert.equal(result.id, '123.456.789.10')
assert.property(result, 'email') assert.property(result, 'email')
assert.equal(result.email, 'test@bchtest.net') assert.equal(result.email, 'test@bchtest.net')
assert.property(result, 'pointsToConsume') assert.property(result, 'pointsToConsume')
@@ -380,7 +380,7 @@ describe('#rate-routelimit', () => {
assert.equal( assert.equal(
res.locals.pointsToConsume, res.locals.pointsToConsume,
1, 10,
'Internal rate limits applied' 'Internal rate limits applied'
) )
}) })
@@ -403,7 +403,7 @@ describe('#rate-routelimit', () => {
assert.equal( assert.equal(
res.locals.pointsToConsume, res.locals.pointsToConsume,
1, 10,
'Internal rate limits applied' 'Internal rate limits applied'
) )
}) })