From ba38d3e107583a4e145923b56ed5d7b3ba835e33 Mon Sep 17 00:00:00 2001 From: Chris Troutner Date: Sun, 11 Oct 2020 17:46:27 -0700 Subject: [PATCH] fix(tokenUtxoDetails): handling dust attack transactions better --- package.json | 2 +- src/slp/utils.js | 20 +++++++++++--------- test/integration/slp.js | 24 ++++++++++++++++++++++++ test/unit/slp-utils.js | 30 ++++++++++++++++++++++++++++++ 4 files changed, 66 insertions(+), 10 deletions(-) diff --git a/package.json b/package.json index 14a87f5..e2b3f71 100644 --- a/package.json +++ b/package.json @@ -16,7 +16,7 @@ "test:integration:free": "bash test/integration/test-free-tier.sh", "test:integration:free:bchn": "bash test/integration/test-free-tier-bchn.sh", "test:integration:free:testnet": "bash test/integration/testnet/test-free-tier.sh", - "test:temp": "mocha --timeout 30000 -g '#blockHeader' test/unit/electrumx.js", + "test:temp": "mocha --timeout 30000 -g '#dustattack' test/unit/slp-utils.js", "test:integration:api": "RESTURL=https://api.fullstack.cash/v3/ mocha --timeout 30000 test/integration", "coverage": "nyc report --reporter=text-lcov | coveralls", "coverage:report": "nyc --reporter=html mocha --timeout 25000 test/unit/", diff --git a/src/slp/utils.js b/src/slp/utils.js index a18fc60..6b04e98 100644 --- a/src/slp/utils.js +++ b/src/slp/utils.js @@ -194,7 +194,7 @@ class Utils { path = `${this.restURL}slp/list` } - //console.log(`path: ${path}`) + // console.log(`path: ${path}`) try { let response @@ -316,7 +316,7 @@ class Utils { return response.data } - throw new Error(`Input address must be a string or array of strings.`) + throw new Error("Input address must be a string or array of strings.") } catch (error) { if (error.response && error.response.data) throw error.response.data throw error @@ -499,7 +499,7 @@ class Utils { typeof txid !== "string" || txid.length !== 64 ) - throw new Error(`txid must be 64 character string.`) + throw new Error("txid must be 64 character string.") const path = `${this.restURL}slp/validateTxid2/${txid}` @@ -700,7 +700,7 @@ class Utils { typeof txid !== "string" || txid.length !== 64 ) - throw new Error(`txid string must be included.`) + throw new Error("txid string must be included.") // console.log(`this.restURL: ${this.restURL}`) const path = `${this.restURL}slp/txDetails/${txid}` @@ -767,7 +767,8 @@ class Utils { // Then pass that cache object back into this function every time its called. if (cache) { if (!(cache instanceof Object)) - throw new Error(`decodeOpReturn cache parameter must be Object`) + throw new Error("decodeOpReturn cache parameter must be Object") + const cachedVal = cache[txid] if (cachedVal) return cachedVal } @@ -775,7 +776,7 @@ class Utils { try { // Validate the txid input. if (!txid || txid === "" || typeof txid !== "string") - throw new Error(`txid string must be included.`) + throw new Error("txid string must be included.") // Retrieve the transaction object from the full node. const path = `${this.restURL}rawtransactions/getRawTransaction/${txid}?verbose=true` @@ -897,7 +898,7 @@ class Utils { const decodeOpReturnCache = {} const cachedTxValidation = {} // Throw error if input is not an array. - if (!Array.isArray(utxos)) throw new Error(`Input must be an array.`) + if (!Array.isArray(utxos)) throw new Error("Input must be an array.") // Loop through each element in the array and validate the input before // further processing. @@ -963,7 +964,8 @@ class Utils { // to display the unknown state. if ( !err.message || - err.message.indexOf("scriptpubkey not op_return") === -1 + (err.message.indexOf("scriptpubkey not op_return") === -1 && + err.message.indexOf("lokad id") === -1) ) { // console.log(`error from decodeOpReturn(${utxo.txid}): `, err) @@ -1263,7 +1265,7 @@ class Utils { async hydrateUtxos(utxos) { try { // Throw error if input is not an array. - if (!Array.isArray(utxos)) throw new Error(`Input must be an array.`) + if (!Array.isArray(utxos)) throw new Error("Input must be an array.") const response = await axios.post( `${this.restURL}slp/hydrateUtxos`, diff --git a/test/integration/slp.js b/test/integration/slp.js index 119807f..99d3b29 100644 --- a/test/integration/slp.js +++ b/test/integration/slp.js @@ -437,6 +437,30 @@ describe(`#SLP`, () => { assert.equal(data[0].isValid, false) assert.equal(data[1].isValid, false) }) + + it("should handle a dust attack", async () => { + // it("#dustattack", async () => { + const utxos = [ + { + height: 655965, + tx_hash: + "a675af87dcd8d39be782737aa52e0076b52eb2f5ce355ffcb5567a64dd96b77e", + tx_pos: 151, + value: 547, + satoshis: 547, + txid: + "a675af87dcd8d39be782737aa52e0076b52eb2f5ce355ffcb5567a64dd96b77e", + vout: 151, + address: "bitcoincash:qq4dw3sm8qvglspy6w2qg0u2ugsy9zcfcqrpeflwww", + hdIndex: 11 + } + ] + + const data = await bchjs.SLP.Utils.tokenUtxoDetails(utxos) + // console.log(`data: ${JSON.stringify(data, null, 2)}`) + + assert.equal(data[0].isValid, false) + }) }) describe("#balancesForAddress", () => { diff --git a/test/unit/slp-utils.js b/test/unit/slp-utils.js index 4480e5e..f04c7ae 100644 --- a/test/unit/slp-utils.js +++ b/test/unit/slp-utils.js @@ -1735,6 +1735,36 @@ describe("#SLP Utils", () => { assert.equal(data[0].isValid, null) assert.equal(data[1].isValid, null) }) + + // it("should handle a dust attack", async () => { + it("#dustattack", async () => { + // Mock external dependencies. + // Stub the calls to decodeOpReturn. + sandbox + .stub(slp.Utils, "decodeOpReturn") + .rejects(new Error("lokad id wrong size")) + + const utxos = [ + { + height: 655965, + tx_hash: + "a675af87dcd8d39be782737aa52e0076b52eb2f5ce355ffcb5567a64dd96b77e", + tx_pos: 151, + value: 547, + satoshis: 547, + txid: + "a675af87dcd8d39be782737aa52e0076b52eb2f5ce355ffcb5567a64dd96b77e", + vout: 151, + address: "bitcoincash:qq4dw3sm8qvglspy6w2qg0u2ugsy9zcfcqrpeflwww", + hdIndex: 11 + } + ] + + const data = await slp.Utils.tokenUtxoDetails(utxos) + // console.log(`data: ${JSON.stringify(data, null, 2)}`) + + assert.equal(data[0].isValid, false) + }) }) describe("#txDetails", () => {