From ff1c37ddb692ce29a903a36b6b9fe7b19e2a81d5 Mon Sep 17 00:00:00 2001 From: Chris Troutner Date: Sat, 5 Dec 2020 08:46:22 -0800 Subject: [PATCH 1/4] fix(validateSingle): Added unit tests for /slp/validateSingle --- package.json | 3 +- src/routes/v3/slp.js | 1 + test/v3/slp.js | 223 +++++++++++++++++++++++++++++-------------- 3 files changed, 152 insertions(+), 75 deletions(-) diff --git a/package.json b/package.json index 92095fb..d2e68f3 100644 --- a/package.json +++ b/package.json @@ -17,7 +17,8 @@ "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/integration/price.js", + "test:temp:integration": "export NETWORK=mainnet && export TEST=integration && mocha --timeout 25000 test/v3/integration/price.js", + "test:temp:unit": "export NETWORK=mainnet && mocha -g '#validateSingle' --exit test/v3/", "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/slp.js b/src/routes/v3/slp.js index 8e4301e..5778167 100644 --- a/src/routes/v3/slp.js +++ b/src/routes/v3/slp.js @@ -1110,6 +1110,7 @@ class Slp { } // Get data from SLPDB. const tokenRes = await _this.axios.request(opt) + // console.log(`tokenRes.data: ${JSON.stringify(tokenRes.data, null, 2)}`) // Default return value. let result = { diff --git a/test/v3/slp.js b/test/v3/slp.js index e847904..cd685f9 100644 --- a/test/v3/slp.js +++ b/test/v3/slp.js @@ -338,6 +338,155 @@ describe('#SLP', () => { } }) + describe('#validate2Single', () => { + it('should throw 400 if txid is empty', async () => { + req.params.txid = '' + const result = await slpRoute.validate2Single(req, res) + // console.log(`result: ${util.inspect(result)}`) + + assert.hasAllKeys(result, ['error']) + assert.include(result.error, 'txid can not be empty') + }) + + it('should invalidate a known invalid TXID', async () => { + if (process.env.TEST === 'unit') { + // Mock to prevent live network connection. + sandbox + .stub(slpRoute.axios, 'request') + .resolves({ data: { isValid: false } }) + } + + const txid = + 'f7e5199ef6669ad4d078093b3ad56e355b6ab84567e59ad0f08a5ad0244f783a' + + req.params.txid = txid + const result = await slpRoute.validate2Single(req, res) + // console.log(`result: ${JSON.stringify(result, null, 2)}`) + + assert.equal(result.txid, txid) + assert.equal(result.isValid, false) + }) + + it('should validate a known valid TXID', async () => { + if (process.env.TEST === 'unit') { + // Mock to prevent live network connection. + sandbox + .stub(slpRoute.axios, 'request') + .resolves({ data: { isValid: true } }) + } + + const txid = + '3a4b628cbcc183ab376d44ce5252325f042268307ffa4a53443e92b6d24fb488' + + req.params.txid = txid + const result = await slpRoute.validate2Single(req, res) + // console.log(`result: ${JSON.stringify(result, null, 2)}`) + + assert.equal(result.txid, txid) + assert.equal(result.isValid, true) + }) + + // This test can only be run as a mocked unit test. It's too inconsistent + // to run as an integration test, due to the caching built into slp-validate. + if (process.env.TEST === 'unit') { + it('should cancel if validation takes too long', async () => { + // Mock the timeout error. + sandbox.stub(slpRoute.axios, 'request').throws({ + code: 'ECONNABORTED' + }) + + const txid = + 'eacb1085dfa296fef6d4ae2c0f4529a1bef096dd2325bdcc6dcb5241b3bdb579' + + req.params.txid = txid + const result = await slpRoute.validate2Single(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' + ) + }) + } + }) + + describe('#validateSingle', () => { + it('should throw 400 if txid is empty', async () => { + req.params.txid = '' + const result = await slpRoute.validateSingle(req, res) + // console.log(`result: ${util.inspect(result)}`) + + assert.hasAllKeys(result, ['error']) + assert.include(result.error, 'txid can not be empty') + }) + + it('should invalidate a known invalid TXID', async () => { + if (process.env.TEST === 'unit') { + // Mock to prevent live network connection. + sandbox.stub(slpRoute.axios, 'request').resolves({ + data: { + c: [], + u: [] + } + }) + } + + const txid = + 'f7e5199ef6669ad4d078093b3ad56e355b6ab84567e59ad0f08a5ad0244f783a' + + req.params.txid = txid + const result = await slpRoute.validateSingle(req, res) + // console.log(`result: ${JSON.stringify(result, null, 2)}`) + + assert.equal(result.txid, txid) + assert.equal(result.valid, false) + }) + + it('should validate a known valid TXID', async () => { + if (process.env.TEST === 'unit') { + // Mock to prevent live network connection. + sandbox + .stub(slpRoute.axios, 'request') + .resolves({ data: mockData.mockSingleValidTxid }) + } + + const txid = + '77872738b6bddee6c0cbdb9509603de20b15d4f6b26602f629417aec2f5d5e8d' + + req.params.txid = txid + const result = await slpRoute.validateSingle(req, res) + // console.log(`result: ${JSON.stringify(result, null, 2)}`) + + assert.equal(result.txid, txid) + assert.equal(result.valid, true) + }) + + if (process.env.TEST === 'unit') { + it('should cancel if validation takes too long', async () => { + // Mock the timeout error. + sandbox.stub(slpRoute.axios, 'request').throws({ + code: 'ECONNABORTED' + }) + + const txid = + 'eacb1085dfa296fef6d4ae2c0f4529a1bef096dd2325bdcc6dcb5241b3bdb579' + + req.params.txid = txid + const result = await slpRoute.validateSingle(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' + ) + }) + } + }) + describe('validateBulk()', () => { const validateBulk = slpRoute.validateBulk @@ -463,80 +612,6 @@ describe('#SLP', () => { }) }) - describe('#validate2Single', () => { - it('should throw 400 if txid is empty', async () => { - req.params.txid = '' - const result = await slpRoute.validate2Single(req, res) - // console.log(`result: ${util.inspect(result)}`) - - assert.hasAllKeys(result, ['error']) - assert.include(result.error, 'txid can not be empty') - }) - - it('should invalidate a known invalid TXID', async () => { - if (process.env.TEST === 'unit') { - // Mock to prevent live network connection. - sandbox - .stub(slpRoute.axios, 'request') - .resolves({ data: { isValid: false } }) - } - - const txid = - 'f7e5199ef6669ad4d078093b3ad56e355b6ab84567e59ad0f08a5ad0244f783a' - - req.params.txid = txid - const result = await slpRoute.validate2Single(req, res) - // console.log(`result: ${JSON.stringify(result, null, 2)}`) - - assert.equal(result.txid, txid) - assert.equal(result.isValid, false) - }) - - it('should validate a known valid TXID', async () => { - if (process.env.TEST === 'unit') { - // Mock to prevent live network connection. - sandbox - .stub(slpRoute.axios, 'request') - .resolves({ data: { isValid: true } }) - } - - const txid = - '3a4b628cbcc183ab376d44ce5252325f042268307ffa4a53443e92b6d24fb488' - - req.params.txid = txid - const result = await slpRoute.validate2Single(req, res) - // console.log(`result: ${JSON.stringify(result, null, 2)}`) - - assert.equal(result.txid, txid) - assert.equal(result.isValid, true) - }) - - // This test can only be run as a mocked unit test. It's too inconsistent - // to run as an integration test, due to the caching built into slp-validate. - if (process.env.TEST === 'unit') { - it('should cancel if validation takes too long', async () => { - // Mock the timeout error. - sandbox.stub(slpRoute.axios, 'request').throws({ - code: 'ECONNABORTED' - }) - - const txid = - 'eacb1085dfa296fef6d4ae2c0f4529a1bef096dd2325bdcc6dcb5241b3bdb579' - - req.params.txid = txid - const result = await slpRoute.validate2Single(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' - ) - }) - } - }) - describe('tokenStats()', () => { it('should throw 400 if tokenID is empty', async () => { req.params.tokenId = '' From d93d20321d1f91f1b00937cdd297419a9a76354a Mon Sep 17 00:00:00 2001 From: Chris Troutner Date: Sat, 5 Dec 2020 09:31:38 -0800 Subject: [PATCH 2/4] feat(validate3Single): Adding third SLP validation option using whitelist server --- src/routes/v3/slp.js | 140 +++++++++++++++++++++++++++++++++++++++++++ test/v3/slp.js | 75 +++++++++++++++++++++++ 2 files changed, 215 insertions(+) diff --git a/src/routes/v3/slp.js b/src/routes/v3/slp.js index 5778167..94a176c 100644 --- a/src/routes/v3/slp.js +++ b/src/routes/v3/slp.js @@ -74,6 +74,8 @@ class Slp { _this.router.post('/validateTxid', _this.validateBulk) _this.router.get('/validateTxid/:txid', _this.validateSingle) _this.router.get('/validateTxid2/:txid', _this.validate2Single) + _this.router.get('/validateTxid3/:txid', _this.validate3Single) + _this.router.get('/whitelist', _this.getSlpWhitelist) _this.router.get('/txDetails/:txid', _this.txDetails) _this.router.get('/tokenStats/:tokenId', _this.tokenStats) _this.router.get( @@ -1200,6 +1202,144 @@ class Slp { } } + /** + * @api {get} /slp/whitelist SLP token whitelist. + * @apiName SLP token whitelist + * @apiGroup SLP + * @apiDescription Get tokens that are on the whitelist. + * SLPDB is typically used to validate SLP transactions. It can become unstable + * during periods of high network usage. A second SLPDB has been implemented + * that is much more stable, because it only tracks a whitelist of SLP tokens. + * This endpoint will return information on the SLP tokens that are included + * in that whitelist. + * + * For tokens on the whitelist, the /slp/validateTxid3 endpoints can be used. + * + * + * @apiExample Example usage: + * curl -X GET "https://api.fullstack.cash/v3/slp/whitelist" -H "accept:application/json" + * + */ + async getSlpWhitelist (req, res, next) { + try { + const list = [ + { + name: 'USDH', + tokenId: + 'c4b0d62156b3fa5c8f3436079b5394f7edc1bef5dc1cd2f9d0c4d46f82cca479' + }, + { + name: 'SPICE', + tokenId: + '4de69e374a8ed21cbddd47f2338cc0f479dc58daa2bbe11cd604ca488eca0ddf' + }, + { + name: 'PSF', + tokenId: + '38e97c5d7d3585a2cbf3f9580c82ca33985f9cb0845d4dcce220cb709f9538b0' + }, + { + name: 'TROUT', + tokenId: + 'a4fb5c2da1aa064e25018a43f9165040071d9e984ba190c222a7f59053af84b2' + }, + { + name: 'PSFTEST', + tokenId: + 'd0ef4de95b78222bfee2326ab11382f4439aa0855936e2fe6ac129a8d778baa0' + } + ] + + res.status(200) + return res.json(list) + } catch (err) { + wlogger.error('Error in slp.ts/whitelist().', err) + + return _this.errorHandler(err, res) + } + } + + /** + * @api {get} /slp/validateTxid3/{txid} Validate single SLP transaction by txid. + * @apiName Validate single SLP transaction by txid. + * @apiGroup SLP + * @apiDescription Alternative validation for tokens on the whitelist + * This endpoint is exactly the same as /slp/validateTxid/{txid} but it uses + * a different SLPDB. This server only indexes the SLP tokens that are on the + * whitelist. You can see which tokens are on the whitelist by calling the + * /slp/whitelist endpoint. + * + * @apiExample Example usage: + * curl -X GET "https://api.fullstack.cash/v3/slp/validateTxid3/f7e5199ef6669ad4d078093b3ad56e355b6ab84567e59ad0f08a5ad0244f783a" -H "accept:application/json" + * + * + */ + async validate3Single (req, res, next) { + try { + const txid = req.params.txid + + // Validate input + if (!txid || txid === '') { + res.status(400) + return res.json({ error: 'txid can not be empty' }) + } + + wlogger.debug('Executing slp/validate/:txid with this txid: ', txid) + + const query = { + v: 3, + q: { + db: ['c', 'u'], + find: { + 'tx.h': txid + }, + limit: 300, + project: { 'slp.valid': 1, 'tx.h': 1, 'slp.invalidReason': 1 } + } + } + + const options = _this.generateCredentials() + + const s = JSON.stringify(query) + const b64 = Buffer.from(s).toString('base64') + const url = `${process.env.SLPDB_WHITELIST_URL}q/${b64}` + const opt = { + method: 'get', + baseURL: url, + headers: options.headers, + timeout: options.timeout + } + // Get data from SLPDB. + const tokenRes = await _this.axios.request(opt) + // console.log(`tokenRes.data: ${JSON.stringify(tokenRes.data, null, 2)}`) + + // Default return value. + let result = { + txid: txid, + valid: false + } + + // Build result. + const concatArray = tokenRes.data.c.concat(tokenRes.data.u) + if (concatArray.length > 0) { + result = { + txid: concatArray[0].tx.h, + valid: concatArray[0].slp.valid + } + if (!result.valid) { + result.invalidReason = concatArray[0].slp.invalidReason + } + } + + res.status(200) + return res.json(result) + } catch (err) { + wlogger.error('Error in slp.js/validate3Single().', err) + + return _this.errorHandler(err, res) + } + } + /** * @api {get} /slp/txDetails/{txid} SLP transaction details. * @apiName SLP transaction details. diff --git a/test/v3/slp.js b/test/v3/slp.js index cd685f9..c5f57f2 100644 --- a/test/v3/slp.js +++ b/test/v3/slp.js @@ -487,6 +487,81 @@ describe('#SLP', () => { } }) + describe('#validate3Single', () => { + it('should throw 400 if txid is empty', async () => { + req.params.txid = '' + const result = await slpRoute.validate3Single(req, res) + // console.log(`result: ${util.inspect(result)}`) + + assert.hasAllKeys(result, ['error']) + assert.include(result.error, 'txid can not be empty') + }) + + it('should invalidate a known invalid TXID', async () => { + if (process.env.TEST === 'unit') { + // Mock to prevent live network connection. + sandbox.stub(slpRoute.axios, 'request').resolves({ + data: { + c: [], + u: [] + } + }) + } + + const txid = + 'f7e5199ef6669ad4d078093b3ad56e355b6ab84567e59ad0f08a5ad0244f783a' + + req.params.txid = txid + const result = await slpRoute.validate3Single(req, res) + // console.log(`result: ${JSON.stringify(result, null, 2)}`) + + assert.equal(result.txid, txid) + assert.equal(result.valid, false) + }) + + it('should validate a known valid TXID', async () => { + if (process.env.TEST === 'unit') { + // Mock to prevent live network connection. + sandbox + .stub(slpRoute.axios, 'request') + .resolves({ data: mockData.mockSingleValidTxid }) + } + + const txid = + 'cdfed769ef7b69e09be06d2821b88598d9a5d711b8f9bd369763c78b7a578fbc' + + req.params.txid = txid + const result = await slpRoute.validate3Single(req, res) + // console.log(`result: ${JSON.stringify(result, null, 2)}`) + + // assert.equal(result.txid, txid) + assert.equal(result.valid, true) + }) + + if (process.env.TEST === 'unit') { + it('should cancel if validation takes too long', async () => { + // Mock the timeout error. + sandbox.stub(slpRoute.axios, 'request').throws({ + code: 'ECONNABORTED' + }) + + const txid = + 'eacb1085dfa296fef6d4ae2c0f4529a1bef096dd2325bdcc6dcb5241b3bdb579' + + req.params.txid = txid + const result = await slpRoute.validate3Single(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' + ) + }) + } + }) + describe('validateBulk()', () => { const validateBulk = slpRoute.validateBulk From f2c0cd6febf78e19f734a4491ad01b9679d6ba06 Mon Sep 17 00:00:00 2001 From: Chris Troutner Date: Sat, 5 Dec 2020 09:48:05 -0800 Subject: [PATCH 3/4] feat(validate3Bulk): Adding bulk SLP tx validtion for whitelist endpoint --- package.json | 2 +- src/routes/v3/slp.js | 124 ++++++++++++++++++++++++++++++++++++ test/v3/mocks/slp-mocks.js | 19 +++++- test/v3/slp.js | 125 +++++++++++++++++++++++++++++++++++++ 4 files changed, 268 insertions(+), 2 deletions(-) diff --git a/package.json b/package.json index d2e68f3..b928f09 100644 --- a/package.json +++ b/package.json @@ -18,7 +18,7 @@ "lint": "standard --env mocha --fix", "test-v3": "export NETWORK=mainnet && nyc --reporter=text mocha --timeout 60000 test/v3/", "test:temp:integration": "export NETWORK=mainnet && export TEST=integration && mocha --timeout 25000 test/v3/integration/price.js", - "test:temp:unit": "export NETWORK=mainnet && mocha -g '#validateSingle' --exit test/v3/", + "test:temp:unit": "export NETWORK=mainnet && mocha -g '#validate3Bulk' --exit test/v3/", "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/slp.js b/src/routes/v3/slp.js index 94a176c..0a85637 100644 --- a/src/routes/v3/slp.js +++ b/src/routes/v3/slp.js @@ -75,6 +75,7 @@ class Slp { _this.router.get('/validateTxid/:txid', _this.validateSingle) _this.router.get('/validateTxid2/:txid', _this.validate2Single) _this.router.get('/validateTxid3/:txid', _this.validate3Single) + _this.router.post('/validateTxid3', _this.validate3Bulk) _this.router.get('/whitelist', _this.getSlpWhitelist) _this.router.get('/txDetails/:txid', _this.txDetails) _this.router.get('/tokenStats/:tokenId', _this.tokenStats) @@ -1340,6 +1341,129 @@ class Slp { } } + /** + * @api {post} /slp/validateTxid3/ Validate multiple SLP transactions by txid. + * @apiName Validate multiple SLP transactions by txid. + * @apiGroup SLP + * @apiDescription Alternative validation for tokens on the whitelist + * This endpoint is exactly the same as /slp/validateTxid but it uses + * a different SLPDB. This server only indexes the SLP tokens that are on the + * whitelist. You can see which tokens are on the whitelist by calling the + * /slp/whitelist endpoint. + * + * @apiExample Example usage: + * curl -X POST "https://api.fullstack.cash/v3/slp/validateTxid3" -H "accept:application/json" -H "Content-Type: application/json" -d '{"txids":["f7e5199ef6669ad4d078093b3ad56e355b6ab84567e59ad0f08a5ad0244f783a","fb0eeaa501a6e1acb721669c62a3f70741f48ae0fd7f4b8e1d72088785c51952"]}' + * + * + */ + async validate3Bulk (req, res, next) { + try { + const txids = req.body.txids + + // Reject if txids is not an array. + if (!Array.isArray(txids)) { + res.status(400) + return res.json({ error: 'txids needs to be an array' }) + } + + // Enforce array size rate limits + if (!_this.routeUtils.validateArraySize(req, txids)) { + res.status(429) // https://github.com/Bitcoin-com/rest.bitcoin.com/issues/330 + return res.json({ + error: 'Array too large.' + }) + } + + wlogger.debug('Executing slp/validate with these txids: ', txids) + + const query = { + v: 3, + q: { + db: ['c', 'u'], + find: { + 'tx.h': { $in: txids } + }, + limit: 300, + project: { 'slp.valid': 1, 'tx.h': 1, 'slp.invalidReason': 1 } + } + } + const s = JSON.stringify(query) + const b64 = Buffer.from(s).toString('base64') + const url = `${process.env.SLPDB_WHITELIST_URL}q/${b64}` + + const options = _this.generateCredentials() + + // Get data from SLPDB. + const opt = { + method: 'get', + baseURL: url, + headers: options.headers, + timeout: options.timeout + } + const tokenRes = await _this.axios.request(opt) + // console.log(`tokenRes.data: ${JSON.stringify(tokenRes.data, null, 2)}`) + + let formattedTokens = [] + + // Combine the arrays. Why? Generally there is nothing in the u array. + const concatArray = tokenRes.data.c.concat(tokenRes.data.u) + + const tokenIds = [] + if (concatArray.length > 0) { + concatArray.forEach((token) => { + tokenIds.push(token.tx.h) // txid + + const validationResult = { + txid: token.tx.h, + valid: token.slp.valid + } + + // If the txid is invalid, add the reason it's invalid. + if (!validationResult.valid) { + validationResult.invalidReason = token.slp.invalidReason + } + + formattedTokens.push(validationResult) + }) + + // If a user-provided txid doesn't exist in the data, add it with + // valid:false property. + txids.forEach((txid) => { + if (!tokenIds.includes(txid)) { + formattedTokens.push({ + txid: txid, + valid: false + }) + } + }) + } + + // Catch a corner case of repeated txids. SLPDB will remove redundent TXIDs, + // which will cause the output array to be smaller than the input array. + if (txids.length > formattedTokens.length) { + const newOutput = [] + for (let i = 0; i < txids.length; i++) { + const thisTxid = txids[i] + + // Find the element that matches the current txid. + const elem = formattedTokens.filter((x) => x.txid === thisTxid) + + newOutput.push(elem[0]) + } + + // Replace the original output object with the new output object. + formattedTokens = newOutput + } + + res.status(200) + return res.json(formattedTokens) + } catch (err) { + wlogger.error('Error in slp.ts/validateBulk().', err) + + return _this.errorHandler(err, res) + } + } + /** * @api {get} /slp/txDetails/{txid} SLP transaction details. * @apiName SLP transaction details. diff --git a/test/v3/mocks/slp-mocks.js b/test/v3/mocks/slp-mocks.js index 198e9b7..736c2f2 100644 --- a/test/v3/mocks/slp-mocks.js +++ b/test/v3/mocks/slp-mocks.js @@ -341,6 +341,22 @@ const mockTwoRedundentTxid = { u: [] } +const mockPsfToken = { + c: [ + { + _id: '5fcaf6152898f9887902986c', + tx: { + h: 'cdfed769ef7b69e09be06d2821b88598d9a5d711b8f9bd369763c78b7a578fbc' + }, + slp: { + valid: true, + invalidReason: null + } + } + ], + u: [] +} + const mockTxHistory = [ { tx: { @@ -541,5 +557,6 @@ module.exports = { mockSingleValidTxid, mockTwoValidTxid, mockTwoRedundentTxid, - mockTxHistory + mockTxHistory, + mockPsfToken } diff --git a/test/v3/slp.js b/test/v3/slp.js index c5f57f2..9f6d6b7 100644 --- a/test/v3/slp.js +++ b/test/v3/slp.js @@ -687,6 +687,131 @@ describe('#SLP', () => { }) }) + describe('#validate3Bulk', () => { + const validate3Bulk = slpRoute.validate3Bulk + + it('should throw 400 if txid array is empty', async () => { + const result = await validate3Bulk(req, res) + // console.log(`result: ${util.inspect(result)}`) + + assert.hasAllKeys(result, ['error']) + assert.include(result.error, 'txids needs to be an array') + assert.equal(res.statusCode, 400) + }) + + it('should throw 400 error if array is too large', async () => { + const testArray = [] + for (var i = 0; i < 25; i++) testArray.push('') + + req.body.txids = testArray + + const result = await validate3Bulk(req, res) + // console.log(`result: ${util.inspect(result)}`) + + assert.hasAllKeys(result, ['error']) + assert.include(result.error, 'Array too large') + }) + + it('returns proper error when downstream service stalls', async () => { + // Mock the timeout error. + sandbox.stub(slpRoute.axios, 'request').throws({ code: 'ECONNABORTED' }) + + req.body.txids = [ + '77872738b6bddee6c0cbdb9509603de20b15d4f6b26602f629417aec2f5d5e8d' + ] + const result = await validate3Bulk(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(slpRoute.axios, 'request').throws({ code: 'ECONNREFUSED' }) + + req.body.txids = [ + '77872738b6bddee6c0cbdb9509603de20b15d4f6b26602f629417aec2f5d5e8d' + ] + const result = await validate3Bulk(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 validate array with single element', async () => { + // Mock the RPC call for unit tests. + if (process.env.TEST === 'unit') { + sandbox.stub(slpRoute.axios, 'request').resolves({ + data: mockData.mockPsfToken + }) + } + + req.body.txids = [ + 'cdfed769ef7b69e09be06d2821b88598d9a5d711b8f9bd369763c78b7a578fbc' + ] + + const result = await validate3Bulk(req, res) + // console.log(`result: ${util.inspect(result)}`) + + assert.isArray(result) + assert.hasAllKeys(result[0], ['txid', 'valid']) + }) + + it('should validate array with two elements', async () => { + // Mock the RPC call for unit tests. + if (process.env.TEST === 'unit') { + sandbox.stub(slpRoute.axios, 'request').resolves({ + data: mockData.mockPsfToken + }) + } + + req.body.txids = [ + 'cdfed769ef7b69e09be06d2821b88598d9a5d711b8f9bd369763c78b7a578fbc', + 'cdfed769ef7b69e09be06d2821b88598d9a5d711b8f9bd369763c78b7a578fbc' + ] + + const result = await validate3Bulk(req, res) + // console.log(`result: ${util.inspect(result)}`) + + assert.isArray(result) + assert.hasAllKeys(result[0], ['txid', 'valid']) + assert.equal(result.length, 2) + }) + + // Captures a regression bug that went out to production, captured in this + // GitHub Issue: https://github.com/Bitcoin-com/rest.bitcoin.com/issues/518 + it('should return two elements if given two elements', async () => { + // Mock the RPC call for unit tests. + if (process.env.TEST === 'unit') { + sandbox.stub(slpRoute.axios, 'request').resolves({ + data: mockData.mockPsfToken + }) + } + + req.body.txids = [ + 'cdfed769ef7b69e09be06d2821b88598d9a5d711b8f9bd369763c78b7a578fbc', + 'cdfed769ef7b69e09be06d2821b88598d9a5d711b8f9bd369763c78b7a578fbc' + ] + + const result = await validate3Bulk(req, res) + // console.log(`result: ${util.inspect(result)}`) + + assert.isArray(result) + assert.hasAllKeys(result[0], ['txid', 'valid']) + assert.equal(result.length, 2) + }) + }) + describe('tokenStats()', () => { it('should throw 400 if tokenID is empty', async () => { req.params.tokenId = '' From d61ea0831ad093825fe4d85c9a0d8179c53c721f Mon Sep 17 00:00:00 2001 From: Chris Troutner Date: Sat, 5 Dec 2020 11:02:44 -0800 Subject: [PATCH 4/4] Testing new validation calls against BCHN and BCHA --- src/routes/v3/slp.js | 4 +++- start-dev-example.sh | 11 ++++++++--- test/v3/slp.js | 2 +- 3 files changed, 12 insertions(+), 5 deletions(-) diff --git a/src/routes/v3/slp.js b/src/routes/v3/slp.js index 0a85637..0a84a33 100644 --- a/src/routes/v3/slp.js +++ b/src/routes/v3/slp.js @@ -990,6 +990,7 @@ class Slp { const s = JSON.stringify(query) const b64 = Buffer.from(s).toString('base64') const url = `${process.env.SLPDB_URL}q/${b64}` + // console.log('url: ', url) const options = _this.generateCredentials() @@ -1390,6 +1391,7 @@ class Slp { const s = JSON.stringify(query) const b64 = Buffer.from(s).toString('base64') const url = `${process.env.SLPDB_WHITELIST_URL}q/${b64}` + // console.log('url: ', url) const options = _this.generateCredentials() @@ -1458,7 +1460,7 @@ class Slp { res.status(200) return res.json(formattedTokens) } catch (err) { - wlogger.error('Error in slp.ts/validateBulk().', err) + wlogger.error('Error in slp.js/validate3Bulk().', err) return _this.errorHandler(err, res) } diff --git a/start-dev-example.sh b/start-dev-example.sh index 713c56b..f64313f 100755 --- a/start-dev-example.sh +++ b/start-dev-example.sh @@ -8,6 +8,14 @@ export NETWORK=mainnet # SLPDB export SLPDB_URL=http://:12300/ +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://: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://:9131/ @@ -23,9 +31,6 @@ export TOKENSECRET=somelongpassword # So that bch-api can call bch-js locally. export LOCAL_RESTURL=http://127.0.0.1:3000/v3/ -# slp-api alternative SLP validator. -export SLP_API_URL=http://10.0.0.5:5001/ - # Basic Authentication password export PRO_PASS=somerandomepassword:someotherrandompassword:aThirdPassword diff --git a/test/v3/slp.js b/test/v3/slp.js index 9f6d6b7..a842aeb 100644 --- a/test/v3/slp.js +++ b/test/v3/slp.js @@ -513,7 +513,7 @@ describe('#SLP', () => { req.params.txid = txid const result = await slpRoute.validate3Single(req, res) - // console.log(`result: ${JSON.stringify(result, null, 2)}`) + console.log(`result: ${JSON.stringify(result, null, 2)}`) assert.equal(result.txid, txid) assert.equal(result.valid, false)