Compare commits

..
19 Commits
Author SHA1 Message Date
Chris Troutner e881b68ca5 Merge pull request #65 from Permissionless-Software-Foundation/ct-unstable
ix(electrumx): Fixed connect/disconnect bug that was preventing tests
2020-11-21 11:39:05 -08:00
Chris Troutner f54c69a664 Fixing test typo 2020-11-21 11:35:01 -08:00
Chris Troutner 319281c096 fix(electrumx): Fixed connect/disconnect bug that was preventing tests 2020-11-21 11:29:18 -08:00
Chris Troutner 7dee8c0604 Merge branch 'master' into ct-unstable 2020-11-21 11:15:41 -08:00
Chris Troutner efbcbc5deb linting 2020-11-21 11:15:33 -08:00
Chris Troutner c742518612 Merge pull request #64 from Permissionless-Software-Foundation/ct-unstable
fix(electrumx): Automatically reconnecting when Electrumx disconnects
2020-11-20 20:53:40 -08:00
Chris Troutner 6a0ed46413 fix(electrumx): Automatically reconnecting when Electrumx disconnects 2020-11-20 20:52:22 -08:00
Chris Troutner 5ea71bc89b Updating default Docker run script 2020-11-20 08:36:34 -08:00
Chris Troutner 84701e90dc Merge pull request #63 from Permissionless-Software-Foundation/ct-unstable
fix(price): Fixing bug in new price endpoint
2020-11-17 14:29:22 -08:00
Chris Troutner 34ed5ddc4c fix(price): Fixing bug in new price endpoint 2020-11-17 14:27:42 -08:00
Chris Troutner 9a8cd4b664 Merge pull request #62 from Permissionless-Software-Foundation/ct-unstable
fix(price): Adding Coinex price feed for BCHA
2020-11-17 14:00:37 -08:00
Chris Troutner 2a0b06ca96 fix(price): Adding Coinex price feed for BCHA 2020-11-17 13:48:10 -08:00
Chris Troutner 9c70fc5007 Merge pull request #61 from Permissionless-Software-Foundation/ct-unstable
fix(whitelist): Adding splitbch.com to the whitelist
2020-11-14 17:39:41 -08:00
Chris Troutner b24b659124 fix(whitelist): Adding splitbch.com to the whitelist 2020-11-14 17:36:38 -08:00
Chris Troutner 7ad5acef34 Merge pull request #60 from Permissionless-Software-Foundation/ct-unstable
fix(rate limits): Setting anonymous rate limits to a constant
2020-11-12 09:11:48 -08:00
Chris Troutner 304a3f1d5a fix(rate limits): Setting anonymous rate limits to a constant 2020-11-12 09:10:35 -08:00
Chris Troutner 86a58e9ff3 Merge pull request #59 from Permissionless-Software-Foundation/ct-unstable
fix(auth): Fixing typo that was breaking basic authentication
2020-11-11 19:58:11 -08:00
Chris Troutner d7d722b27b Merge branch 'master' into ct-unstable 2020-11-11 19:55:58 -08:00
Chris Troutner d439165dd2 fix(auth): Fixing typo that was breaking basic authentication 2020-11-11 19:55:15 -08:00
12 changed files with 188 additions and 17 deletions
+5 -1
View File
@@ -17,7 +17,11 @@ export BLOCKBOOK_URL=https://172.17.0.1:9131/
# Allow node.js to make network calls to https using self-signed certificate. # Allow node.js to make network calls to https using self-signed certificate.
export NODE_TLS_REJECT_UNAUTHORIZED=0 export NODE_TLS_REJECT_UNAUTHORIZED=0
# Redis DB # Mainnet Fulcrum / ElectrumX
export FULCRUM_URL=172.17.0.1
export FULCRUM_PORT=50002
# Redis DB - Used for rate limiting
export REDIS_PORT=6379 export REDIS_PORT=6379
export REDIS_HOST=172.17.0.1 export REDIS_HOST=172.17.0.1
+4
View File
@@ -14,6 +14,10 @@ export BLOCKBOOK_URL=https://172.17.0.1:19131/
# Allow node.js to make network calls to https using self-signed certificate. # Allow node.js to make network calls to https using self-signed certificate.
export NODE_TLS_REJECT_UNAUTHORIZED=0 export NODE_TLS_REJECT_UNAUTHORIZED=0
# Mainnet Fulcrum / ElectrumX
export FULCRUM_URL=172.17.0.1
export FULCRUM_PORT=50002
# Redis DB # Redis DB
export REDIS_PORT=6380 export REDIS_PORT=6380
export REDIS_HOST=172.17.0.1 export REDIS_HOST=172.17.0.1
+1 -1
View File
@@ -17,7 +17,7 @@
"test": "npm run lint && npm run test-v3", "test": "npm run lint && npm run test-v3",
"lint": "standard --env mocha --fix", "lint": "standard --env mocha --fix",
"test-v3": "export NETWORK=mainnet && nyc --reporter=text mocha --timeout 60000 test/v3/", "test-v3": "export NETWORK=mainnet && nyc --reporter=text mocha --timeout 60000 test/v3/",
"test:temp": "export NETWORK=mainnet && export TEST=integration && mocha --timeout 25000 test/v3/encryption.js", "test:temp": "export NETWORK=mainnet && export TEST=integration && mocha --timeout 25000 test/v3/integration/price.js",
"test:integration": "mocha test/v3/integration", "test:integration": "mocha test/v3/integration",
"test:integration:slpdb": "mocha --timeout 25000 -g '#validate2Single' test/v3/integration/slp*.js", "test:integration:slpdb": "mocha --timeout 25000 -g '#validate2Single' test/v3/integration/slp*.js",
"coverage": "nyc report --reporter=text-lcov | coveralls", "coverage": "nyc report --reporter=text-lcov | coveralls",
+5 -1
View File
@@ -33,7 +33,9 @@ util.inspect.defaultOptions = { depth: 1 }
// let _this // let _this
// Set default rate limit value for testing // Set default rate limit value for testing
const PRO_PASSES = process.env.PRO_PASSES ? process.env.PRO_PASSES : 'testpassword' const PRO_PASSES = process.env.PRO_PASS
? process.env.PRO_PASS
: 'testpassword'
// Convert the pro-tier password string into an array split by ':'. // Convert the pro-tier password string into an array split by ':'.
const PRO_PASS = PRO_PASSES.split(':') const PRO_PASS = PRO_PASSES.split(':')
@@ -47,6 +49,8 @@ class AuthMW {
// Initialize passport for 'anonymous' authentication. // Initialize passport for 'anonymous' authentication.
passport.use(new AnonymousStrategy()) passport.use(new AnonymousStrategy())
console.log(`PRO_PASS: ${JSON.stringify(PRO_PASS, null, 2)}`)
// Initialize passport for 'basic' authentication. // Initialize passport for 'basic' authentication.
passport.use( passport.use(
new BasicStrategy({ passReqToCallback: true }, function ( new BasicStrategy({ passReqToCallback: true }, function (
+10 -7
View File
@@ -12,10 +12,12 @@
'use strict' 'use strict'
const jwt = require('jsonwebtoken')
const wlogger = require('../util/winston-logging') const wlogger = require('../util/winston-logging')
const config = require('../../config') const config = require('../../config')
const jwt = require('jsonwebtoken') const ANON_LIMITS = 50
// Redis // Redis
const redisOptions = { const redisOptions = {
@@ -111,7 +113,7 @@ class RateLimits {
} }
// Default value is 50 points per request = 20 RPM // Default value is 50 points per request = 20 RPM
let rateLimit = 50 let rateLimit = ANON_LIMITS
// Only evaluate the JWT token if the user is not using Basic Authentication. // Only evaluate the JWT token if the user is not using Basic Authentication.
if (!req.locals.proLimit) { if (!req.locals.proLimit) {
@@ -146,6 +148,7 @@ class RateLimits {
origin && origin &&
(origin.toString().indexOf('wallet.fullstack.cash') > -1 || (origin.toString().indexOf('wallet.fullstack.cash') > -1 ||
origin.toString().indexOf('sandbox.fullstack.cash') > -1 || origin.toString().indexOf('sandbox.fullstack.cash') > -1 ||
origin.toString().indexOf('splitbch.com') > -1 ||
origin === 'slp-api') origin === 'slp-api')
) { ) {
pointsToConsume = 10 pointsToConsume = 10
@@ -199,7 +202,7 @@ class RateLimits {
// Calculates the points consumed, based on the jwt information and the route // Calculates the points consumed, based on the jwt information and the route
// requested. // requested.
calcPoints (jwtInfo) { calcPoints (jwtInfo) {
let retVal = 50 // By default, use anonymous tier. let retVal = ANON_LIMITS // By default, use anonymous tier.
try { try {
// console.log(`jwtInfo: ${JSON.stringify(jwtInfo, null, 2)}`) // console.log(`jwtInfo: ${JSON.stringify(jwtInfo, null, 2)}`)
@@ -218,12 +221,12 @@ class RateLimits {
if (level40Routes.includes(resource)) { if (level40Routes.includes(resource)) {
if (apiLevel >= 40) retVal = 10 if (apiLevel >= 40) retVal = 10
// else if (apiLevel >= 10) retVal = 10 // else if (apiLevel >= 10) retVal = 10
else retVal = 50 else retVal = ANON_LIMITS
// Normal indexer routes // Normal indexer routes
} else if (level30Routes.includes(resource)) { } else if (level30Routes.includes(resource)) {
if (apiLevel >= 30) retVal = 10 if (apiLevel >= 30) retVal = 10
else retVal = 50 else retVal = ANON_LIMITS
// Full node tier // Full node tier
} else if (apiLevel >= 20) { } else if (apiLevel >= 20) {
@@ -231,7 +234,7 @@ class RateLimits {
// Free tier, full node only. // Free tier, full node only.
} else { } else {
retVal = 50 retVal = ANON_LIMITS
} }
} }
@@ -239,7 +242,7 @@ class RateLimits {
} catch (err) { } catch (err) {
wlogger.error('Error in route-ratelimit.js/calcPoints()') wlogger.error('Error in route-ratelimit.js/calcPoints()')
// throw err // throw err
retVal = 50 retVal = ANON_LIMITS
} }
return retVal return retVal
+18
View File
@@ -79,6 +79,21 @@ class Electrum {
// Set the connection flag. // Set the connection flag.
_this.isReady = true _this.isReady = true
// Periodically check the connection. If it's not connected, attempt to
// reconnect.
_this.reconnectIntervalHandle = setInterval(async function () {
const status = _this.electrumx.connection.status
// console.log(`Electrumx status: ${status}`)
// 1 = connected. If we're not connected, attemp to reconnect.
if (status !== 1) {
wlogger.info(`Electrumx not connectes. Status: ${status}`)
wlogger.info('Attempting to reconnect...')
await _this.electrumx.connect()
wlogger.info('...reconnected.')
}
}, 30000)
console.log('...Successfully connected to ElectrumX server.') console.log('...Successfully connected to ElectrumX server.')
// console.log(`_this.isReady: ${_this.isReady}`) // console.log(`_this.isReady: ${_this.isReady}`)
@@ -96,6 +111,9 @@ class Electrum {
// Return immediately if the isReady flag is false. // Return immediately if the isReady flag is false.
if (!_this.isReady) return true if (!_this.isReady) return true
// Disable the reconnect timer.
clearInterval(_this.reconnectIntervalHandle)
// Disconnect from the server. // Disconnect from the server.
await _this.electrumx.disconnect() await _this.electrumx.disconnect()
+39 -2
View File
@@ -24,11 +24,14 @@ class Price {
this.routeUtils = routeUtils this.routeUtils = routeUtils
this.priceUrl = 'https://api.coinbase.com/v2/exchange-rates?currency=BCH' this.priceUrl = 'https://api.coinbase.com/v2/exchange-rates?currency=BCH'
this.coinexPriceUrl =
'https://api.coinex.com/v1/market/ticker?market=bchausdt'
this.router = express.Router() this.router = express.Router()
this.router.get('/', _this.root) this.router.get('/', _this.root)
this.router.get('/usd', _this.getUSD) this.router.get('/usd', _this.getUSD)
this.router.get('/rates', _this.getBCHRate) this.router.get('/rates', _this.getBCHRate)
this.router.get('/bchausd', _this.getBCHAUSD)
} }
// DRY error handler. // DRY error handler.
@@ -53,7 +56,7 @@ class Price {
* @api {get} /price/usd Get the USD price of BCH * @api {get} /price/usd Get the USD price of BCH
* @apiName Get the USD price of BCH * @apiName Get the USD price of BCH
* @apiGroup Price * @apiGroup Price
* @apiDescription Get the USD price of BCH * @apiDescription Get the USD price of BCH from Coinbase.
* *
* *
* @apiExample Example usage: * @apiExample Example usage:
@@ -85,7 +88,7 @@ class Price {
* @api {get} /price/usd Get rates for several different currencies * @api {get} /price/usd Get rates for several different currencies
* @apiName Get rates for several different currencies * @apiName Get rates for several different currencies
* @apiGroup Price * @apiGroup Price
* @apiDescription Get rates for several different currencies * @apiDescription Get rates for several different currencies from Coinbase.
* *
* *
* @apiExample Example usage: * @apiExample Example usage:
@@ -113,6 +116,40 @@ class Price {
return _this.errorHandler(err, res) return _this.errorHandler(err, res)
} }
} }
/**
* @api {get} /price/bchausd Get the USD price of BCHA
* @apiName Get the USD price of BCHA
* @apiGroup Price
* @apiDescription Get the USD price of BCHA from Coinex.
*
*
* @apiExample Example usage:
* curl -X GET "https://api.fullstack.cash/v3/price/bchausd" -H "accept: application/json"
*
*/
async getBCHAUSD (req, res, next) {
try {
// Request options
const opt = {
method: 'get',
baseURL: _this.coinexPriceUrl,
timeout: 15000
}
const response = await axios.request(opt)
// console.log(`response.data: ${JSON.stringify(response.data, null, 2)}`)
const price = Number(response.data.data.ticker.last)
return res.json({ usd: price })
} catch (err) {
// Write out error to error log.
wlogger.error('Error in price.js/getBCHAUSD().', err)
return _this.errorHandler(err, res)
}
}
} }
module.exports = Price module.exports = Price
+1 -1
View File
@@ -42,7 +42,7 @@ function expectRouteError (res, result, expectedError, code = 400) {
assert.equal(result.success, false) assert.equal(result.success, false)
} }
describe('#ElectrumX Router', () => { describe('#Electrumx', () => {
let req, res let req, res
let sandbox let sandbox
const electrumxRoute = new ElecrumxRoute() const electrumxRoute = new ElecrumxRoute()
+5 -1
View File
@@ -821,6 +821,7 @@ describe('#BlockchainRouter', () => {
]) ])
}) })
}) })
describe('getRawMempool()', () => { describe('getRawMempool()', () => {
it('should throw 503 when network issues', async () => { it('should throw 503 when network issues', async () => {
// Save the existing RPC URL. // Save the existing RPC URL.
@@ -842,6 +843,7 @@ describe('#BlockchainRouter', () => {
'Error message expected' 'Error message expected'
) )
}) })
it('returns proper error when downstream service stalls', async () => { it('returns proper error when downstream service stalls', async () => {
// Mock the timeout error. // Mock the timeout error.
sandbox.stub(uut.axios, 'request').throws({ code: 'ECONNABORTED' }) sandbox.stub(uut.axios, 'request').throws({ code: 'ECONNABORTED' })
@@ -856,6 +858,7 @@ describe('#BlockchainRouter', () => {
'Error message expected' 'Error message expected'
) )
}) })
it('returns proper error when downstream service is down', async () => { it('returns proper error when downstream service is down', async () => {
// Mock the timeout error. // Mock the timeout error.
sandbox.stub(uut.axios, 'request').throws({ code: 'ECONNREFUSED' }) sandbox.stub(uut.axios, 'request').throws({ code: 'ECONNREFUSED' })
@@ -871,7 +874,7 @@ describe('#BlockchainRouter', () => {
) )
}) })
it('should GET /getMempoolInfo', async () => { it('should GET /getRawMempool', async () => {
// Mock the RPC call for unit tests. // Mock the RPC call for unit tests.
if (process.env.TEST === 'unit') { if (process.env.TEST === 'unit') {
sandbox sandbox
@@ -886,6 +889,7 @@ describe('#BlockchainRouter', () => {
// Not sure what other assertions should be made here. // Not sure what other assertions should be made here.
}) })
}) })
describe('getMempoolEntrySingle()', () => { describe('getMempoolEntrySingle()', () => {
it('should throw 400 if txid is empty', async () => { it('should throw 400 if txid is empty', async () => {
const result = await uut.getMempoolEntrySingle(req, res) const result = await uut.getMempoolEntrySingle(req, res)
+12 -1
View File
@@ -4,7 +4,7 @@
'use strict' 'use strict'
// const assert = require('chai').assert const assert = require('chai').assert
// const axios = require('axios') // const axios = require('axios')
// Used for debugging. // Used for debugging.
@@ -29,6 +29,17 @@ describe('#price', () => {
it('should get the USD price', async () => { it('should get the USD price', async () => {
const result = await price.getUSD(req, res) const result = await price.getUSD(req, res)
console.log(`result: ${util.inspect(result)}`) console.log(`result: ${util.inspect(result)}`)
assert.isNumber(result.usd)
})
})
describe('#getBCHAUSD', () => {
it('should get the USD price of BCHA', async () => {
const result = await price.getBCHAUSD(req, res)
console.log(`result: ${util.inspect(result)}`)
assert.isNumber(result.usd)
}) })
}) })
}) })
+22 -2
View File
@@ -216,6 +216,26 @@ const mockCoinbaseFeed = {
} }
} }
module.exports = { const mockCoinexFeed = {
mockCoinbaseFeed code: 0,
data: {
date: 1605649499848,
ticker: {
vol: '26717.39062407',
low: '10.4000',
open: '11.8000',
high: '19.0029',
last: '18.5000',
buy: '18.0100',
buy_amount: '200.00000000',
sell: '18.5000',
sell_amount: '47.89580474'
}
},
message: 'OK'
}
module.exports = {
mockCoinbaseFeed,
mockCoinexFeed
} }
+66
View File
@@ -211,4 +211,70 @@ describe('#PriceRouter', () => {
assert.property(result, 'error') assert.property(result, 'error')
}) })
}) })
describe('#getBCHAUSD', () => {
// const getNetworkInfo = controlRoute.testableComponents.getNetworkInfo
it('should throw 500 when network issues', async () => {
uut.coinexPriceUrl = 'http://fakeurl/api/'
await uut.getBCHAUSD(req, res)
// console.log(`result: ${util.inspect(result)}`)
assert.isAbove(
res.statusCode,
499,
'HTTP status code 500 or greater expected.'
)
// console.log(res)
assert.include(
res.output.error,
'Network error: Could not communicate with full node or other external service'
)
})
it('returns proper error when downstream service stalls', async () => {
// Mock the timeout error.
sandbox.stub(uut.axios, 'request').throws({ code: 'ECONNABORTED' })
const result = await uut.getBCHAUSD(req, res)
// console.log(`result: ${JSON.stringify(result, null, 2)}`)
assert.isAbove(res.statusCode, 499, 'HTTP status code 503 expected.')
assert.include(
result.error,
'Could not communicate with full node',
'Error message expected'
)
})
it('returns proper error when downstream service is down', async () => {
// Mock the timeout error.
sandbox.stub(uut.axios, 'request').throws({ code: 'ECONNREFUSED' })
const result = await uut.getBCHAUSD(req, res)
// console.log(`result: ${JSON.stringify(result, null, 2)}`)
assert.isAbove(res.statusCode, 499, 'HTTP status code 503 expected.')
assert.include(
result.error,
'Could not communicate with full node',
'Error message expected'
)
})
it('should get the USD price of BCH', async () => {
// Mock the RPC call for unit tests.
if (process.env.TEST === 'unit') {
sandbox
.stub(uut.axios, 'request')
.resolves({ data: mockData.mockCoinexFeed })
}
const result = await uut.getBCHAUSD(req, res)
// console.log(`result: ${util.inspect(result)}`)
assert.isNumber(result.usd)
})
})
}) })