diff --git a/CHANGELOG.md b/CHANGELOG.md index 7e10b011f..9f8269ea9 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,14 @@ +## [0.11.1](https://github.com/dashevo/dapi/compare/v0.11.0...v0.11.1) (2020-03-17) + +### Bug Fixes + +* throw correct JSON RPC error on invalid Insight params (#252, [52b1276](https://github.com/dashevo/dapi/commit/52b12765b2a369099d7700bdb077a9d6454d99b5)) + + # [0.11.0](https://github.com/dashevo/dapi/compare/v0.9.0...v0.11.0) (2020-03-09) +### Bug Fixes + * Core gRPC service is not initialized ([86dff35](https://github.com/dashevo/dapi/commit/86dff354415669e206e543b3b83704eaf62ceb32)) * load .env at correct time for tx-filter-stream ([7b091e0](https://github.com/dashevo/dapi/commit/7b091e0cefcd7d6c63829bd6229a0c3e8d4b692f)) * prevent to update dependencies with major version `0` to minor versions ([ea7de93](https://github.com/dashevo/js-dpp/commit/ea7de9379a38b856f4a7b779786986afacd75b0d)) @@ -10,17 +19,20 @@ * handle Tendermint errors in applyStateTransition ([f8764e9](https://github.com/dashevo/dapi/commit/f8764e901c09445e66319fc5d2ff7cf8bc0dd7da)) * "not found" instead of "invalid argument" in gRPC endpoints ([126c929](https://github.com/dashevo/dapi/commit/126c92905d63e2b63f9949d3c58d3a469e680201)) + ### Features * remove insecure API endpoints and code ([11b3df3](https://github.com/dashevo/dapi/commit/11b3df3c3dd0fef9d892320f35745b1b68b5b66c)) * introduce `generateToAddress` endpoint ([3a2f497](https://github.com/dashevo/dapi/commit/3a2f49737f5cc75c02a3abffb64b2060b14beb39)) * upgrade DPP to 0.11 ([3b36078](https://github.com/dashevo/dapi/commit/3b360787697d9cfb7f5088058cf11ea12a516c50)) + ### Tests * functional test for `getStatus` endpoint ([3f3ec06](https://github.com/dashevo/dapi/commit/3f3ec0606c3a2b6875fa40c17943ac080bc945eb)) * forced json rpc client tests ([5259535](https://github.com/dashevo/dapi/commit/52595357bef4ee0c0ed9d704a2232cfa59b9a11c)) + ### BREAKING CHANGES * A ton of insecure endpoints were removed so it's easier to list what left. diff --git a/lib/externalApis/insight/index.js b/lib/externalApis/insight/index.js index cdd59903e..6d39d38f9 100644 --- a/lib/externalApis/insight/index.js +++ b/lib/externalApis/insight/index.js @@ -91,7 +91,7 @@ const getAddressUnconfirmedBalance = async address => ( : get(`/addr/${address}/unconfirmedBalance`) ); -const getAddressSummary = async (address, noTxList = false, from, to, fromHeight, toHeight) => ( +const getAddressSummary = async (address, noTxList = false, from = '', to = '', fromHeight = '', toHeight = '') => ( Array.isArray(address) ? get(`/addrs/${address.join()}?noTxList=${+noTxList}&from=${from}&to=${to}&fromHeight=${fromHeight}&to=${toHeight}`) : get(`/addr/${address}?noTxList=${+noTxList}&from=${from}&to=${to}&fromHeight=${fromHeight}&to=${toHeight}`) diff --git a/lib/rpcServer/commands/getAddressSummary.js b/lib/rpcServer/commands/getAddressSummary.js index 65edcb114..169b001f3 100644 --- a/lib/rpcServer/commands/getAddressSummary.js +++ b/lib/rpcServer/commands/getAddressSummary.js @@ -1,13 +1,15 @@ const Validator = require('../../utils/Validator'); const argsSchema = require('../schemas/addresses'); +const RPCError = require('../RPCError'); + const validator = new Validator(argsSchema); /** - * @param coreAPI + * @param {InsightAPI} insightAPI * @return {getAddressSummary} */ -const getAddressSummaryFactory = (coreAPI) => { +const getAddressSummaryFactory = (insightAPI) => { /** * Layer 1 endpoint * get summary for address @@ -19,7 +21,20 @@ const getAddressSummaryFactory = (coreAPI) => { async function getAddressSummary(args) { validator.validate(args); const { address } = args; - return coreAPI.getAddressSummary(address); + + let result; + + try { + result = await insightAPI.getAddressSummary(address); + } catch (e) { + if (e.statusCode === 400) { + throw new RPCError(-32602, e.message || 'Invalid params'); + } + + throw new RPCError(-32603, e.message || 'Internal error'); + } + + return result; } return getAddressSummary; diff --git a/lib/rpcServer/commands/getMnListDiff.js b/lib/rpcServer/commands/getMnListDiff.js index 2257ec2d5..ea86874df 100644 --- a/lib/rpcServer/commands/getMnListDiff.js +++ b/lib/rpcServer/commands/getMnListDiff.js @@ -20,6 +20,7 @@ const getMnListDiffFactory = (coreAPI) => { async function getMnListDiff(args) { validator.validate(args); const { baseBlockHash, blockHash } = args; + return coreAPI.getMnListDiff(baseBlockHash, blockHash); } diff --git a/lib/rpcServer/commands/getUTXO.js b/lib/rpcServer/commands/getUTXO.js index 7aa1f6d11..7442dea27 100644 --- a/lib/rpcServer/commands/getUTXO.js +++ b/lib/rpcServer/commands/getUTXO.js @@ -2,14 +2,15 @@ const Validator = require('../../utils/Validator'); const argsSchema = require('../schemas/addresses'); const ArgumentsValidationError = require('../../errors/ArgumentsValidationError'); -const validator = new Validator(argsSchema); +const RPCError = require('../RPCError'); +const validator = new Validator(argsSchema); /** - * @param coreAPI + * @param {InsightAPI} insightAPI * @return {getUTXO} */ -const getUTXOFactory = (coreAPI) => { +const getUTXOFactory = (insightAPI) => { /** * Layer 1 endpoint * Returns unspent outputs for the given address @@ -30,7 +31,19 @@ const getUTXOFactory = (coreAPI) => { if (from !== undefined && to !== undefined && to - from > 1000) { throw new ArgumentsValidationError(`"from" (${from}) and "to" (${to}) range should be less than or equal to 1000`); } - return coreAPI.getUTXO(address, from, to, fromHeight, toHeight); + + let result; + try { + result = await insightAPI.getUTXO(address, from, to, fromHeight, toHeight); + } catch (e) { + if (e.statusCode === 400) { + throw new RPCError(-32602, e.message || 'Invalid params'); + } + + throw new RPCError(-32603, e.message || 'Internal error'); + } + + return result; } return getUTXO; diff --git a/package-lock.json b/package-lock.json index 259a2343d..734b39cb5 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,6 +1,6 @@ { "name": "@dashevo/dapi", - "version": "0.11.0", + "version": "0.11.1", "lockfileVersion": 1, "requires": true, "dependencies": { @@ -80,13 +80,13 @@ "dev": true }, "@babel/polyfill": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/polyfill/-/polyfill-7.8.3.tgz", - "integrity": "sha512-0QEgn2zkCzqGIkSWWAEmvxD7e00Nm9asTtQvi7HdlYvMhjy/J38V/1Y9ode0zEJeIuxAI0uftiAzqc7nVeWUGg==", + "version": "7.8.7", + "resolved": "https://registry.npmjs.org/@babel/polyfill/-/polyfill-7.8.7.tgz", + "integrity": "sha512-LeSfP9bNZH2UOZgcGcZ0PIHUt1ZuHub1L3CVmEyqLxCeDLm4C5Gi8jRH8ZX2PNpDhQCo0z6y/+DIs2JlliXW8w==", "dev": true, "requires": { "core-js": "^2.6.5", - "regenerator-runtime": "^0.13.2" + "regenerator-runtime": "^0.13.4" } }, "@babel/template": { @@ -146,9 +146,8 @@ } }, "@dashevo/dapi-client": { - "version": "0.9.0-dev.6", - "resolved": "https://registry.npmjs.org/@dashevo/dapi-client/-/dapi-client-0.9.0-dev.6.tgz", - "integrity": "sha512-izM1zzp+HDXinQNOx3wIvHiURc80JWJewserwq6ipeLfsAaIIDh6fCN20s/wZ0mLN7THYfR23VwtaMfEkLg1Zw==", + "version": "0.9.0-dev.7", + "resolved": "github:dashevo/dapi-client#f235eb2a4521014fdaf2ab2885fc6eb4075865b7", "dev": true, "requires": { "@babel/polyfill": "^7.8.3", @@ -156,6 +155,7 @@ "@dashevo/dash-spv": "^1.1.6", "@dashevo/dashcore-lib": "~0.18.0", "@dashevo/dpp": "~0.10.1", + "@dashevo/grpc-common": "~0.2.1", "axios": "^0.19.2", "cbor": "^5.0.1", "lodash": "^4.17.15", @@ -300,6 +300,65 @@ "lodash": "^4.17.15", "mongodb": "^3.3.4", "tendermint": "^4.0.8" + }, + "dependencies": { + "@dashevo/dapi-client": { + "version": "0.9.0-dev.7", + "resolved": "https://registry.npmjs.org/@dashevo/dapi-client/-/dapi-client-0.9.0-dev.7.tgz", + "integrity": "sha512-dMb4uGeSbjSFv+z26IlOOZU6J6gk4l08B7YoFUhebIGI7IobGnqtJrIoGh+8hlj9DsjmLJxNTUHlkjIBFfmmqA==", + "dev": true, + "requires": { + "@babel/polyfill": "^7.8.3", + "@dashevo/dapi-grpc": "~0.12.1", + "@dashevo/dash-spv": "^1.1.6", + "@dashevo/dashcore-lib": "~0.18.0", + "@dashevo/dpp": "~0.10.1", + "axios": "^0.19.2", + "cbor": "^5.0.1", + "lodash": "^4.17.15", + "lowdb": "^1.0.0" + }, + "dependencies": { + "cbor": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/cbor/-/cbor-5.0.1.tgz", + "integrity": "sha512-l4ghwqioCyuAaD3LvY4ONwv8NMuERz62xjbMHGdWBqERJPygVmoFER1b4+VS6iW0rXwoVGuKZPPPTofwWOg3YQ==", + "dev": true, + "requires": { + "bignumber.js": "^9.0.0", + "nofilter": "^1.0.3" + } + } + } + }, + "@dashevo/dpp": { + "version": "0.10.1", + "resolved": "https://registry.npmjs.org/@dashevo/dpp/-/dpp-0.10.1.tgz", + "integrity": "sha512-IVqat0OIgbC/cU4Hu9BQvC6hchxFWqFZGnkgyRcgH7pk3iN56KJ74FDvnL+qy4onVT+XAbwXEcbITiOZIhkjLA==", + "dev": true, + "requires": { + "@dashevo/dashcore-lib": "0.18.0", + "ajv": "^6.10.2", + "bs58": "^4.0.1", + "cbor": "^5.0.1", + "lodash.get": "^4.4.2", + "lodash.mergewith": "^4.6.2", + "lodash.set": "^4.3.2", + "multihashes": "^0.4.15" + }, + "dependencies": { + "cbor": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/cbor/-/cbor-5.0.1.tgz", + "integrity": "sha512-l4ghwqioCyuAaD3LvY4ONwv8NMuERz62xjbMHGdWBqERJPygVmoFER1b4+VS6iW0rXwoVGuKZPPPTofwWOg3YQ==", + "dev": true, + "requires": { + "bignumber.js": "^9.0.0", + "nofilter": "^1.0.3" + } + } + } + } } }, "@dashevo/dpp": { @@ -6572,9 +6631,9 @@ } }, "regenerator-runtime": { - "version": "0.13.3", - "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.13.3.tgz", - "integrity": "sha512-naKIZz2GQ8JWh///G7L3X6LaQUAMp2lvb1rvwwsURe/VXwD6VMfr+/1NuNw3ag8v2kY1aQ/go5SNn79O9JU7yw==", + "version": "0.13.5", + "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.13.5.tgz", + "integrity": "sha512-ZS5w8CpKFinUzOwW3c83oPeVXoNsrLsaCoLtJvAClH135j/R77RuymhiSErhm2lKcwSCIpmvIWSbDkIfAqKQlA==", "dev": true }, "regex-not": { diff --git a/package.json b/package.json index 6a0162859..f3f36f1a3 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@dashevo/dapi", - "version": "0.11.0", + "version": "0.11.1", "description": "A decentralized API for the Dash network", "scripts": { "api": "node scripts/api.js", diff --git a/test/functional/rpcServer/commands/getAddressSummary.spec.js b/test/functional/rpcServer/commands/getAddressSummary.spec.js new file mode 100644 index 000000000..f23bfbfe6 --- /dev/null +++ b/test/functional/rpcServer/commands/getAddressSummary.spec.js @@ -0,0 +1,52 @@ +const { + startDapi, +} = require('@dashevo/dp-services-ctl'); + +describe('rpcServer', function main() { + this.timeout(200000); + + let removeDapi; + let dapiClient; + let address; + + beforeEach(async () => { + const { + dapiCore, + dashCore, + remove, + } = await startDapi(); + + removeDapi = remove; + + dapiClient = dapiCore.getApi(); + const coreAPI = dashCore.getApi(); + + ({ result: address } = await coreAPI.getNewAddress()); + + await coreAPI.generateToAddress(500, address); + }); + + afterEach(async () => { + await removeDapi(); + }); + + it('should return address summary', async () => { + const result = await dapiClient.getAddressSummary(address); + + expect(result).to.be.an('object'); + expect(result.addrStr).to.equal(address); + }); + + it('should throw an error on invalid params', async () => { + address = 'Xh7nD4vTUYAxy8GV7t1k8Er9ZKmxRBDcL'; + + try { + await dapiClient.getAddressSummary(address); + + expect.fail('should throw an error'); + } catch (e) { + expect(e.name).to.equal('RPCError'); + expect(e.message).contains('Invalid address'); + } + }); +}); diff --git a/test/functional/rpcServer/commands/getBlockHash.spec.js b/test/functional/rpcServer/commands/getBlockHash.spec.js new file mode 100644 index 000000000..1ef1645c2 --- /dev/null +++ b/test/functional/rpcServer/commands/getBlockHash.spec.js @@ -0,0 +1,56 @@ +const { + startDapi, +} = require('@dashevo/dp-services-ctl'); + +describe('rpcServer', function main() { + this.timeout(200000); + + describe('getBlockHash', () => { + let removeDapi; + let dapiClient; + let blocksNumber; + + beforeEach(async () => { + const { + dapiCore, + dashCore, + remove, + } = await startDapi(); + + removeDapi = remove; + + dapiClient = dapiCore.getApi(); + const coreAPI = dashCore.getApi(); + + const { result: addressString } = await coreAPI.getNewAddress(); + + blocksNumber = 500; + + await coreAPI.generateToAddress(blocksNumber, addressString); + }); + + afterEach(async () => { + await removeDapi(); + }); + + it('should get block hash by height', async () => { + const height = blocksNumber - 10; + const hash = await dapiClient.getBlockHash(height); + + expect(hash).to.be.a('string'); + }); + + it('should return RPC error if hash not found', async () => { + const height = blocksNumber * 3; + + try { + await dapiClient.getBlockHash(height); + + expect.fail('Should throw error'); + } catch (e) { + expect(e.name).to.equal('RPCError'); + expect(e.message).contains('Block height out of range'); + } + }); + }); +}); diff --git a/test/unit/rpcServer/commands/getAddressSummary.js b/test/unit/rpcServer/commands/getAddressSummary.js index c278643ed..232ee5e39 100644 --- a/test/unit/rpcServer/commands/getAddressSummary.js +++ b/test/unit/rpcServer/commands/getAddressSummary.js @@ -3,6 +3,7 @@ const chaiAsPromised = require('chai-as-promised'); const sion = require('sinon'); const getAddressSummaryFactory = require('../../../../lib/rpcServer/commands/getAddressSummary'); const coreAPIFixture = require('../../../mocks/coreAPIFixture'); +const RPCError = require('../../../../lib/rpcServer/RPCError'); const { expect } = chai; chai.use(chaiAsPromised); @@ -46,4 +47,41 @@ describe('getAddressSummary', () => { await expect(getAddressSummary({ address: 1 })).to.be.rejectedWith('params.address should be array,string'); expect(spy.callCount).to.be.equal(0); }); + + it('should throw RPCError with code -32603', async function it() { + const message = 'Some error'; + const error = new Error(message); + + coreAPIFixture.getAddressSummary = this.sinon.stub().throws(error); + const getAddressSummary = getAddressSummaryFactory(coreAPIFixture); + + try { + await getAddressSummary({ address: 'XsLdVrfJpzt6Fc8RSUFkqYqtxkLjEv484w' }); + + expect.fail('should throw RPCError'); + } catch (e) { + expect(e).to.be.an.instanceOf(RPCError); + expect(e.code).to.equal(-32603); + expect(e.message).to.equal(message); + } + }); + + it('should throw RPCError with code -32602', async function it() { + const message = 'Some error'; + const error = new Error(message); + error.statusCode = 400; + + coreAPIFixture.getAddressSummary = this.sinon.stub().throws(error); + const getAddressSummary = getAddressSummaryFactory(coreAPIFixture); + + try { + await getAddressSummary({ address: 'XsLdVrfJpzt6Fc8RSUFkqYqtxkLjEv484w' }); + + expect.fail('should throw RPCError'); + } catch (e) { + expect(e).to.be.an.instanceOf(RPCError); + expect(e.code).to.equal(-32602); + expect(e.message).to.equal(message); + } + }); }); diff --git a/test/unit/rpcServer/commands/getMnListDiff.js b/test/unit/rpcServer/commands/getMnListDiff.js index a87851adb..b115a0895 100644 --- a/test/unit/rpcServer/commands/getMnListDiff.js +++ b/test/unit/rpcServer/commands/getMnListDiff.js @@ -7,6 +7,8 @@ const coreAPIFixture = require('../../../mocks/coreAPIFixture'); chai.use(chaiAsPromised); const { expect } = chai; let spy; +let baseBlockHash; +let blockHash; describe('getMNListDiff', () => { describe('#factory', () => { @@ -22,6 +24,10 @@ describe('getMNListDiff', () => { beforeEach(() => { spy.resetHistory(); + + + baseBlockHash = '0000000000000000000000000000000000000000000000000000000000000000'; + blockHash = '0000000000000000000000000000000000000000000000000000000000000000'; }); after(() => { @@ -32,9 +38,6 @@ describe('getMNListDiff', () => { const getMNListDiff = getMNListDiffFactory(coreAPIFixture); expect(spy.callCount).to.be.equal(0); - const baseBlockHash = '0000000000000000000000000000000000000000000000000000000000000000'; - const blockHash = '0000000000000000000000000000000000000000000000000000000000000000'; - const mnDiffList = await getMNListDiff({ baseBlockHash, blockHash }); expect(mnDiffList).to.be.an('object'); expect(mnDiffList.baseBlockHash.length).to.equal(64); diff --git a/test/unit/rpcServer/commands/getUTXO.js b/test/unit/rpcServer/commands/getUTXO.js index 231237e0a..8206aa057 100644 --- a/test/unit/rpcServer/commands/getUTXO.js +++ b/test/unit/rpcServer/commands/getUTXO.js @@ -3,6 +3,7 @@ const chaiAsPromised = require('chai-as-promised'); const sinon = require('sinon'); const getUTXOFactory = require('../../../../lib/rpcServer/commands/getUTXO.js'); const coreAPIFixture = require('../../../mocks/coreAPIFixture'); +const RPCError = require('../../../../lib/rpcServer/RPCError'); const { expect } = chai; chai.use(chaiAsPromised); @@ -72,4 +73,45 @@ describe('getUTXO', () => { await expect(getUTXO({ address: 1 })).to.be.rejectedWith('params.address should be array,string'); expect(spy.callCount).to.be.equal(0); }); + + it('should throw RPCError with code -32603', async function it() { + const message = 'Some error'; + const error = new Error(message); + + coreAPIFixture.getUTXO = this.sinon.stub().throws(error); + const getUTXO = getUTXOFactory(coreAPIFixture); + + const addressArray = ['XsLdVrfJpzt6Fc8RSUFkqYqtxkLjEv484w']; + + try { + await getUTXO({ address: addressArray, from: 1, to: 1001 }); + + expect.fail('should throw RPCError'); + } catch (e) { + expect(e).to.be.an.instanceOf(RPCError); + expect(e.code).to.equal(-32603); + expect(e.message).to.equal(message); + } + }); + + it('should throw RPCError with code -32602', async function it() { + const message = 'Some error'; + const error = new Error(message); + error.statusCode = 400; + + coreAPIFixture.getUTXO = this.sinon.stub().throws(error); + const getUTXO = getUTXOFactory(coreAPIFixture); + + const addressArray = ['XsLdVrfJpzt6Fc8RSUFkqYqtxkLjEv484w']; + + try { + await getUTXO({ address: addressArray, from: 1, to: 1001 }); + + expect.fail('should throw RPCError'); + } catch (e) { + expect(e).to.be.an.instanceOf(RPCError); + expect(e.code).to.equal(-32602); + expect(e.message).to.equal(message); + } + }); });