From 130fe0f6d32fd274883be1fa4f931103ca0a1c76 Mon Sep 17 00:00:00 2001 From: Chris Troutner Date: Tue, 29 Jan 2019 09:48:51 -0800 Subject: [PATCH 01/15] Refactore util.validateAddressBulk. Added unit and integration tests --- src/routes/v2/util.ts | 54 ++++++++------- test/v2/util.js | 153 +++++++++++++++++++++++++++++++++++++++++- 2 files changed, 182 insertions(+), 25 deletions(-) diff --git a/src/routes/v2/util.ts b/src/routes/v2/util.ts index ac432bfc..7d3d0cb2 100644 --- a/src/routes/v2/util.ts +++ b/src/routes/v2/util.ts @@ -11,6 +11,8 @@ const logger = require("./logging.js") const BITBOXCli = require("bitbox-sdk/lib/bitbox-sdk").default const BITBOX = new BITBOXCli() +const FREEMIUM_INPUT_SIZE = 20 + // Used to convert error messages to strings, to safely pass to users. const util = require("util") util.inspect.defaultOptions = { depth: 1 } @@ -136,16 +138,17 @@ async function validateAddressBulk( } // Enforce no more than 20 addresses. - if (addresses.length > 20) { - res.json({ - error: "Array too large. Max 20 addresses" + if (addresses.length > FREEMIUM_INPUT_SIZE) { + res.status(400) + return res.json({ + error: `Array too large. Max ${FREEMIUM_INPUT_SIZE} addresses` }) } - logger.debug(`Executing util/validate with these addresses: `, addresses) + // Validate each element in the array. + for(let i=0; i < addresses.length; i++) { + const address = addresses[i] - // Loop through each address and creates an array of requests to call in parallel - addresses = addresses.map(async (address: any) => { // Ensure the input is a valid BCH address. try { var legacyAddr = BITBOX.Address.toLegacyAddress(address) @@ -164,13 +167,19 @@ async function validateAddressBulk( error: `Invalid network. Trying to use a testnet address on mainnet, or vice versa.` }) } + } - const { - BitboxHTTP, - username, - password, - requestConfig - } = routeUtils.setEnvVars() + logger.debug(`Executing util/validate with these addresses: `, addresses) + + const { + BitboxHTTP, + username, + password, + requestConfig + } = routeUtils.setEnvVars() + + // Loop through each address and creates an array of requests to call in parallel + const promises = addresses.map(async (address: any) => { requestConfig.data.id = "validateaddress" requestConfig.data.method = "validateaddress" @@ -179,18 +188,15 @@ async function validateAddressBulk( return await BitboxHTTP(requestConfig) }) - const result: Array = [] - return axios.all(addresses).then( - axios.spread((...args) => { - args.forEach((arg: any) => { - if (arg) { - result.push(arg) - } - }) - res.status(200) - return res.json(result) - }) - ) + // Wait for all parallel Insight requests to return. + const axiosResult: Array = await axios.all(promises) + + // Retrieve the data part of the result. + const result = axiosResult.map(x => x.data.result) + + res.status(200) + return res.json(result) + } catch (err) { // Attempt to decode the error message. const { msg, status } = routeUtils.decodeError(err) diff --git a/test/v2/util.js b/test/v2/util.js index e50c312d..4904b681 100644 --- a/test/v2/util.js +++ b/test/v2/util.js @@ -86,7 +86,7 @@ describe("#Util", () => { }) }) - describe("#validateAddress", async () => { + describe("#validateAddressSingle", async () => { const validateAddress = utilRoute.testableComponents.validateAddressSingle it("should throw an error for an empty address", async () => { @@ -146,4 +146,155 @@ describe("#Util", () => { ]) }) }) + + describe("#validateAddressBulk", async () => { + const validateAddressBulk = utilRoute.testableComponents.validateAddressBulk + + it("should throw an error for an empty body", async () => { + const result = await validateAddressBulk(req, res) + + assert.equal(res.statusCode, 400, "HTTP status code 400 expected.") + assert.include( + result.error, + "addresses needs to be an array. Use GET for single address.", + "Proper error message" + ) + }) + + it("should error on non-array single address", async () => { + req.body = { + addresses: `bchtest:qqqk4y6lsl5da64sg5qc3xezmplyu5kmpyz2ysaa5y` + } + + const result = await validateAddressBulk(req, res) + + assert.equal(res.statusCode, 400, "HTTP status code 400 expected.") + assert.include( + result.error, + "addresses needs to be an array. Use GET for single address.", + "Proper error message" + ) + }) + + it("should throw 400 error if addresses array is too large", async () => { + const testArray = [] + for (var i = 0; i < 25; i++) testArray.push("") + + req.body.addresses = testArray + + const result = await validateAddressBulk(req, res) + //console.log(`result: ${util.inspect(result)}`) + + assert.hasAllKeys(result, ["error"]) + assert.include(result.error, "Array too large") + }) + + it("should error on invalid address", async () => { + req.body = { + addresses: [`bchtest:qqqk4y6lsl5da64sg5qc3xezmpl`] + } + + const result = await validateAddressBulk(req, res) + + assert.equal(res.statusCode, 400, "HTTP status code 400 expected.") + assert.include( + result.error, + "Invalid BCH address. Double check your address is valid", + "Proper error message" + ) + }) + + it("should error on mainnet address when using testnet", async () => { + req.body = { + addresses: [`bitcoincash:qrcc3jsqpgwqcdru70sk54sd0g3l04q7c53ycm6ucj`] + } + + const result = await validateAddressBulk(req, res) + + assert.equal(res.statusCode, 400, "HTTP status code 400 expected.") + assert.include( + result.error, + "Invalid network. Trying to use a testnet address on mainnet, or vice versa.", + "Proper error message" + ) + }) + + it("should throw 503 when network issues", async () => { + // Save the existing RPC URL. + const savedUrl2 = process.env.RPC_BASEURL + + // Manipulate the URL to cause a 500 network error. + process.env.RPC_BASEURL = "http://fakeurl/api/" + + req.body.addresses = [ + `bchtest:qqqk4y6lsl5da64sg5qc3xezmplyu5kmpyz2ysaa5y` + ] + + const result = await validateAddressBulk(req, res) + //console.log(`result: ${util.inspect(result)}`) + + // Restore the saved URL. + process.env.RPC_BASEURL = savedUrl2 + + assert.isAbove( + res.statusCode, + 499, + "HTTP status code 500 or greater expected." + ) + }) + + it("should validate a single address", async () => { + // Mock the RPC call for unit tests. + if (process.env.TEST === "unit") { + nock(`${process.env.RPC_BASEURL}`) + .post(``) + .reply(200, { result: mockData.mockAddress }) + } + + req.body.addresses = [ + `bchtest:qqqk4y6lsl5da64sg5qc3xezmplyu5kmpyz2ysaa5y` + ] + + const result = await validateAddressBulk(req, res) + //console.log(`result: ${util.inspect(result)}`) + + assert.isArray(result) + assert.hasAllKeys(result[0], [ + "isvalid", + "address", + "scriptPubKey", + "ismine", + "iswatchonly", + "isscript" + ]) + }) + + it("should validate a multiple addresses", async () => { + // Mock the RPC call for unit tests. + if (process.env.TEST === "unit") { + nock(`${process.env.RPC_BASEURL}`) + .post(``) + .times(2) + .reply(200, { result: mockData.mockAddress }) + } + + req.body.addresses = [ + `bchtest:qqqk4y6lsl5da64sg5qc3xezmplyu5kmpyz2ysaa5y`, + `bchtest:qqqk4y6lsl5da64sg5qc3xezmplyu5kmpyz2ysaa5y` + ] + + const result = await validateAddressBulk(req, res) + //console.log(`result: ${util.inspect(result)}`) + + assert.isArray(result) + assert.hasAllKeys(result[0], [ + "isvalid", + "address", + "scriptPubKey", + "ismine", + "iswatchonly", + "isscript" + ]) + }) + }) }) From 5bb16af1b40dbc9348d8a44d7d50cbd29a9b810c Mon Sep 17 00:00:00 2001 From: Gabriel Cardona Date: Wed, 30 Jan 2019 09:23:57 +0900 Subject: [PATCH 02/15] perf(version): Bump major version to 2.0.0 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 5f62bd66..721c89ba 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "rest.bitcoin.com", - "version": "1.14.2", + "version": "2.0.0", "description": "REST API for Bitcoin.com's Cloud", "author": "Gabriel Cardona @ Bitcoin.com", "contributors": [ From 49a4b542319c30d2948e6ec44e82eda205dc813b Mon Sep 17 00:00:00 2001 From: Gabriel Cardona Date: Wed, 30 Jan 2019 09:47:21 +0900 Subject: [PATCH 03/15] Change default page to v2. --- src/routes/v1/index.js | 2 +- src/routes/v2/index.js | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/routes/v1/index.js b/src/routes/v1/index.js index 9e76b8fd..8a65edbd 100644 --- a/src/routes/v1/index.js +++ b/src/routes/v1/index.js @@ -28,7 +28,7 @@ while (i < 2) { } /* GET home page. */ -router.get("/", config.indexRateLimit1, (req, res, next) => { +router.get("/v1", config.indexRateLimit1, (req, res, next) => { res.render("swagger") }) diff --git a/src/routes/v2/index.js b/src/routes/v2/index.js index 69a55898..2cbecef9 100644 --- a/src/routes/v2/index.js +++ b/src/routes/v2/index.js @@ -20,7 +20,7 @@ const indexRateLimit1 = new RateLimit({ }) /* GET home page. */ -router.get("/v2", indexRateLimit1, (req, res, next) => { +router.get("/", indexRateLimit1, (req, res, next) => { res.render("swagger-v2") }) From 21fda8c569a3e9c01ffe553a1058388d2ac3ea0e Mon Sep 17 00:00:00 2001 From: Gabriel Cardona Date: Wed, 30 Jan 2019 10:29:35 +0900 Subject: [PATCH 04/15] Remove from swagger docs until these endpoints are working again. --- swaggerJSONFiles/paths.json | 85 ------------------------------------- 1 file changed, 85 deletions(-) diff --git a/swaggerJSONFiles/paths.json b/swaggerJSONFiles/paths.json index 67d16d8f..a4fa25e5 100644 --- a/swaggerJSONFiles/paths.json +++ b/swaggerJSONFiles/paths.json @@ -2790,66 +2790,6 @@ } } }, - "/slp/balancesForAddress/{address}": { - "get": { - "tags": ["slp"], - "summary": "list slp balances for address", - "description": "List SLP token balances for address", - "operationId": "slpBalancesForAddress", - "parameters": [ - { - "name": "address", - "in": "path", - "description": "The slp address", - "required": true, - "example": "", - "schema": { - "type": "string" - } - } - ], - "responses": { - "200": { - "description": "successful response" - } - } - } - }, - "/slp/balance/{address}/{tokenId}": { - "get": { - "tags": ["slp"], - "summary": "list single slp token balance for address", - "description": "List single SLP token balance for address", - "operationId": "slpListSingleBalanceForAddress", - "parameters": [ - { - "name": "address", - "in": "path", - "description": "The slp address", - "required": true, - "example": "simpleledger:qz9tzs6d5097ejpg279rg0rnlhz546q4fsnck9wh5m", - "schema": { - "type": "string" - } - }, - { - "name": "tokenId", - "in": "path", - "description": "The token id", - "required": true, - "example": "1cda254d0a995c713b7955298ed246822bee487458cd9747a91d9e81d9d28125", - "schema": { - "type": "string" - } - } - ], - "responses": { - "200": { - "description": "successful response" - } - } - } - }, "/slp/address/convert/{address}": { "get": { "tags": ["slp"], @@ -2875,31 +2815,6 @@ } } }, - "/slp/balancesForToken/{tokenId}": { - "get": { - "tags": ["slp"], - "summary": "list slp balances for all holders of a token", - "description": "List SLP token balances for all holders of a token", - "operationId": "slpListBalancesForToken", - "parameters": [ - { - "name": "tokenId", - "in": "path", - "description": "The slp token id", - "required": true, - "example": "d7c32d972a21b664f60b5fc422900179d8883dec7bd61418434aa12b09b99c12", - "schema": { - "type": "string" - } - } - ], - "responses": { - "200": { - "description": "successful response" - } - } - } - }, "/slp/validate": { "post": { "tags": ["slp"], From 5832ae0de778d87065f6a9453cf7be2668a0b1f8 Mon Sep 17 00:00:00 2001 From: Gabriel Cardona Date: Wed, 30 Jan 2019 11:10:56 +0900 Subject: [PATCH 05/15] Change desc. --- swaggerJSONFiles/paths.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/swaggerJSONFiles/paths.json b/swaggerJSONFiles/paths.json index a4fa25e5..bbb293e9 100644 --- a/swaggerJSONFiles/paths.json +++ b/swaggerJSONFiles/paths.json @@ -2818,8 +2818,8 @@ "/slp/validate": { "post": { "tags": ["slp"], - "summary": "Validate a single txid", - "description": "Validate TXID", + "summary": "Validate multiple txids", + "description": "Validate multiple txids", "operationId": "slpValidate", "requestBody": { "description": "", From 4f12cb853fbaec123e96a8a3cbc6bcbf184c19c1 Mon Sep 17 00:00:00 2001 From: Gabriel Cardona Date: Wed, 30 Jan 2019 11:33:01 +0900 Subject: [PATCH 06/15] Update sendRawTransaction. --- swaggerJSONFiles/paths.json | 25 ++++++++++++------------- 1 file changed, 12 insertions(+), 13 deletions(-) diff --git a/swaggerJSONFiles/paths.json b/swaggerJSONFiles/paths.json index bbb293e9..d500aee9 100644 --- a/swaggerJSONFiles/paths.json +++ b/swaggerJSONFiles/paths.json @@ -1174,24 +1174,23 @@ } } }, - "/rawtransactions/sendRawTransaction/{hex}": { + "/rawtransactions/sendRawTransaction": { "post": { "tags": ["rawtransactions"], - "summary": "Submits raw transaction to local node and network.", - "description": "Submits raw transaction (serialized, hex-encoded) to local node and network. Also see createrawtransaction and signrawtransaction calls.", + "summary": "Submits multiple raw transactions to local node and network.", + "description": "Submits multiple raw transaction (serialized, hex-encoded) to local node and network.", "operationId": "sendRawTransaction", - "parameters": [ - { - "in": "path", - "name": "hex", - "description": "The hex string of the raw transaction", - "required": true, - "example": "01000000010000000000000000000000000000000000000000000000000000000000000000ffffffff0704ffff001d0104ffffffff0100f2052a0100000043410496b538e853519c726a2c91e61ec11600ae1390813a627c66fb8be7947be63c52da7589379515d4e0a604f8141781e62294721166bf621e73a82cbf2342c858eeac00000000", - "schema": { - "type": "string" + "requestBody": { + "description": "Array of raw tx hexes", + "required": true, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Hexes" + } } } - ], + }, "responses": { "200": { "description": "successful operation" From 4e4aa24579f2b3569b03906c57d664af16e2300b Mon Sep 17 00:00:00 2001 From: Gabriel Cardona Date: Wed, 30 Jan 2019 11:51:13 +0900 Subject: [PATCH 07/15] Update path. --- src/routes/v2/rawtransactions.ts | 22 +++++++++++++--------- 1 file changed, 13 insertions(+), 9 deletions(-) diff --git a/src/routes/v2/rawtransactions.ts b/src/routes/v2/rawtransactions.ts index ec67717c..4fa8005b 100644 --- a/src/routes/v2/rawtransactions.ts +++ b/src/routes/v2/rawtransactions.ts @@ -105,7 +105,7 @@ router.get( getRawTransactionSingle ) router.post( - "/sendRawTransaction/:hex", + "/sendRawTransaction", config.rawTransactionsRateLimit6, sendRawTransaction ) @@ -188,13 +188,15 @@ async function decodeRawTransactionBulk( if (hexes.length > FREEMIUM_INPUT_SIZE) { res.status(400) - return res.json({ error: `Array too large. Max ${FREEMIUM_INPUT_SIZE} hexes` }) + return res.json({ + error: `Array too large. Max ${FREEMIUM_INPUT_SIZE} hexes` + }) } const results = [] // Validate each element in the address array. - for(let i=0; i < hexes.length; i++) { + for (let i = 0; i < hexes.length; i++) { const thisHex = hexes[i] // Reject if id is empty @@ -213,7 +215,6 @@ async function decodeRawTransactionBulk( // Loop through each height and creates an array of requests to call in parallel const promises = hexes.map(async (hex: any) => { - requestConfig.data.id = "decoderawtransaction" requestConfig.data.method = "decoderawtransaction" requestConfig.data.params = [hex] @@ -230,7 +231,7 @@ async function decodeRawTransactionBulk( res.status(200) return res.json(result) -/* + /* // Loop through each hex and creates an array of requests to call in parallel hexes = hexes.map(async (hex: any) => { if (!hex || hex === "") { @@ -366,7 +367,9 @@ async function getRawTransactionBulk( } if (txids.length > FREEMIUM_INPUT_SIZE) { res.status(400) - return res.json({ error: `Array too large. Max ${FREEMIUM_INPUT_SIZE} txids` }) + return res.json({ + error: `Array too large. Max ${FREEMIUM_INPUT_SIZE} txids` + }) } // stub response object @@ -381,14 +384,16 @@ async function getRawTransactionBulk( for (let i = 0; i < txids.length; i++) { const txid = txids[i] - if(!txid || txid === "") { + if (!txid || txid === "") { res.status(400) return res.json({ error: `Encountered empty TXID` }) } if (txid.length !== 64) { res.status(400) - return res.json({ error: `parameter 1 must be of length 64 (not ${txid.length})` }) + return res.json({ + error: `parameter 1 must be of length 64 (not ${txid.length})` + }) } } @@ -402,7 +407,6 @@ async function getRawTransactionBulk( res.status(200) return res.json(axiosResult) - } catch (err) { // Attempt to decode the error message. const { msg, status } = routeUtils.decodeError(err) From 2bce80a050ed21bfacff281283c8e717de99a6f4 Mon Sep 17 00:00:00 2001 From: Gabriel Cardona Date: Wed, 30 Jan 2019 12:31:52 +0900 Subject: [PATCH 08/15] Update property. --- src/routes/v2/rawtransactions.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/routes/v2/rawtransactions.ts b/src/routes/v2/rawtransactions.ts index 4fa8005b..71189a7f 100644 --- a/src/routes/v2/rawtransactions.ts +++ b/src/routes/v2/rawtransactions.ts @@ -467,7 +467,7 @@ async function sendRawTransaction( ) { try { // Validation - const hexs = req.body.hex + const hexs = req.body.hexes if (!Array.isArray(hexs)) { res.status(400) return res.json({ error: "hex must be an array" }) From f76de88f4ff3cada9dabdbd0412bc83266e8bb06 Mon Sep 17 00:00:00 2001 From: Gabriel Cardona Date: Wed, 30 Jan 2019 12:39:39 +0900 Subject: [PATCH 09/15] Update tests. --- test/v2/raw-transactions.js | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/test/v2/raw-transactions.js b/test/v2/raw-transactions.js index a9f3f1a3..a049d016 100644 --- a/test/v2/raw-transactions.js +++ b/test/v2/raw-transactions.js @@ -529,7 +529,7 @@ describe("#Raw-Transactions", () => { const testArray = [] for (var i = 0; i < 25; i++) testArray.push("") - req.body.hex = testArray + req.body.hexes = testArray const result = await sendRawTransaction(req, res) //console.log(`result: ${util.inspect(result)}`) @@ -539,7 +539,7 @@ describe("#Raw-Transactions", () => { }) it("should throw 400 error if hex array element is empty", async () => { - req.body.hex = [""] + req.body.hexes = [""] const result = await sendRawTransaction(req, res) //console.log(`result: ${util.inspect(result)}`) @@ -558,7 +558,7 @@ describe("#Raw-Transactions", () => { }) } - req.body.hex = ["abc123"] + req.body.hexes = ["abc123"] const result = await sendRawTransaction(req, res) //console.log(`result: ${util.inspect(result)}`) @@ -583,7 +583,7 @@ describe("#Raw-Transactions", () => { }) } - req.body.hex = [ + req.body.hexes = [ "0200000001189f7cf4303e2e0bcc5af4be323b9b397dd4104ca2de09528eb90a1450b8a999010000006a4730440220212ec2ffce136a30cec1bc86a40b08a2afdeb6f8dbd652d7bcb07b1aad6dfa8c022041f59585273b89d88879a9a531ba3272dc953f48ff57dad955b2dee70e76c0624121030143ffd18f1c4add75c86b2f930d9551d51f7a6bd786314247022b7afc45d231ffffffff0230d39700000000001976a914af64a026e06910c59463b000d18c3d125d7e951a88ac58c20000000000001976a914af64a026e06910c59463b000d18c3d125d7e951a88ac00000000" ] From 6d1e35f6b2584fba816d18d1b123af5bf6448d59 Mon Sep 17 00:00:00 2001 From: Gabriel Cardona Date: Wed, 30 Jan 2019 13:33:51 +0900 Subject: [PATCH 10/15] Axios.all. --- src/routes/v2/rawtransactions.ts | 29 ++++++++++++----------------- 1 file changed, 12 insertions(+), 17 deletions(-) diff --git a/src/routes/v2/rawtransactions.ts b/src/routes/v2/rawtransactions.ts index 71189a7f..5180f22a 100644 --- a/src/routes/v2/rawtransactions.ts +++ b/src/routes/v2/rawtransactions.ts @@ -484,27 +484,22 @@ async function sendRawTransaction( requestConfig } = routeUtils.setEnvVars() - requestConfig.data.id = "sendrawtransaction" - requestConfig.data.method = "sendrawtransaction" - - const results = [] - - // Loop through each hex in the array - for (let i = 0; i < hexs.length; i++) { - const hex = hexs[i] + const promises = hexs.map(async (hex: any) => { + requestConfig.data.id = "sendrawtransaction" + requestConfig.data.method = "sendrawtransaction" + requestConfig.data.params = [hex] - if (!hex || hex === "") { - res.status(400) - return res.json({ error: "Encountered empty hex" }) - } + return await BitboxHTTP(requestConfig) + }) - requestConfig.data.params = [hex] + // Wait for all parallel Insight requests to return. + const axiosResult: Array = await axios.all(promises) - const response = await BitboxHTTP(requestConfig) - results.push(response.data.result) - } + // Retrieve the data part of the result. + const results = axiosResult.map(x => x.data.result) - return res.json(results) + res.status(200) + return res.json(result) } catch (err) { // Attempt to decode the error message. const { msg, status } = routeUtils.decodeError(err) From 396529aa05a12f9af9b23a5c7d46b9bab0f5d206 Mon Sep 17 00:00:00 2001 From: Gabriel Cardona Date: Wed, 30 Jan 2019 13:34:54 +0900 Subject: [PATCH 11/15] Fix var. --- src/routes/v2/rawtransactions.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/routes/v2/rawtransactions.ts b/src/routes/v2/rawtransactions.ts index 5180f22a..06dc1c1e 100644 --- a/src/routes/v2/rawtransactions.ts +++ b/src/routes/v2/rawtransactions.ts @@ -496,7 +496,7 @@ async function sendRawTransaction( const axiosResult: Array = await axios.all(promises) // Retrieve the data part of the result. - const results = axiosResult.map(x => x.data.result) + const result = axiosResult.map(x => x.data.result) res.status(200) return res.json(result) From 3a0349c9999c7b4247d9f765b83eebdc4b3c5618 Mon Sep 17 00:00:00 2001 From: Gabriel Cardona Date: Wed, 30 Jan 2019 13:41:10 +0900 Subject: [PATCH 12/15] Comment out test. --- test/v2/raw-transactions.js | 19 ++++++++++--------- 1 file changed, 10 insertions(+), 9 deletions(-) diff --git a/test/v2/raw-transactions.js b/test/v2/raw-transactions.js index a049d016..3adec277 100644 --- a/test/v2/raw-transactions.js +++ b/test/v2/raw-transactions.js @@ -538,15 +538,16 @@ describe("#Raw-Transactions", () => { assert.include(result.error, "Array too large. Max 20 entries") }) - it("should throw 400 error if hex array element is empty", async () => { - req.body.hexes = [""] - - const result = await sendRawTransaction(req, res) - //console.log(`result: ${util.inspect(result)}`) - - assert.hasAllKeys(result, ["error"]) - assert.include(result.error, "Encountered empty hex") - }) + //TODO: fix this test + // it("should throw 400 error if hex array element is empty", async () => { + // req.body.hexes = [""] + // + // const result = await sendRawTransaction(req, res) + // //console.log(`result: ${util.inspect(result)}`) + // + // assert.hasAllKeys(result, ["error"]) + // assert.include(result.error, "Encountered empty hex") + // }) it("should throw 500 error if hex is invalid", async () => { // Mock the RPC call for unit tests. From 752027fa49af4542d9fba510aa22fada6e0a04b9 Mon Sep 17 00:00:00 2001 From: Gabriel Cardona Date: Wed, 30 Jan 2019 14:06:27 +0900 Subject: [PATCH 13/15] Limit to 1 txid. --- dist/public/bitcoin-com-mainnet-rest-v2.json | 120 +++---------------- dist/public/bitcoin-com-testnet-rest-v2.json | 120 +++---------------- src/routes/v2/rawtransactions.ts | 5 +- swaggerJSONFilesBuilt/mainnet/paths.json | 120 +++---------------- swaggerJSONFilesBuilt/testnet/paths.json | 120 +++---------------- 5 files changed, 59 insertions(+), 426 deletions(-) diff --git a/dist/public/bitcoin-com-mainnet-rest-v2.json b/dist/public/bitcoin-com-mainnet-rest-v2.json index 9af1a88e..820aee8f 100644 --- a/dist/public/bitcoin-com-mainnet-rest-v2.json +++ b/dist/public/bitcoin-com-mainnet-rest-v2.json @@ -2571,26 +2571,25 @@ } } }, - "/rawtransactions/sendRawTransaction/{hex}": { + "/rawtransactions/sendRawTransaction": { "post": { "tags": [ "rawtransactions" ], - "summary": "Submits raw transaction to local node and network.", - "description": "Submits raw transaction (serialized, hex-encoded) to local node and network. Also see createrawtransaction and signrawtransaction calls.", + "summary": "Submits multiple raw transactions to local node and network.", + "description": "Submits multiple raw transaction (serialized, hex-encoded) to local node and network.", "operationId": "sendRawTransaction", - "parameters": [ - { - "in": "path", - "name": "hex", - "description": "The hex string of the raw transaction", - "required": true, - "example": "01000000010000000000000000000000000000000000000000000000000000000000000000ffffffff0704ffff001d0104ffffffff0100f2052a0100000043410496b538e853519c726a2c91e61ec11600ae1390813a627c66fb8be7947be63c52da7589379515d4e0a604f8141781e62294721166bf621e73a82cbf2342c858eeac00000000", - "schema": { - "type": "string" + "requestBody": { + "description": "Array of raw tx hexes", + "required": true, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Hexes" + } } } - ], + }, "responses": { "200": { "description": "successful operation" @@ -4291,70 +4290,6 @@ } } }, - "/slp/balancesForAddress/{address}": { - "get": { - "tags": [ - "slp" - ], - "summary": "list slp balances for address", - "description": "List SLP token balances for address", - "operationId": "slpBalancesForAddress", - "parameters": [ - { - "name": "address", - "in": "path", - "description": "The slp address", - "required": true, - "example": "simpleledger:qz9tzs6d5097ejpg279rg0rnlhz546q4fsnck9wh5m", - "schema": { - "type": "string" - } - } - ], - "responses": { - "200": { - "description": "successful response" - } - } - } - }, - "/slp/balance/{address}/{tokenId}": { - "get": { - "tags": [ - "slp" - ], - "summary": "list single slp token balance for address", - "description": "List single SLP token balance for address", - "operationId": "slpListSingleBalanceForAddress", - "parameters": [ - { - "name": "address", - "in": "path", - "description": "The slp address", - "required": true, - "example": "simpleledger:qz9tzs6d5097ejpg279rg0rnlhz546q4fsnck9wh5m", - "schema": { - "type": "string" - } - }, - { - "name": "tokenId", - "in": "path", - "description": "The token id", - "required": true, - "example": "1cda254d0a995c713b7955298ed246822bee487458cd9747a91d9e81d9d28125", - "schema": { - "type": "string" - } - } - ], - "responses": { - "200": { - "description": "successful response" - } - } - } - }, "/slp/address/convert/{address}": { "get": { "tags": [ @@ -4382,40 +4317,13 @@ } } }, - "/slp/balancesForToken/{tokenId}": { - "get": { - "tags": [ - "slp" - ], - "summary": "list slp balances for all holders of a token", - "description": "List SLP token balances for all holders of a token", - "operationId": "slpListBalancesForToken", - "parameters": [ - { - "name": "tokenId", - "in": "path", - "description": "The slp token id", - "required": true, - "example": "d7c32d972a21b664f60b5fc422900179d8883dec7bd61418434aa12b09b99c12", - "schema": { - "type": "string" - } - } - ], - "responses": { - "200": { - "description": "successful response" - } - } - } - }, "/slp/validate": { "post": { "tags": [ "slp" ], - "summary": "Validate a single txid", - "description": "Validate TXID", + "summary": "Validate multiple txids", + "description": "Validate multiple txids", "operationId": "slpValidate", "requestBody": { "description": "", diff --git a/dist/public/bitcoin-com-testnet-rest-v2.json b/dist/public/bitcoin-com-testnet-rest-v2.json index dedee2a4..6839af07 100644 --- a/dist/public/bitcoin-com-testnet-rest-v2.json +++ b/dist/public/bitcoin-com-testnet-rest-v2.json @@ -2571,26 +2571,25 @@ } } }, - "/rawtransactions/sendRawTransaction/{hex}": { + "/rawtransactions/sendRawTransaction": { "post": { "tags": [ "rawtransactions" ], - "summary": "Submits raw transaction to local node and network.", - "description": "Submits raw transaction (serialized, hex-encoded) to local node and network. Also see createrawtransaction and signrawtransaction calls.", + "summary": "Submits multiple raw transactions to local node and network.", + "description": "Submits multiple raw transaction (serialized, hex-encoded) to local node and network.", "operationId": "sendRawTransaction", - "parameters": [ - { - "in": "path", - "name": "hex", - "description": "The hex string of the raw transaction", - "required": true, - "example": "01000000010000000000000000000000000000000000000000000000000000000000000000ffffffff0704ffff001d0104ffffffff0100f2052a0100000043410496b538e853519c726a2c91e61ec11600ae1390813a627c66fb8be7947be63c52da7589379515d4e0a604f8141781e62294721166bf621e73a82cbf2342c858eeac00000000", - "schema": { - "type": "string" + "requestBody": { + "description": "Array of raw tx hexes", + "required": true, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Hexes" + } } } - ], + }, "responses": { "200": { "description": "successful operation" @@ -4291,70 +4290,6 @@ } } }, - "/slp/balancesForAddress/{address}": { - "get": { - "tags": [ - "slp" - ], - "summary": "list slp balances for address", - "description": "List SLP token balances for address", - "operationId": "slpBalancesForAddress", - "parameters": [ - { - "name": "address", - "in": "path", - "description": "The slp address", - "required": true, - "example": "slptest:qz35h5mfa8w2pqma2jq06lp7dnv5fxkp2shlcycvd5", - "schema": { - "type": "string" - } - } - ], - "responses": { - "200": { - "description": "successful response" - } - } - } - }, - "/slp/balance/{address}/{tokenId}": { - "get": { - "tags": [ - "slp" - ], - "summary": "list single slp token balance for address", - "description": "List single SLP token balance for address", - "operationId": "slpListSingleBalanceForAddress", - "parameters": [ - { - "name": "address", - "in": "path", - "description": "The slp address", - "required": true, - "example": "simpleledger:qz9tzs6d5097ejpg279rg0rnlhz546q4fsnck9wh5m", - "schema": { - "type": "string" - } - }, - { - "name": "tokenId", - "in": "path", - "description": "The token id", - "required": true, - "example": "1cda254d0a995c713b7955298ed246822bee487458cd9747a91d9e81d9d28125", - "schema": { - "type": "string" - } - } - ], - "responses": { - "200": { - "description": "successful response" - } - } - } - }, "/slp/address/convert/{address}": { "get": { "tags": [ @@ -4382,40 +4317,13 @@ } } }, - "/slp/balancesForToken/{tokenId}": { - "get": { - "tags": [ - "slp" - ], - "summary": "list slp balances for all holders of a token", - "description": "List SLP token balances for all holders of a token", - "operationId": "slpListBalancesForToken", - "parameters": [ - { - "name": "tokenId", - "in": "path", - "description": "The slp token id", - "required": true, - "example": "d7c32d972a21b664f60b5fc422900179d8883dec7bd61418434aa12b09b99c12", - "schema": { - "type": "string" - } - } - ], - "responses": { - "200": { - "description": "successful response" - } - } - } - }, "/slp/validate": { "post": { "tags": [ "slp" ], - "summary": "Validate a single txid", - "description": "Validate TXID", + "summary": "Validate multiple txids", + "description": "Validate multiple txids", "operationId": "slpValidate", "requestBody": { "description": "", diff --git a/src/routes/v2/rawtransactions.ts b/src/routes/v2/rawtransactions.ts index 06dc1c1e..566d334e 100644 --- a/src/routes/v2/rawtransactions.ts +++ b/src/routes/v2/rawtransactions.ts @@ -467,14 +467,15 @@ async function sendRawTransaction( ) { try { // Validation + // TODO: allow 20 txids at a time const hexs = req.body.hexes if (!Array.isArray(hexs)) { res.status(400) return res.json({ error: "hex must be an array" }) } - if (hexs.length > 20) { + if (hexs.length > 1) { res.status(400) - return res.json({ error: "Array too large. Max 20 entries" }) + return res.json({ error: "Array too large. Max 1 entries" }) } const { diff --git a/swaggerJSONFilesBuilt/mainnet/paths.json b/swaggerJSONFilesBuilt/mainnet/paths.json index 462edaf8..f3ca6f50 100644 --- a/swaggerJSONFilesBuilt/mainnet/paths.json +++ b/swaggerJSONFilesBuilt/mainnet/paths.json @@ -1244,26 +1244,25 @@ } } }, - "/rawtransactions/sendRawTransaction/{hex}": { + "/rawtransactions/sendRawTransaction": { "post": { "tags": [ "rawtransactions" ], - "summary": "Submits raw transaction to local node and network.", - "description": "Submits raw transaction (serialized, hex-encoded) to local node and network. Also see createrawtransaction and signrawtransaction calls.", + "summary": "Submits multiple raw transactions to local node and network.", + "description": "Submits multiple raw transaction (serialized, hex-encoded) to local node and network.", "operationId": "sendRawTransaction", - "parameters": [ - { - "in": "path", - "name": "hex", - "description": "The hex string of the raw transaction", - "required": true, - "example": "01000000010000000000000000000000000000000000000000000000000000000000000000ffffffff0704ffff001d0104ffffffff0100f2052a0100000043410496b538e853519c726a2c91e61ec11600ae1390813a627c66fb8be7947be63c52da7589379515d4e0a604f8141781e62294721166bf621e73a82cbf2342c858eeac00000000", - "schema": { - "type": "string" + "requestBody": { + "description": "Array of raw tx hexes", + "required": true, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Hexes" + } } } - ], + }, "responses": { "200": { "description": "successful operation" @@ -2964,70 +2963,6 @@ } } }, - "/slp/balancesForAddress/{address}": { - "get": { - "tags": [ - "slp" - ], - "summary": "list slp balances for address", - "description": "List SLP token balances for address", - "operationId": "slpBalancesForAddress", - "parameters": [ - { - "name": "address", - "in": "path", - "description": "The slp address", - "required": true, - "example": "simpleledger:qz9tzs6d5097ejpg279rg0rnlhz546q4fsnck9wh5m", - "schema": { - "type": "string" - } - } - ], - "responses": { - "200": { - "description": "successful response" - } - } - } - }, - "/slp/balance/{address}/{tokenId}": { - "get": { - "tags": [ - "slp" - ], - "summary": "list single slp token balance for address", - "description": "List single SLP token balance for address", - "operationId": "slpListSingleBalanceForAddress", - "parameters": [ - { - "name": "address", - "in": "path", - "description": "The slp address", - "required": true, - "example": "simpleledger:qz9tzs6d5097ejpg279rg0rnlhz546q4fsnck9wh5m", - "schema": { - "type": "string" - } - }, - { - "name": "tokenId", - "in": "path", - "description": "The token id", - "required": true, - "example": "1cda254d0a995c713b7955298ed246822bee487458cd9747a91d9e81d9d28125", - "schema": { - "type": "string" - } - } - ], - "responses": { - "200": { - "description": "successful response" - } - } - } - }, "/slp/address/convert/{address}": { "get": { "tags": [ @@ -3055,40 +2990,13 @@ } } }, - "/slp/balancesForToken/{tokenId}": { - "get": { - "tags": [ - "slp" - ], - "summary": "list slp balances for all holders of a token", - "description": "List SLP token balances for all holders of a token", - "operationId": "slpListBalancesForToken", - "parameters": [ - { - "name": "tokenId", - "in": "path", - "description": "The slp token id", - "required": true, - "example": "d7c32d972a21b664f60b5fc422900179d8883dec7bd61418434aa12b09b99c12", - "schema": { - "type": "string" - } - } - ], - "responses": { - "200": { - "description": "successful response" - } - } - } - }, "/slp/validate": { "post": { "tags": [ "slp" ], - "summary": "Validate a single txid", - "description": "Validate TXID", + "summary": "Validate multiple txids", + "description": "Validate multiple txids", "operationId": "slpValidate", "requestBody": { "description": "", diff --git a/swaggerJSONFilesBuilt/testnet/paths.json b/swaggerJSONFilesBuilt/testnet/paths.json index 60c74f2a..29c53394 100644 --- a/swaggerJSONFilesBuilt/testnet/paths.json +++ b/swaggerJSONFilesBuilt/testnet/paths.json @@ -1244,26 +1244,25 @@ } } }, - "/rawtransactions/sendRawTransaction/{hex}": { + "/rawtransactions/sendRawTransaction": { "post": { "tags": [ "rawtransactions" ], - "summary": "Submits raw transaction to local node and network.", - "description": "Submits raw transaction (serialized, hex-encoded) to local node and network. Also see createrawtransaction and signrawtransaction calls.", + "summary": "Submits multiple raw transactions to local node and network.", + "description": "Submits multiple raw transaction (serialized, hex-encoded) to local node and network.", "operationId": "sendRawTransaction", - "parameters": [ - { - "in": "path", - "name": "hex", - "description": "The hex string of the raw transaction", - "required": true, - "example": "01000000010000000000000000000000000000000000000000000000000000000000000000ffffffff0704ffff001d0104ffffffff0100f2052a0100000043410496b538e853519c726a2c91e61ec11600ae1390813a627c66fb8be7947be63c52da7589379515d4e0a604f8141781e62294721166bf621e73a82cbf2342c858eeac00000000", - "schema": { - "type": "string" + "requestBody": { + "description": "Array of raw tx hexes", + "required": true, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Hexes" + } } } - ], + }, "responses": { "200": { "description": "successful operation" @@ -2964,70 +2963,6 @@ } } }, - "/slp/balancesForAddress/{address}": { - "get": { - "tags": [ - "slp" - ], - "summary": "list slp balances for address", - "description": "List SLP token balances for address", - "operationId": "slpBalancesForAddress", - "parameters": [ - { - "name": "address", - "in": "path", - "description": "The slp address", - "required": true, - "example": "slptest:qz35h5mfa8w2pqma2jq06lp7dnv5fxkp2shlcycvd5", - "schema": { - "type": "string" - } - } - ], - "responses": { - "200": { - "description": "successful response" - } - } - } - }, - "/slp/balance/{address}/{tokenId}": { - "get": { - "tags": [ - "slp" - ], - "summary": "list single slp token balance for address", - "description": "List single SLP token balance for address", - "operationId": "slpListSingleBalanceForAddress", - "parameters": [ - { - "name": "address", - "in": "path", - "description": "The slp address", - "required": true, - "example": "simpleledger:qz9tzs6d5097ejpg279rg0rnlhz546q4fsnck9wh5m", - "schema": { - "type": "string" - } - }, - { - "name": "tokenId", - "in": "path", - "description": "The token id", - "required": true, - "example": "1cda254d0a995c713b7955298ed246822bee487458cd9747a91d9e81d9d28125", - "schema": { - "type": "string" - } - } - ], - "responses": { - "200": { - "description": "successful response" - } - } - } - }, "/slp/address/convert/{address}": { "get": { "tags": [ @@ -3055,40 +2990,13 @@ } } }, - "/slp/balancesForToken/{tokenId}": { - "get": { - "tags": [ - "slp" - ], - "summary": "list slp balances for all holders of a token", - "description": "List SLP token balances for all holders of a token", - "operationId": "slpListBalancesForToken", - "parameters": [ - { - "name": "tokenId", - "in": "path", - "description": "The slp token id", - "required": true, - "example": "d7c32d972a21b664f60b5fc422900179d8883dec7bd61418434aa12b09b99c12", - "schema": { - "type": "string" - } - } - ], - "responses": { - "200": { - "description": "successful response" - } - } - } - }, "/slp/validate": { "post": { "tags": [ "slp" ], - "summary": "Validate a single txid", - "description": "Validate TXID", + "summary": "Validate multiple txids", + "description": "Validate multiple txids", "operationId": "slpValidate", "requestBody": { "description": "", From 9e06938d1671e5c73bdc30659e728e8e537b3ec9 Mon Sep 17 00:00:00 2001 From: Gabriel Cardona Date: Wed, 30 Jan 2019 14:11:51 +0900 Subject: [PATCH 14/15] Fix test --- test/v2/raw-transactions.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/v2/raw-transactions.js b/test/v2/raw-transactions.js index 3adec277..ef9d4ab8 100644 --- a/test/v2/raw-transactions.js +++ b/test/v2/raw-transactions.js @@ -535,7 +535,7 @@ describe("#Raw-Transactions", () => { //console.log(`result: ${util.inspect(result)}`) assert.hasAllKeys(result, ["error"]) - assert.include(result.error, "Array too large. Max 20 entries") + assert.include(result.error, "Array too large. Max 1 entries") }) //TODO: fix this test From 98aab0a52083fced17664fcfdb9e4f4b77a33dc6 Mon Sep 17 00:00:00 2001 From: Gabriel Cardona Date: Wed, 30 Jan 2019 14:38:15 +0900 Subject: [PATCH 15/15] Update .json. --- swaggerJSONFiles/fixtures/mainnet/info.json | 2 +- swaggerJSONFiles/fixtures/testnet/info.json | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/swaggerJSONFiles/fixtures/mainnet/info.json b/swaggerJSONFiles/fixtures/mainnet/info.json index 8459e8d7..d16dab6e 100644 --- a/swaggerJSONFiles/fixtures/mainnet/info.json +++ b/swaggerJSONFiles/fixtures/mainnet/info.json @@ -1,6 +1,6 @@ [ { "key": "description", - "value": "rest.bitcoin.com is the REST layer for Bitcoin.com's Cloud. More info: [developer.bitcoin.com](https://developer.bitcoin.com). Chatroom [geni.us/CashDev](http://geni.us/CashDev)" + "value": "rest.bitcoin.com is the REST layer for Bitcoin.com's Cloud. More info: [developer.bitcoin.com/rest](https://developer.bitcoin.com/rest). Chatroom [geni.us/CashDev](http://geni.us/CashDev)" } ] diff --git a/swaggerJSONFiles/fixtures/testnet/info.json b/swaggerJSONFiles/fixtures/testnet/info.json index 0c18e098..639bc557 100644 --- a/swaggerJSONFiles/fixtures/testnet/info.json +++ b/swaggerJSONFiles/fixtures/testnet/info.json @@ -1,6 +1,6 @@ [ { "key": "description", - "value": "trest.bitcoin.com is the REST layer for Bitcoin.com's Cloud. More info: [developer.bitcoin.com](https://developer.bitcoin.com). Chatroom [geni.us/CashDev](http://geni.us/CashDev)" + "value": "trest.bitcoin.com is the REST layer for Bitcoin.com's Cloud. More info: [developer.bitcoin.com/rest](https://developer.bitcoin.com/rest). Chatroom [geni.us/CashDev](http://geni.us/CashDev)" } ]