diff --git a/package.json b/package.json index 92ad764..36f46d7 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 '#hydrateUtxos' test/integration/slp.js", + "test:temp": "mocha --timeout 30000 -g '#validateTxid2' 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 58580d0..a18fc60 100644 --- a/src/slp/utils.js +++ b/src/slp/utils.js @@ -455,6 +455,66 @@ class Utils { } } + /** + * @api SLP.Utils.validateTxid2() validateTxid2() + * @apiName validateTxid2 + * @apiGroup SLP Utils + * @apiDescription Validate that txid is an SLP transaction. + * + * This second validatoin version uses the slp-validate slp library. It is + * much slower and less efficient than SLPDB and is prone to time-outs for + * tokens with large DAGs. However, it operates independently of SLPDB and + * is a great second validation option, particularly when SLPDB returns 'null' + * values. + * + * Due to the inefficiency of this call, only a single TXID can be input at a + * time. This call will throw an error if the input is an array. + * + * @apiExample Example usage: + * + * // validate single SLP txid + * (async () => { + * try { + * let validated = await bchjs.SLP.Utils.validateTxid( + * "df808a41672a0a0ae6475b44f272a107bc9961b90f29dc918d71301f24fe92fb" + * ); + * console.log(validated); + * } catch (error) { + * console.error(error); + * } + * })(); + * + * // returns + * [ { txid: + * 'df808a41672a0a0ae6475b44f272a107bc9961b90f29dc918d71301f24fe92fb', + * valid: true } ] + */ + async validateTxid2(txid) { + try { + // console.log(`txid: ${JSON.stringify(txid, null, 2)}`) + + if ( + !txid || + txid === "" || + typeof txid !== "string" || + txid.length !== 64 + ) + throw new Error(`txid must be 64 character string.`) + + const path = `${this.restURL}slp/validateTxid2/${txid}` + + const response = await axios.get(path, _this.axiosOptions) + return response.data + } catch (error) { + if (error.response && error.response.data) throw error.response.data + + if (error.error && error.error.indexOf("Network error") > -1) + throw new Error("slp-validate timed out") + + throw error + } + } + /** * @api SLP.Utils.tokenStats() tokenStats() * @apiName tokenStats diff --git a/test/integration/slp.js b/test/integration/slp.js index 32c75ae..119807f 100644 --- a/test/integration/slp.js +++ b/test/integration/slp.js @@ -607,6 +607,36 @@ describe(`#SLP`, () => { } }) }) + + describe("#validateTxid2", () => { + it("should invalidate a known invalid TXID", async () => { + const txid = + "f7e5199ef6669ad4d078093b3ad56e355b6ab84567e59ad0f08a5ad0244f783a" + + const result = await bchjs.SLP.Utils.validateTxid2(txid) + // console.log(`result: ${JSON.stringify(result, null, 2)}`) + + assert.property(result, "txid") + assert.equal(result.txid, txid) + + assert.property(result, "isValid") + assert.equal(result.isValid, false) + }) + + it("should validate a known valid TXID", async () => { + const txid = + "3a4b628cbcc183ab376d44ce5252325f042268307ffa4a53443e92b6d24fb488" + + const result = await bchjs.SLP.Utils.validateTxid2(txid) + // console.log(`result: ${JSON.stringify(result, null, 2)}`) + + assert.property(result, "txid") + assert.equal(result.txid, txid) + + assert.property(result, "isValid") + assert.equal(result.isValid, true) + }) + }) }) describe("#tokentype1", () => { diff --git a/test/unit/slp-utils.js b/test/unit/slp-utils.js index ed1175e..4480e5e 100644 --- a/test/unit/slp-utils.js +++ b/test/unit/slp-utils.js @@ -1815,4 +1815,103 @@ describe("#SLP Utils", () => { } }) }) + + describe("#validateTxid2", () => { + it("should throw an error if the input is an array", async () => { + try { + const txid = [ + "f7e5199ef6669ad4d078093b3ad56e355b6ab84567e59ad0f08a5ad0244f783a" + ] + + await slp.Utils.validateTxid2(txid) + + assert2.equal(true, false, "Unexpected result") + } catch (err) { + // console.log("err: ", err) + assert2.include(err.message, "txid must be 64 character string") + } + }) + + it("should throw an error for a malformed txid", async () => { + try { + const txid = + "f7e5199ef6669ad4d078093b3ad56e355b6ab84567e59ad0f08a5ad0244f783" + + await slp.Utils.validateTxid2(txid) + + assert2.equal(true, false, "Unexpected result") + } catch (err) { + // console.log("err: ", err) + assert2.include(err.message, "txid must be 64 character string") + } + }) + + it("should invalidate a known invalid TXID", async () => { + const txid = + "f7e5199ef6669ad4d078093b3ad56e355b6ab84567e59ad0f08a5ad0244f783a" + + // Mock live network calls. + sandbox.stub(axios, "get").resolves({ + data: { + txid: txid, + isValid: false, + msg: "" + } + }) + + const result = await slp.Utils.validateTxid2(txid) + // console.log(`result: ${JSON.stringify(result, null, 2)}`) + + assert2.property(result, "txid") + assert2.equal(result.txid, txid) + + assert2.property(result, "isValid") + assert2.equal(result.isValid, false) + }) + + it("should validate a known valid TXID", async () => { + const txid = + "3a4b628cbcc183ab376d44ce5252325f042268307ffa4a53443e92b6d24fb488" + + // Mock live network calls. + sandbox.stub(axios, "get").resolves({ + data: { + txid: txid, + isValid: true, + msg: "" + } + }) + + const result = await slp.Utils.validateTxid2(txid) + // console.log(`result: ${JSON.stringify(result, null, 2)}`) + + assert2.property(result, "txid") + assert2.equal(result.txid, txid) + + assert2.property(result, "isValid") + assert2.equal(result.isValid, true) + }) + + // slp-validate can take a long time. bch-api cuts it off if it fails to + // return is less than 10 seconds. This test case handle this situation. + it("should handle timeout errors", async () => { + try { + const txid = + "eacb1085dfa296fef6d4ae2c0f4529a1bef096dd2325bdcc6dcb5241b3bdb579" + + // Mock live network calls. + sandbox.stub(axios, "get").rejects({ + error: + "Network error: Could not communicate with full node or other external service." + }) + + await slp.Utils.validateTxid2(txid) + + assert2.equal(true, false, "Unexpected result") + } catch (err) { + // console.log("err: ", err) + assert2.include(err.message, "slp-validate timed out") + } + }) + }) })