diff --git a/src/controllers/json-rpc/bch/index.js b/src/controllers/json-rpc/bch/index.js index 8f0dc17..ff58215 100644 --- a/src/controllers/json-rpc/bch/index.js +++ b/src/controllers/json-rpc/bch/index.js @@ -68,6 +68,10 @@ class BCHRPC { case 'transaction': await this.rateLimit.limiter(rpcData.from) return await this.transaction(rpcData) + + case 'pubkey': + await this.rateLimit.limiter(rpcData.from) + return await this.pubKey(rpcData) } } catch (err) { console.error('Error in BCHRPC/rpcRouter()') @@ -532,6 +536,78 @@ class BCHRPC { } } } + + /** + * @api {JSON} /bch PubKey + * @apiPermission public + * @apiName PubKey + * @apiGroup JSON BCH + * @apiDescription Get the public key from an address. + * Given an address the endpoint will return an object with the + * following properties + * + * - jsonrpc: "" - jsonrpc version + * - id: "" - jsonrpc id + * - result: {} - Result of the petition with the RPC information + * - success: - Request status + * - publickey: - Address public key + * + * @apiExample Example usage: + * {"jsonrpc":"2.0","id":"555","method":"bch","params":{ "endpoint": "pubkey", "address": "bitcoincash:qpnty9t0w93fez04h7yzevujpv8pun204qv6yfuahk"}} + * + * @apiSuccessExample {json} Success-Response: + * { + * "jsonrpc":"2.0", + * "id":"555", + * "result":{ + * "method":"bch", + * "reciever":"QmU86vLVbUY1UhziKB6rak7GPKRA2QHWvzNm2AjEvXNsT6", + * "value":{ + * "success": true, + * "status": 200, + * "endpoint": "pubkey", + * "pubkey": { + * "success": true, + * "publicKey": "033f267fec0f7eb2b27f8c2e3052b3d03b09d36b47de4082ffb638ffb334ef0eee" + * } + * + * } + */ + + async pubKey (rpcData) { + try { + // console.log('createUser rpcData: ', rpcData) + + const address = rpcData.payload.params.address + + const data = await this.bchjs.encryption.getPubKey(address) + // console.log(`data: ${JSON.stringify(data, null, 2)}`) + + const retObj = { + success: true, + status: 200, + endpoint: 'pubkey', + pubkey: data + } + // retObj.status = 200 + + return retObj + } catch (err) { + console.error('Error in JSON RPC BCH pubKey()') + // throw err + let error = err + if (err.error && typeof err.error === 'string') { + error = new Error(err.error) + } + // Return an error response + return { + success: false, + status: 422, + message: error.message, + endpoint: 'pubkey' + } + } + } } module.exports = BCHRPC diff --git a/src/controllers/rest-api/bch/controller.js b/src/controllers/rest-api/bch/controller.js index 43363e2..d5cb0f0 100644 --- a/src/controllers/rest-api/bch/controller.js +++ b/src/controllers/rest-api/bch/controller.js @@ -94,7 +94,7 @@ class BCHRESTController { * @api {post} /bch/balance Balance * @apiName Balance * @apiGroup REST BCH - * @apiDescription Devuelve el balance de un address o un array de adresses. + * @apiDescription Returns the balance of an address or an array of addresses. * * Given the 'addresses' property returns an array of objects * with the following properties @@ -378,6 +378,56 @@ class BCHRESTController { } } + /** + * @api {post} /bch/pubkey PubKey + * @apiName PubKey + * @apiGroup REST BCH + * @apiDescription Get the public key from an address + * + * Given the 'address' param returns an object + * with the following properties + * + * - success : - Petition status + * - publicKey : '' - Public key of the provided address + * + * + * @apiExample Example usage: + * curl -H "Content-Type: application/json" -X GET localhost:5001/bch/pubkey/bitcoincash:qpnty9t0w93fez04h7yzevujpv8pun204qv6yfuahk + * + * @apiSuccessExample {json} Success-Response: + * HTTP/1.1 200 OK + * { + * "success":true, + * "publicKey": '033f267fec0f7eb2b27f8c2e3052b3d03b09d36b47de4082ffb638ffb334ef0eee' + * } + * + * @apiError UnprocessableEntity Missing required parameters + * + * @apiErrorExample {json} Error-Response: + * HTTP/1.1 422 Unprocessable Entity + * { + * "status": 422, + * "error": "Unprocessable Entity" + * } + */ + async pubKey (ctx) { + try { + const address = ctx.params.address + + const pubkey = await _this.bchjs.encryption.getPubKey(address) + // console.log(`pubkey: ${JSON.stringify(pubkey, null, 2)}`) + + ctx.body = pubkey + } catch (err) { + console.log(err) + let error = err + if (err.error && typeof err.error === 'string') { + error = new Error(err.error) + } + _this.handleError(ctx, error) + } + } + // DRY error handler handleError (ctx, err) { // If an HTTP status is specified by the buisiness logic, use that. diff --git a/src/controllers/rest-api/bch/index.js b/src/controllers/rest-api/bch/index.js index c0f42e1..e6c0786 100644 --- a/src/controllers/rest-api/bch/index.js +++ b/src/controllers/rest-api/bch/index.js @@ -50,6 +50,7 @@ class BCHRouter { this.router.post('/utxos', this.bchRESTController.utxos) this.router.post('/broadcast', this.bchRESTController.broadcast) this.router.post('/transaction', this.bchRESTController.transaction) + this.router.get('/pubkey/:address', this.bchRESTController.pubKey) // Attach the Controller routes to the Koa app. app.use(this.router.routes()) diff --git a/test/unit/controllers/json-rpc/bch.json-rpc.controller.unit.js b/test/unit/controllers/json-rpc/bch.json-rpc.controller.unit.js index 955b6aa..b7e44da 100644 --- a/test/unit/controllers/json-rpc/bch.json-rpc.controller.unit.js +++ b/test/unit/controllers/json-rpc/bch.json-rpc.controller.unit.js @@ -155,6 +155,24 @@ describe('#BCHRPC', () => { assert.equal(result, true) }) + it('should route to the pubkey method', async () => { + // Mock dependencies + sandbox.stub(uut, 'pubKey').resolves(true) + + // Generate the parsed data that the main router would pass to this + // endpoint. + const id = uid() + const rpcCall = jsonrpc.request(id, 'bch', { + endpoint: 'pubkey' + }) + const jsonStr = JSON.stringify(rpcCall, null, 2) + const rpcData = jsonrpc.parse(jsonStr) + rpcData.from = 'Origin request' + + const result = await uut.bchRouter(rpcData) + + assert.equal(result, true) + }) it('should return 500 status on routing issue', async () => { // Mock dependencies @@ -420,4 +438,81 @@ describe('#BCHRPC', () => { assert.equal(response.endpoint, 'transaction') }) }) + + describe('#pubKey', () => { + it('should return data from bchjs', async () => { + // Mock dependencies + const mock = { success: true, publicKey: '033f267fec0f7eb2b27f8c2e3052b3d03b09d36b47de4082ffb638ffb334ef0eee' } + sandbox.stub(uut.bchjs.encryption, 'getPubKey').resolves(mock) + + // Generate the parsed data that the main router would pass to this + // endpoint. + const id = uid() + const rpcCall = jsonrpc.request(id, 'bch', { + endpoint: 'pubkey', + address: 'bitcoincash:qpnty9t0w93fez04h7yzevujpv8pun204qv6yfuahk' + }) + const jsonStr = JSON.stringify(rpcCall, null, 2) + const rpcData = jsonrpc.parse(jsonStr) + + const response = await uut.pubKey(rpcData) + + assert.equal(response.success, true) + assert.equal(response.status, 200) + + assert.property(response, 'pubkey') + + const pubKey = response.pubkey + assert.property(pubKey, 'publicKey') + assert.equal(pubKey.publicKey, mock.publicKey) + }) + it('should throw an error if public key is not found', async () => { + // Force an error + sandbox + .stub(uut.bchjs.encryption, 'getPubKey') + .rejects({ success: false, error: 'No transaction history.' }) + + // Generate the parsed data that the main router would pass to this + // endpoint. + const id = uid() + const rpcCall = jsonrpc.request(id, 'bch', { + endpoint: 'pubkey', + address: 'bitcoincash:qrnx7l2e6yejgswehf54gs30ljzumnhqdqgn8yscr2' + }) + const jsonStr = JSON.stringify(rpcCall, null, 2) + const rpcData = jsonrpc.parse(jsonStr) + + const response = await uut.pubKey(rpcData) + // console.log('response: ', response) + + assert.equal(response.success, false) + assert.equal(response.status, 422) + assert.equal(response.message, 'No transaction history.') + assert.equal(response.endpoint, 'pubkey') + }) + it('should return an error for invalid input', async () => { + // Force an error + sandbox + .stub(uut.bchjs.encryption, 'getPubKey') + .rejects(new Error('Invalid data')) + + // Generate the parsed data that the main router would pass to this + // endpoint. + const id = uid() + const rpcCall = jsonrpc.request(id, 'bch', { + endpoint: 'pubkey', + address: 'bitcoincash' + }) + const jsonStr = JSON.stringify(rpcCall, null, 2) + const rpcData = jsonrpc.parse(jsonStr) + + const response = await uut.pubKey(rpcData) + // console.log('response: ', response) + + assert.equal(response.success, false) + assert.equal(response.status, 422) + assert.equal(response.message, 'Invalid data') + assert.equal(response.endpoint, 'pubkey') + }) + }) }) diff --git a/test/unit/controllers/rest-api/bch/bch.rest.controller.unit.js b/test/unit/controllers/rest-api/bch/bch.rest.controller.unit.js index 98af1f4..cf55049 100644 --- a/test/unit/controllers/rest-api/bch/bch.rest.controller.unit.js +++ b/test/unit/controllers/rest-api/bch/bch.rest.controller.unit.js @@ -233,6 +233,56 @@ describe('#BCH-REST-Router', () => { }) }) + describe('#pubKey', () => { + it('should return data from bchjs', async () => { + // Mock dependencies + const mock = { success: true, publicKey: '033f267fec0f7eb2b27f8c2e3052b3d03b09d36b47de4082ffb638ffb334ef0eee' } + sandbox.stub(uut.bchjs.encryption, 'getPubKey').resolves(mock) + + ctx.params = { + address: 'bitcoincash:qpnty9t0w93fez04h7yzevujpv8pun204qv6yfuahk' + } + + await uut.pubKey(ctx) + // console.log('ctx.body: ', ctx.body) + + assert.equal(ctx.body.success, true) + assert.equal(ctx.body.publicKey, mock.publicKey) + }) + it('should throw an error if public key is not found', async () => { + try { + // Force an error + sandbox + .stub(uut.bchjs.encryption, 'getPubKey') + .rejects({ success: false, error: 'No transaction history.' }) + + ctx.params = { + address: 'bitcoincash:qrnx7l2e6yejgswehf54gs30ljzumnhqdqgn8yscr2' + } + await uut.pubKey(ctx) + } catch (err) { + // console.log('err: ', err) + assert.include(err.message, 'No transaction history') + } + }) + it('should catch and throw an error', async () => { + try { + // Force an error + sandbox + .stub(uut.bchjs.encryption, 'getPubKey') + .rejects(new Error('test error')) + + ctx.params = { + address: 'bitcoincash:qpnty9t0w93fez04h7yzevujpv8pun204qv6yfuahk' + } + await uut.pubKey(ctx) + } catch (err) { + // console.log('err: ', err) + assert.include(err.message, 'test error') + } + }) + }) + describe('#handleError', () => { it('should pass an error message', () => { try { diff --git a/test/unit/mocks/adapters/index.js b/test/unit/mocks/adapters/index.js index b8c0232..e10114c 100644 --- a/test/unit/mocks/adapters/index.js +++ b/test/unit/mocks/adapters/index.js @@ -66,6 +66,9 @@ const bchjs = { }, RawTransactions: { sendRawTransaction: () => {} + }, + encryption: { + getPubKey: () => {} } }