From 348a67a79bfd4964a2c1c5db12fd0d2943bfccda Mon Sep 17 00:00:00 2001 From: Chris Troutner Date: Sun, 6 Dec 2020 09:49:30 -0800 Subject: [PATCH] tokenUtxoDetails: prototyped different validation methods --- package.json | 3 +- src/slp/utils.js | 75 ++++++++++++++++++++++++++--- test/integration/bchn/slp.js | 92 ++++++++++++++++++++++++++++++++++++ test/unit/slp-utils.js | 8 ++-- 4 files changed, 167 insertions(+), 11 deletions(-) diff --git a/package.json b/package.json index deb3304..d0f3605 100644 --- a/package.json +++ b/package.json @@ -16,10 +16,11 @@ "test:integration:local": "RESTURL=http://localhost:3000/v4/ mocha --timeout 30000 test/integration", "test:integration:local:bchn": "RESTURL=http://localhost:3000/v4/ bash test/integration/test-bchn-integration.sh", "test:integration:local:testnet": "RESTURL=http://localhost:4000/v4/ mocha --timeout 30000 test/integration/testnet", + "test:integration:api": "RESTURL=https://api.fullstack.cash/v4/ mocha --timeout 30000 test/integration", "test:temp": "RESTURL=https://bchn.fullstack.cash/v4/ mocha --timeout 30000 -g '#validateTxid' test/integration/bchn/", "test:temp2": "RESTURL=https://abc.fullstack.cash/v4/ mocha --timeout 30000 -g '#validateTxid' test/integration/", "test:temp3": "ISBCHN=true RESTURL=http://localhost:3000/v4/ mocha --timeout 30000 -g '#validateTxid' test/integration/bchn/", - "test:integration:api": "RESTURL=https://api.fullstack.cash/v4/ mocha --timeout 30000 test/integration", + "test:temp4": "mocha -g '#SLP Utils' test/unit/", "coverage": "nyc report --reporter=text-lcov | coveralls", "coverage:report": "nyc --reporter=html mocha --timeout 25000 test/unit/", "docs": "./node_modules/.bin/apidoc -i src/ -o docs" diff --git a/src/slp/utils.js b/src/slp/utils.js index 694c27d..671821c 100644 --- a/src/slp/utils.js +++ b/src/slp/utils.js @@ -14,6 +14,7 @@ class Utils { this.apiToken = config.apiToken this.slpParser = slpParser this.authToken = config.authToken + this.axios = axios if (this.authToken) { // Add Basic Authentication token to the authorization header. @@ -32,6 +33,12 @@ class Utils { } _this = this + + this.whitelist = [] + if (process.env.TEST !== "unit") { + // Get the whitelist. + this.getWhitelist() + } } /** @@ -565,14 +572,19 @@ class Utils { * } * ] */ - async getWhitelist(txid) { + async getWhitelist() { try { const path = `${this.restURL}slp/whitelist` - const response = await axios.get(path, _this.axiosOptions) - // console.log(`response.data: ${JSON.stringify(response.data, null, 2)}`) + // Retrieve the whitelist from the REST API if we haven't gotten it yet. + if (this.whitelist.length === 0) { + const response = await axios.get(path, _this.axiosOptions) + console.log(`response.data: ${JSON.stringify(response.data, null, 2)}`) - return response.data + this.whitelist = response.data + } + + return this.whitelist } catch (error) { if (error.response && error.response.data) throw error.response.data throw error @@ -1074,6 +1086,7 @@ class Utils { } } + // Ensure the UTXO has a vout or tx_pos property. if (!Number.isInteger(utxo.vout)) { if (Number.isInteger(utxo.tx_pos)) { utxo.vout = utxo.tx_pos @@ -1262,16 +1275,64 @@ class Utils { } } - // Finally, validate the SLP txid with SLPDB. + // *After* the UTXO has been hydrated with SLP data, + // validate the TXID with SLPDB. if (outAry[i].tokenType) { - var isValid = cachedTxValidation[utxo.txid] + // Only execute this block if the current UTXO has a 'tokenType' + // property. i.e. it has been hydrated with SLP information. + + // If the value has been cached, use the cached version first. + let isValid = cachedTxValidation[utxo.txid] + + // If not in the cache, try the general SLPDB. if (isValid == null) { isValid = await this.validateTxid(utxo.txid) + isValid = isValid[0].valid + + // Save the result to the local cache. cachedTxValidation[utxo.txid] = isValid } // console.log(`isValid: ${JSON.stringify(isValid, null, 2)}`) - outAry[i].isValid = isValid[0].valid + // If still null, check the whitelist SLPDB + if (isValid === null) { + console.log(`outAry[${i}]: ${JSON.stringify(outAry[i], null, 2)}`) + + // Figure out if the token UTXO is in the whitelist. + let utxoInWhitelist = false + for (let j = 0; j < this.whitelist.length; j++) { + if (outAry[i].tokenId === this.whitelist[j].tokenId) { + utxoInWhitelist = true + break + } + } + + // If the utxo.tokenId is in the whitelist, check the validity with + // the whitelist SLPDB. This should still be functioning properly + // if the general SLPDB is not. + if (utxoInWhitelist) { + isValid = await this.validateTxid3(utxo.txid) + isValid = isValid[0].valid + + // Save the result to the local cache. + cachedTxValidation[utxo.txid] = isValid + } + } + + // If still null, as a last resort, check it against slp-validate + if (isValid === null) { + isValid = await this.validateTxid2(utxo.txid) + console.log( + `slp-validate isValid: ${JSON.stringify(isValid, null, 2)}` + ) + isValid = isValid.valid + + // Save the result to the local cache. + cachedTxValidation[utxo.txid] = isValid + } + + console.log(`isValid: ${JSON.stringify(isValid, null, 2)}`) + outAry[i].isValid = isValid } } diff --git a/test/integration/bchn/slp.js b/test/integration/bchn/slp.js index be71f78..e29cbe5 100644 --- a/test/integration/bchn/slp.js +++ b/test/integration/bchn/slp.js @@ -463,6 +463,98 @@ describe(`#SLP`, () => { assert.equal(data[0].isValid, false) }) + + it("should handle a range of UTXO types", async () => { + const utxos = [ + // Malformed SLP tx + { + note: "Malformed SLP tx", + tx_hash: + "f7e5199ef6669ad4d078093b3ad56e355b6ab84567e59ad0f08a5ad0244f783a", + tx_pos: 1, + value: 546 + }, + // Normal TX (non-SLP) + { + note: "Normal TX (non-SLP)", + tx_hash: + "01cdaec2f8b311fc2d6ecc930247bd45fa696dc204ab684596e281fe1b06c1f0", + tx_pos: 0, + value: 400000 + }, + // Valid PSF SLP tx + { + note: "Valid PSF SLP tx", + tx_hash: + "daf4d8b8045e7a90b7af81bfe2370178f687da0e545511bce1c9ae539eba5ffd", + tx_pos: 1, + value: 546 + }, + // Valid SLP token not in whitelist + { + note: "Valid SLP token not in whitelist", + tx_hash: + "3a4b628cbcc183ab376d44ce5252325f042268307ffa4a53443e92b6d24fb488", + tx_pos: 1, + value: 546 + }, + // Token send on BCHN network. + { + note: "Token send on BCHN network", + tx_hash: + "402c663379d9699b6e2dd38737061e5888c5a49fca77c97ab98e79e08959e019", + tx_pos: 1, + value: 546 + }, + // Token send on ABC network. + { + note: "Token send on ABC network", + tx_hash: + "336bfe2168aac4c3303508a9e8548a0d33797a83b85b76a12d845c8d6674f79d", + tx_pos: 1, + value: 546 + }, + // Known invalid SLP token send of PSF tokens. + { + note: "Known invalid SLP token send of PSF tokens", + tx_hash: + "2bf691ad3679d928fef880b8a45b93b233f8fa0d0a92cf792313dbe77b1deb74", + tx_pos: 1, + value: 546 + } + ] + + const data = await bchjs.SLP.Utils.tokenUtxoDetails(utxos) + console.log(`data: ${JSON.stringify(data, null, 2)}`) + + // Malformed SLP tx + assert.equal(data[0].tx_hash, utxos[0].tx_hash) + assert.equal(data[0].isValid, null) + + // Normal TX (non-SLP) + assert.equal(data[1].tx_hash, utxos[1].tx_hash) + assert.equal(data[1].isValid, false) + + // Valid PSF SLP tx + assert.equal(data[2].tx_hash, utxos[2].tx_hash) + assert.equal(data[2].isValid, true) + + // Valid SLP token not in whitelist + assert.equal(data[3].tx_hash, utxos[3].tx_hash) + assert.equal(data[3].isValid, true) + + // Token send on BCHN network + assert.equal(data[4].tx_hash, utxos[4].tx_hash) + assert.equal(data[4].isValid, true) + + // Token send on ABC network + assert.equal(data[5].tx_hash, utxos[5].tx_hash) + assert.equal(data[5].isValid, null) + + // Known invalid SLP token send of PSF tokens + assert.equal(data[6].tx_hash, utxos[6].tx_hash) + assert.equal(data[6].isValid, false) + }) }) describe("#balancesForAddress", () => { diff --git a/test/unit/slp-utils.js b/test/unit/slp-utils.js index 6b85d3f..3c1a419 100644 --- a/test/unit/slp-utils.js +++ b/test/unit/slp-utils.js @@ -1737,7 +1737,7 @@ describe("#SLP Utils", () => { }) // it("should handle a dust attack", async () => { - it("#dustattack", async () => { + it("should handle dust attack UTXOs", async () => { // Mock external dependencies. // Stub the calls to decodeOpReturn. sandbox @@ -1765,6 +1765,8 @@ describe("#SLP Utils", () => { assert.equal(data[0].isValid, false) }) + + // }) describe("#txDetails", () => { @@ -2005,9 +2007,9 @@ describe("#SLP Utils", () => { await slp.Utils.getWhitelist() - assert2.equal(true, false, "Unexpected result") + assert2.fail("Unexpected result") } catch (err) { - // console.log("err: ", err) + console.log("err: ", err) assert2.include(err.error, "Network error") } })