From b24b659124c4fce098fb819144a155a777bf4a8d Mon Sep 17 00:00:00 2001 From: Chris Troutner Date: Sat, 14 Nov 2020 17:36:38 -0800 Subject: [PATCH 1/8] fix(whitelist): Adding splitbch.com to the whitelist --- src/middleware/route-ratelimit.js | 1 + 1 file changed, 1 insertion(+) diff --git a/src/middleware/route-ratelimit.js b/src/middleware/route-ratelimit.js index 1395596..c2719ce 100644 --- a/src/middleware/route-ratelimit.js +++ b/src/middleware/route-ratelimit.js @@ -148,6 +148,7 @@ class RateLimits { origin && (origin.toString().indexOf('wallet.fullstack.cash') > -1 || origin.toString().indexOf('sandbox.fullstack.cash') > -1 || + origin.toString().indexOf('splitbch.com') > -1 || origin === 'slp-api') ) { pointsToConsume = 10 From 2a0b06ca96d5dbef27dcbacba2419c076785bfde Mon Sep 17 00:00:00 2001 From: Chris Troutner Date: Tue, 17 Nov 2020 13:48:10 -0800 Subject: [PATCH 2/8] fix(price): Adding Coinex price feed for BCHA --- package.json | 2 +- src/routes/v3/price.js | 41 ++++++++++++++++++++-- test/v3/integration/price.js | 13 ++++++- test/v3/mocks/price-mock.js | 24 +++++++++++-- test/v3/price.js | 66 ++++++++++++++++++++++++++++++++++++ 5 files changed, 140 insertions(+), 6 deletions(-) diff --git a/package.json b/package.json index 5fae3e2..92095fb 100644 --- a/package.json +++ b/package.json @@ -17,7 +17,7 @@ "test": "npm run lint && npm run test-v3", "lint": "standard --env mocha --fix", "test-v3": "export NETWORK=mainnet && nyc --reporter=text mocha --timeout 60000 test/v3/", - "test:temp": "export NETWORK=mainnet && 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:slpdb": "mocha --timeout 25000 -g '#validate2Single' test/v3/integration/slp*.js", "coverage": "nyc report --reporter=text-lcov | coveralls", diff --git a/src/routes/v3/price.js b/src/routes/v3/price.js index 32e5ec5..bf16183 100644 --- a/src/routes/v3/price.js +++ b/src/routes/v3/price.js @@ -24,11 +24,14 @@ class Price { this.routeUtils = routeUtils 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.get('/', _this.root) this.router.get('/usd', _this.getUSD) this.router.get('/rates', _this.getBCHRate) + this.router.get('/bchausd', _this.getBCHAUSD) } // DRY error handler. @@ -53,7 +56,7 @@ class Price { * @api {get} /price/usd Get the USD price of BCH * @apiName Get the USD price of BCH * @apiGroup Price - * @apiDescription Get the USD price of BCH + * @apiDescription Get the USD price of BCH from Coinbase. * * * @apiExample Example usage: @@ -85,7 +88,7 @@ class Price { * @api {get} /price/usd Get rates for several different currencies * @apiName Get rates for several different currencies * @apiGroup Price - * @apiDescription Get rates for several different currencies + * @apiDescription Get rates for several different currencies from Coinbase. * * * @apiExample Example usage: @@ -113,6 +116,40 @@ class Price { 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 diff --git a/test/v3/integration/price.js b/test/v3/integration/price.js index 33106ad..f15cd36 100644 --- a/test/v3/integration/price.js +++ b/test/v3/integration/price.js @@ -4,7 +4,7 @@ 'use strict' -// const assert = require('chai').assert +const assert = require('chai').assert // const axios = require('axios') // Used for debugging. @@ -29,6 +29,17 @@ describe('#price', () => { it('should get the USD price', async () => { const result = await price.getUSD(req, res) 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) }) }) }) diff --git a/test/v3/mocks/price-mock.js b/test/v3/mocks/price-mock.js index 6e3168b..5bf3569 100644 --- a/test/v3/mocks/price-mock.js +++ b/test/v3/mocks/price-mock.js @@ -216,6 +216,26 @@ const mockCoinbaseFeed = { } } -module.exports = { - mockCoinbaseFeed +const mockCoinexFeed = { + 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 } diff --git a/test/v3/price.js b/test/v3/price.js index 23dab3e..691b4eb 100644 --- a/test/v3/price.js +++ b/test/v3/price.js @@ -211,4 +211,70 @@ describe('#PriceRouter', () => { 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) + }) + }) }) From 34ed5ddc4c74938dec42b1bbf20ef78e98af2f7b Mon Sep 17 00:00:00 2001 From: Chris Troutner Date: Tue, 17 Nov 2020 14:27:42 -0800 Subject: [PATCH 3/8] fix(price): Fixing bug in new price endpoint --- src/routes/v3/price.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/routes/v3/price.js b/src/routes/v3/price.js index bf16183..3530493 100644 --- a/src/routes/v3/price.js +++ b/src/routes/v3/price.js @@ -133,7 +133,7 @@ class Price { // Request options const opt = { method: 'get', - baseURL: this.coinexPriceUrl, + baseURL: _this.coinexPriceUrl, timeout: 15000 } From 5ea71bc89bfef42450aca7d1f09c989a160e5253 Mon Sep 17 00:00:00 2001 From: Chris Troutner Date: Fri, 20 Nov 2020 08:36:34 -0800 Subject: [PATCH 4/8] Updating default Docker run script --- docker/mainnet/start-local-mainnet.sh | 6 +++++- docker/testnet/start-local-testnet.sh | 4 ++++ 2 files changed, 9 insertions(+), 1 deletion(-) diff --git a/docker/mainnet/start-local-mainnet.sh b/docker/mainnet/start-local-mainnet.sh index d6b691b..b68cfb7 100755 --- a/docker/mainnet/start-local-mainnet.sh +++ b/docker/mainnet/start-local-mainnet.sh @@ -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. 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_HOST=172.17.0.1 diff --git a/docker/testnet/start-local-testnet.sh b/docker/testnet/start-local-testnet.sh index 2d969d6..14fcacc 100755 --- a/docker/testnet/start-local-testnet.sh +++ b/docker/testnet/start-local-testnet.sh @@ -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. export NODE_TLS_REJECT_UNAUTHORIZED=0 +# Mainnet Fulcrum / ElectrumX +export FULCRUM_URL=172.17.0.1 +export FULCRUM_PORT=50002 + # Redis DB export REDIS_PORT=6380 export REDIS_HOST=172.17.0.1 From 6a0ed46413de1835f8f9d48ed351a5b7c3cea3ce Mon Sep 17 00:00:00 2001 From: Chris Troutner Date: Fri, 20 Nov 2020 20:52:22 -0800 Subject: [PATCH 5/8] fix(electrumx): Automatically reconnecting when Electrumx disconnects --- src/routes/v3/electrumx.js | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/src/routes/v3/electrumx.js b/src/routes/v3/electrumx.js index 43f2bb7..59a5687 100644 --- a/src/routes/v3/electrumx.js +++ b/src/routes/v3/electrumx.js @@ -79,6 +79,21 @@ class Electrum { // Set the connection flag. _this.isReady = true + // Periodically check the connection. If it's not connected, attempt to + // reconnect. + 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(`_this.isReady: ${_this.isReady}`) From efbcbc5debc3fb070c98847577361a296d5b93e8 Mon Sep 17 00:00:00 2001 From: Chris Troutner Date: Sat, 21 Nov 2020 11:15:33 -0800 Subject: [PATCH 6/8] linting --- src/routes/v3/electrumx.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/routes/v3/electrumx.js b/src/routes/v3/electrumx.js index 59a5687..a1e9edf 100644 --- a/src/routes/v3/electrumx.js +++ b/src/routes/v3/electrumx.js @@ -88,9 +88,9 @@ class Electrum { // 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...`) + wlogger.info('Attempting to reconnect...') await _this.electrumx.connect() - wlogger.info(`...reconnected.`) + wlogger.info('...reconnected.') } }, 30000) From 319281c096d5a7b174012395ffa7fa5f8cdb4921 Mon Sep 17 00:00:00 2001 From: Chris Troutner Date: Sat, 21 Nov 2020 11:29:18 -0800 Subject: [PATCH 7/8] fix(electrumx): Fixed connect/disconnect bug that was preventing tests --- src/routes/v3/electrumx.js | 5 ++++- test/v3/a01-electrumx.js | 2 +- 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/src/routes/v3/electrumx.js b/src/routes/v3/electrumx.js index a1e9edf..8e5cc67 100644 --- a/src/routes/v3/electrumx.js +++ b/src/routes/v3/electrumx.js @@ -81,7 +81,7 @@ class Electrum { // Periodically check the connection. If it's not connected, attempt to // reconnect. - setInterval(async function () { + _this.reconnectIntervalHandle = setInterval(async function () { const status = _this.electrumx.connection.status // console.log(`Electrumx status: ${status}`) @@ -111,6 +111,9 @@ class Electrum { // Return immediately if the isReady flag is false. if (!_this.isReady) return true + // Disable the reconnect timer. + clearInterval(_this.reconnectIntervalHandle) + // Disconnect from the server. await _this.electrumx.disconnect() diff --git a/test/v3/a01-electrumx.js b/test/v3/a01-electrumx.js index 0b45b88..c1f003a 100644 --- a/test/v3/a01-electrumx.js +++ b/test/v3/a01-electrumx.js @@ -42,7 +42,7 @@ function expectRouteError (res, result, expectedError, code = 400) { assert.equal(result.success, false) } -describe('#ElectrumX Router', () => { +describe('#Electrumx', () => { let req, res let sandbox const electrumxRoute = new ElecrumxRoute() From f54c69a664b37a7c2a6dab737548e47ae9e3b731 Mon Sep 17 00:00:00 2001 From: Chris Troutner Date: Sat, 21 Nov 2020 11:35:01 -0800 Subject: [PATCH 8/8] Fixing test typo --- test/v3/blockchain.js | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/test/v3/blockchain.js b/test/v3/blockchain.js index cba83ad..548381e 100644 --- a/test/v3/blockchain.js +++ b/test/v3/blockchain.js @@ -821,6 +821,7 @@ describe('#BlockchainRouter', () => { ]) }) }) + describe('getRawMempool()', () => { it('should throw 503 when network issues', async () => { // Save the existing RPC URL. @@ -842,6 +843,7 @@ describe('#BlockchainRouter', () => { 'Error message expected' ) }) + it('returns proper error when downstream service stalls', async () => { // Mock the timeout error. sandbox.stub(uut.axios, 'request').throws({ code: 'ECONNABORTED' }) @@ -856,6 +858,7 @@ describe('#BlockchainRouter', () => { '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' }) @@ -871,7 +874,7 @@ describe('#BlockchainRouter', () => { ) }) - it('should GET /getMempoolInfo', async () => { + it('should GET /getRawMempool', async () => { // Mock the RPC call for unit tests. if (process.env.TEST === 'unit') { sandbox @@ -886,6 +889,7 @@ describe('#BlockchainRouter', () => { // Not sure what other assertions should be made here. }) }) + describe('getMempoolEntrySingle()', () => { it('should throw 400 if txid is empty', async () => { const result = await uut.getMempoolEntrySingle(req, res)