From e8c08beeb33f5e5ee4f97b82aaedfcfbc7279a7f Mon Sep 17 00:00:00 2001 From: Chris Troutner Date: Tue, 10 May 2022 13:06:03 -0700 Subject: [PATCH] feat(utxoIsValid): Adding endpoint support for utxoIsValid --- src/adapters/bch/index.js | 42 ++++++++++++++++ src/controllers/rest-api/bch/controller.js | 50 +++++++++++++++++++ src/controllers/rest-api/bch/index.js | 6 +++ test/unit/adapters/bch.adapter.unit.js | 30 +++++++++++ .../rest-api/bch/bch.rest.controller.unit.js | 36 +++++++++++++ test/unit/mocks/adapters/index.js | 8 +++ 6 files changed, 172 insertions(+) diff --git a/src/adapters/bch/index.js b/src/adapters/bch/index.js index 5300276..83f7bce 100644 --- a/src/adapters/bch/index.js +++ b/src/adapters/bch/index.js @@ -387,6 +387,48 @@ class BchAdapter { } } + async utxoIsValid (utxo) { + try { + // Throw an error if this IPFS node has not yet made a connection to a + // wallet service provider. + const selectedProvider = + this.ipfs.ipfsCoordAdapter.state.selectedServiceProvider + if (!selectedProvider) { + throw new Error('No BCH Wallet Service provider available yet.') + } + + const rpcData = { + endpoint: 'utxoIsValid', + utxo + } + + // Generate a UUID for the call. + const rpcId = this.uid() + + // Generate a JSON RPC command. + const cmd = this.jsonrpc.request(rpcId, 'bch', rpcData) + const cmdStr = JSON.stringify(cmd) + console.log('cmdStr: ', cmdStr) + + // Send the RPC command to selected wallet service. + const thisNode = this.ipfs.ipfsCoordAdapter.ipfsCoord.thisNode + await this.ipfs.ipfsCoordAdapter.ipfsCoord.useCases.peer.sendPrivateMessage( + selectedProvider, + cmdStr, + thisNode + ) + + // Wait for data to come back from the wallet service. + const data = await this.waitForRPCResponse(rpcId) + + return data + } catch (err) { + console.log('utxoIsValid() error: ', err) + wlogger.error('Error in adapters/bch.js/utxoIsValid()') + throw err + } + } + // Returns a promise that resolves to data when the RPC response is recieved. async waitForRPCResponse (rpcId) { try { diff --git a/src/controllers/rest-api/bch/controller.js b/src/controllers/rest-api/bch/controller.js index def217f..27d1e55 100644 --- a/src/controllers/rest-api/bch/controller.js +++ b/src/controllers/rest-api/bch/controller.js @@ -489,6 +489,8 @@ class BchRESTControllerLib { * "pubkey": { * "success": true, * "publicKey": "033f267fec0f7eb2b27f8c2e3052b3d03b09d36b47de4082ffb638ffb334ef0eee" + * } + * } * } * } */ @@ -505,6 +507,54 @@ class BchRESTControllerLib { } } + /** + * @api {REST} /bch/utxoIsValid utxoIsValid + * @apiPermission public + * @apiName utxoIsValid + * @apiGroup REST BCH + * @apiDescription Verify if UTXO is valid + * Given a UTXO object (txid and vout), a full node is queried to verify that + * the UTXO still exists in the mempool (true), or if it has been spent (false). + * + * - jsonrpc: "" - jsonrpc version + * - id: "" - jsonrpc id + * - result: {} - Result of the petition with the RPC information + * - success: - Request status + * - isValid: - Boolean: true or false + * + * @apiExample Example usage: + * {"jsonrpc":"2.0","id":"555","method":"bch","params":{ "endpoint": "utxoIsValid", "utxo": {"tx_hash": "17754221b29f189532d4fc2ae89fb467ad2dede30fdec4854eb2129b3ba90d7a", "tx_pos": 0}}} + * + * @apiSuccessExample {json} Success-Response: + * { + * "jsonrpc":"2.0", + * "id":"555", + * "result":{ + * "method":"bch", + * "reciever":"QmU86vLVbUY1UhziKB6rak7GPKRA2QHWvzNm2AjEvXNsT6", + * "value":{ + * "success": true, + * "status": 200, + * "endpoint": "utxoIsValid", + * "isValid": true + * } + * } + * + * } + */ + async utxoIsValid (ctx) { + try { + const utxo = ctx.request.body.utxo + + const data = await this.adapters.bch.utxoIsValid(utxo) + console.log(`utxoIsValid data: ${JSON.stringify(data, null, 2)}`) + + ctx.body = data + } catch (err) { + this.handleError(ctx, err) + } + } + // 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 a851f53..8afbf29 100644 --- a/src/controllers/rest-api/bch/index.js +++ b/src/controllers/rest-api/bch/index.js @@ -59,6 +59,8 @@ class BchRouter { this.router.post('/txHistory', this.postTxHistory) this.router.post('/txData', this.postTxData) this.router.post('/pubkey', this.postPubKey) + this.router.post('/utxoIsValid', this.utxoIsValid) + // this.router.post('/getTokenData', this.getTokenData) // Attach the Controller routes to the Koa app. app.use(this.router.routes()) @@ -96,6 +98,10 @@ class BchRouter { async postPubKey (ctx, next) { await _this.bchRESTController.pubKey(ctx, next) } + + async utxoIsValid (ctx, next) { + await _this.bchRESTController.utxoIsValid(ctx, next) + } } module.exports = BchRouter diff --git a/test/unit/adapters/bch.adapter.unit.js b/test/unit/adapters/bch.adapter.unit.js index 1833714..988ea0c 100644 --- a/test/unit/adapters/bch.adapter.unit.js +++ b/test/unit/adapters/bch.adapter.unit.js @@ -284,4 +284,34 @@ describe('#bch-use-case', () => { } }) }) + + describe('#utxoIsValid', () => { + it('should validate a utxo', async () => { + // Force connection to a wallet service + uut.ipfs.ipfsCoordAdapter.state = { + selectedServiceProvider: 'abc123' + } + + // Mock depenencies + sandbox.stub(uut, 'waitForRPCResponse').resolves({ key: 'value' }) + + const utxo = {a: 'b'} + + const result = await uut.utxoIsValid(utxo) + // console.log('result: ', result) + + assert.equal(result.key, 'value') + }) + + it('should catch and throw an error', async () => { + try { + await uut.utxoIsValid() + + assert.fail('Unexpected code path') + } catch (err) { + // console.log(err) + assert.equal(err.message, 'test error') + } + }) + }) }) 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 3a509e3..0ef9bf9 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 @@ -322,4 +322,40 @@ describe('#BCH-REST-Controller', () => { assert.equal(ctx.status, 200) }) }) + + describe('#utxoIsValid', () => { + it('should return 422 status on arbitrary error', async () => { + try { + // Force an error + sandbox + .stub(uut.adapters.bch, 'utxoIsValid') + .rejects(new Error('test error')) + + ctx.request.body = { + utxo: 'blah' + } + + await uut.utxoIsValid(ctx) + + assert.fail('Unexpected result') + } catch (err) { + console.log('err: ', err) + assert.equal(err.status, 422) + assert.include(err.message, 'test error') + } + }) + + it('should return 200 status on success', async () => { + sandbox.stub(uut.adapters.bch, 'utxoIsValid').resolves({ status: 200 }) + + ctx.request.body = { + utxo: 'blah' + } + + await uut.utxoIsValid(ctx) + + // Assert the expected HTTP response + assert.equal(ctx.status, 200) + }) + }) }) diff --git a/test/unit/mocks/adapters/index.js b/test/unit/mocks/adapters/index.js index 13ad0e0..f4a8ca3 100644 --- a/test/unit/mocks/adapters/index.js +++ b/test/unit/mocks/adapters/index.js @@ -93,6 +93,14 @@ class BchUseCaseMock { return {} } + async utxoIsValid() { + return {} + } + + async getTokenData() { + return {} + } + async waitForRPCResponse () { return {} }