diff --git a/package.json b/package.json index 58da9ae..986e388 100644 --- a/package.json +++ b/package.json @@ -16,7 +16,8 @@ "test:integration:local": "RESTURL=http://localhost:3000/v3/ mocha --timeout 30000 test/integration", "test:integration:local:bchn": "RESTURL=http://localhost:3000/v3/ bash test/integration/test-bchn-integration.sh", "test:integration:local:testnet": "RESTURL=http://localhost:4000/v3/ mocha --timeout 30000 test/integration/testnet", - "test:temp": "RESTURL=http://localhost:3000/v3/ mocha --timeout 30000 -g '#price' test/integration/", + "test:temp": "RESTURL=http://bchn.fullstack.cash/v3/ mocha --timeout 30000 -g '#validateTxid' test/integration/bchn/", + "test:temp2": "RESTURL=http://abc.fullstack.cash/v3/ mocha --timeout 30000 -g '#validateTxid' test/integration/", "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 2653ad5..4c3bc4d 100644 --- a/src/slp/utils.js +++ b/src/slp/utils.js @@ -444,16 +444,24 @@ class Utils { }, _this.axiosOptions ) - // console.log(`response.data: ${JSON.stringify(response.data, null, 2)}`) + console.log(`response.data: ${JSON.stringify(response.data, null, 2)}`) const validatedTxids = response.data + // CT 12/5/2020: Leaving this here for future reference. 'null' is a special + // value returned by SLPDB. It indicates that SLPDB is not aware of the TXID. + // It might mean that SLPDB has fallen behind. + // 'null' is distict from 'false'. 'false' firmly indicates that the TXID is + // not valid. 'null' indicates a lack of knowledge. // Handle any null values for (let i = 0; i < validatedTxids.length; i++) { if (validatedTxids[i] === null) { + console.log(`i: ${i}`) + console.log(`validatedTxids: ${validatedTxids}`) + console.log(`txids: ${txids}`) validatedTxids[i] = { txid: txids[i], - valid: false + valid: null } } } @@ -589,6 +597,100 @@ class Utils { } } + /** + * @api SLP.Utils.validateTxid3() validateTxid3() + * @apiName validateTxid3 + * @apiGroup SLP Utils + * @apiDescription Validate that txid is an SLP transaction using the SLPDB whitelist server. + * Same exact functionality as the validateTxid() function, but this function + * calls the whitelist SLPDB. It will only validate SLP tokens that are in the + * whitelist. You can retrieve the whitelist with the SLP.Utils.whitelist() + * function. + * + * @apiExample Example usage: + * + * // validate single SLP txid + * (async () => { + * try { + * let validated = await bchjs.SLP.Utils.validateTxid3( + * "df808a41672a0a0ae6475b44f272a107bc9961b90f29dc918d71301f24fe92fb" + * ); + * console.log(validated); + * } catch (error) { + * console.error(error); + * } + * })(); + * + * // returns + * [ { txid: + * 'df808a41672a0a0ae6475b44f272a107bc9961b90f29dc918d71301f24fe92fb', + * valid: true } ] + * + * // validate multiple SLP txids + * (async () => { + * try { + * let validated = await bchjs.SLP.Utils.validateTxid3([ + * "df808a41672a0a0ae6475b44f272a107bc9961b90f29dc918d71301f24fe92fb", + * "00ea27261196a411776f81029c0ebe34362936b4a9847deb1f7a40a02b3a1476" + * ]); + * console.log(validated); + * } catch (error) { + * console.error(error); + * } + * })(); + * + * // returns + * [ { txid: + * 'df808a41672a0a0ae6475b44f272a107bc9961b90f29dc918d71301f24fe92fb', + * valid: true }, + * { txid: + * '00ea27261196a411776f81029c0ebe34362936b4a9847deb1f7a40a02b3a1476', + * valid: true } ] + */ + async validateTxid3(txid) { + const path = `${this.restURL}slp/validateTxid3` + + // console.log(`txid: ${JSON.stringify(txid, null, 2)}`) + + // Handle a single TXID or an array of TXIDs. + let txids + if (typeof txid === "string") txids = [txid] + else txids = txid + + try { + const response = await axios.post( + path, + { + txids: txids + }, + _this.axiosOptions + ) + // console.log(`response.data: ${JSON.stringify(response.data, null, 2)}`) + + const validatedTxids = response.data + + // CT 12/5/2020: Leaving this here for future reference. 'null' is a special + // value returned by SLPDB. It indicates that SLPDB is not aware of the TXID. + // It might mean that SLPDB has fallen behind. + // 'null' is distict from 'false'. 'false' firmly indicates that the TXID is + // not valid. 'null' indicates a lack of knowledge. + // Handle any null values + for (let i = 0; i < validatedTxids.length; i++) { + if (validatedTxids[i] === null) { + validatedTxids[i] = { + txid: txids[i], + valid: null + } + } + } + + return validatedTxids + } catch (error) { + if (error.response && error.response.data) throw error.response.data + throw error + } + } + /** * @api SLP.Utils.tokenStats() tokenStats() * @apiName tokenStats diff --git a/test/integration/bchn/slp.js b/test/integration/bchn/slp.js index a9d7c34..57c3e42 100644 --- a/test/integration/bchn/slp.js +++ b/test/integration/bchn/slp.js @@ -674,6 +674,83 @@ describe(`#SLP`, () => { assert.property(result[1], "tokenId") }) }) + + describe("#validateTxid3", () => { + it("should invalidate a known invalid TXID", async () => { + const txid = + "f7e5199ef6669ad4d078093b3ad56e355b6ab84567e59ad0f08a5ad0244f783a" + + const result = await bchjs.SLP.Utils.validateTxid3(txid) + console.log(`result: ${JSON.stringify(result, null, 2)}`) + + assert.isArray(result) + + assert.property(result[0], "txid") + assert.equal(result[0].txid, txid) + + assert.property(result[0], "valid") + assert.equal(result[0].valid, false) + }) + + it("should validate a known valid TXID", async () => { + const txid = + "daf4d8b8045e7a90b7af81bfe2370178f687da0e545511bce1c9ae539eba5ffd" + + const result = await bchjs.SLP.Utils.validateTxid3(txid) + // console.log(`result: ${JSON.stringify(result, null, 2)}`) + + assert.isArray(result) + + assert.property(result[0], "txid") + assert.equal(result[0].txid, txid) + + assert.property(result[0], "valid") + assert.equal(result[0].valid, true) + }) + + it("should handle a mix of valid, invalid, and non-SLP txs", async () => { + const txids = [ + // Malformed SLP tx + "f7e5199ef6669ad4d078093b3ad56e355b6ab84567e59ad0f08a5ad0244f783a", + // Normal TX (non-SLP) + "01cdaec2f8b311fc2d6ecc930247bd45fa696dc204ab684596e281fe1b06c1f0", + // Valid PSF SLP tx + "daf4d8b8045e7a90b7af81bfe2370178f687da0e545511bce1c9ae539eba5ffd", + // Valid SLP token not in whitelist + "3a4b628cbcc183ab376d44ce5252325f042268307ffa4a53443e92b6d24fb488", + // Unprocessed SLP TX + // "a0d6406eecfd8634158efa9314ff15b4cbf451938e9dc7b5678c46b41eabc6ed" // Mint baton + "402c663379d9699b6e2dd38737061e5888c5a49fca77c97ab98e79e08959e019" + ] + + const result = await bchjs.SLP.Utils.validateTxid3(txids) + // console.log(`result: ${JSON.stringify(result, null, 2)}`) + }) + }) + + describe("#validateTxid", () => { + it("should handle a mix of valid, invalid, and non-SLP txs", async () => { + const txids = [ + // Malformed SLP tx + "f7e5199ef6669ad4d078093b3ad56e355b6ab84567e59ad0f08a5ad0244f783a", + // Normal TX (non-SLP) + "01cdaec2f8b311fc2d6ecc930247bd45fa696dc204ab684596e281fe1b06c1f0", + // Valid PSF SLP tx + "daf4d8b8045e7a90b7af81bfe2370178f687da0e545511bce1c9ae539eba5ffd", + // Valid SLP token not in whitelist + "3a4b628cbcc183ab376d44ce5252325f042268307ffa4a53443e92b6d24fb488", + // Unprocessed SLP TX + // "a0d6406eecfd8634158efa9314ff15b4cbf451938e9dc7b5678c46b41eabc6ed" + // Token send on BCHN network. + "402c663379d9699b6e2dd38737061e5888c5a49fca77c97ab98e79e08959e019", + // Token send on ABC network. + "336bfe2168aac4c3303508a9e8548a0d33797a83b85b76a12d845c8d6674f79d" + ] + + const result = await bchjs.SLP.Utils.validateTxid(txids) + console.log(`result: ${JSON.stringify(result, null, 2)}`) + }) + }) }) describe("#tokentype1", () => { diff --git a/test/integration/slp.js b/test/integration/slp.js index 3cc4a30..1a6580b 100644 --- a/test/integration/slp.js +++ b/test/integration/slp.js @@ -645,6 +645,83 @@ describe(`#SLP`, () => { assert.property(result[1], "tokenId") }) }) + + describe("#validateTxid3", () => { + it("should invalidate a known invalid TXID", async () => { + const txid = + "f7e5199ef6669ad4d078093b3ad56e355b6ab84567e59ad0f08a5ad0244f783a" + + const result = await bchjs.SLP.Utils.validateTxid3(txid) + // console.log(`result: ${JSON.stringify(result, null, 2)}`) + + assert.isArray(result) + + assert.property(result[0], "txid") + assert.equal(result[0].txid, txid) + + assert.property(result[0], "valid") + assert.equal(result[0].valid, false) + }) + + it("should validate a known valid TXID", async () => { + const txid = + "daf4d8b8045e7a90b7af81bfe2370178f687da0e545511bce1c9ae539eba5ffd" + + const result = await bchjs.SLP.Utils.validateTxid3(txid) + // console.log(`result: ${JSON.stringify(result, null, 2)}`) + + assert.isArray(result) + + assert.property(result[0], "txid") + assert.equal(result[0].txid, txid) + + assert.property(result[0], "valid") + assert.equal(result[0].valid, true) + }) + + it("should handle a mix of valid, invalid, and non-SLP txs", async () => { + const txids = [ + // Malformed SLP tx + "f7e5199ef6669ad4d078093b3ad56e355b6ab84567e59ad0f08a5ad0244f783a", + // Normal TX (non-SLP) + "01cdaec2f8b311fc2d6ecc930247bd45fa696dc204ab684596e281fe1b06c1f0", + // Valid PSF SLP tx + "daf4d8b8045e7a90b7af81bfe2370178f687da0e545511bce1c9ae539eba5ffd", + // Valid SLP token not in whitelist + "3a4b628cbcc183ab376d44ce5252325f042268307ffa4a53443e92b6d24fb488", + // Unprocessed SLP TX + // "a0d6406eecfd8634158efa9314ff15b4cbf451938e9dc7b5678c46b41eabc6ed" // Mint baton + "402c663379d9699b6e2dd38737061e5888c5a49fca77c97ab98e79e08959e019" + ] + + const result = await bchjs.SLP.Utils.validateTxid3(txids) + // console.log(`result: ${JSON.stringify(result, null, 2)}`) + }) + + describe("#validateTxid", () => { + it("should handle a mix of valid, invalid, and non-SLP txs", async () => { + const txids = [ + // Malformed SLP tx + "f7e5199ef6669ad4d078093b3ad56e355b6ab84567e59ad0f08a5ad0244f783a", + // Normal TX (non-SLP) + "01cdaec2f8b311fc2d6ecc930247bd45fa696dc204ab684596e281fe1b06c1f0", + // Valid PSF SLP tx + "daf4d8b8045e7a90b7af81bfe2370178f687da0e545511bce1c9ae539eba5ffd", + // Valid SLP token not in whitelist + "3a4b628cbcc183ab376d44ce5252325f042268307ffa4a53443e92b6d24fb488", + // Unprocessed SLP TX + // "a0d6406eecfd8634158efa9314ff15b4cbf451938e9dc7b5678c46b41eabc6ed" + // Token send on BCHN network. + "402c663379d9699b6e2dd38737061e5888c5a49fca77c97ab98e79e08959e019", + // Token send on ABC network. + "336bfe2168aac4c3303508a9e8548a0d33797a83b85b76a12d845c8d6674f79d" + ] + + const result = await bchjs.SLP.Utils.validateTxid(txids) + console.log(`result: ${JSON.stringify(result, null, 2)}`) + }) + }) + }) }) describe("#tokentype1", () => { diff --git a/test/unit/fixtures/slp/mock-utils.js b/test/unit/fixtures/slp/mock-utils.js index f249b41..865ee76 100644 --- a/test/unit/fixtures/slp/mock-utils.js +++ b/test/unit/fixtures/slp/mock-utils.js @@ -1130,6 +1130,39 @@ const mockWhitelist = [ } ] +const mockValidateTxid3Valid = [ + { + txid: "daf4d8b8045e7a90b7af81bfe2370178f687da0e545511bce1c9ae539eba5ffd", + valid: true + } +] + +const mockValidateTxid3Invalid = [ + { + txid: "f7e5199ef6669ad4d078093b3ad56e355b6ab84567e59ad0f08a5ad0244f783a", + valid: false + } +] + +const mockValidateTxidArray = [ + { + txid: "daf4d8b8045e7a90b7af81bfe2370178f687da0e545511bce1c9ae539eba5ffd", + valid: true + }, + { + txid: "3a4b628cbcc183ab376d44ce5252325f042268307ffa4a53443e92b6d24fb488", + valid: true + }, + { + txid: "f7e5199ef6669ad4d078093b3ad56e355b6ab84567e59ad0f08a5ad0244f783a", + valid: false + }, + { + txid: "01cdaec2f8b311fc2d6ecc930247bd45fa696dc204ab684596e281fe1b06c1f0", + valid: false + } +] + module.exports = { mockList, mockToken, @@ -1155,5 +1188,7 @@ module.exports = { mockDualValidation, mockDualOpData, mockInvalidSlpSend, - mockWhitelist + mockWhitelist, + mockValidateTxid3Valid, + mockValidateTxid3Invalid } diff --git a/test/unit/slp-utils.js b/test/unit/slp-utils.js index b964e09..47b88cc 100644 --- a/test/unit/slp-utils.js +++ b/test/unit/slp-utils.js @@ -1945,6 +1945,45 @@ describe("#SLP Utils", () => { }) }) + describe("#validateTxid", () => { + it("should invalidate a known invalid TXID", async () => { + const txid = + "f7e5199ef6669ad4d078093b3ad56e355b6ab84567e59ad0f08a5ad0244f783a" + + // Mock live network calls. + sandbox.stub(axios, "post").resolves({ + data: mockData.mockValidateTxid3Invalid + }) + + const result = await slp.Utils.validateTxid(txid) + // console.log(`result: ${JSON.stringify(result, null, 2)}`) + + assert2.isArray(result) + + assert2.property(result[0], "txid") + assert2.equal(result[0].txid, txid) + + assert2.property(result[0], "valid") + assert2.equal(result[0].valid, false) + }) + + it("should handle a mix of valid, invalid, and non-SLP txs", async () => { + const txids = [ + // Malformed SLP tx + "f7e5199ef6669ad4d078093b3ad56e355b6ab84567e59ad0f08a5ad0244f783a", + // Normal TX (non-SLP) + "01cdaec2f8b311fc2d6ecc930247bd45fa696dc204ab684596e281fe1b06c1f0", + // Valid PSF SLP tx + "daf4d8b8045e7a90b7af81bfe2370178f687da0e545511bce1c9ae539eba5ffd", + // Valid SLP token not in whitelist + "3a4b628cbcc183ab376d44ce5252325f042268307ffa4a53443e92b6d24fb488" + ] + + const result = await bchjs.SLP.Utils.validateTxid(txids) + console.log(`result: ${JSON.stringify(result, null, 2)}`) + }) + }) + describe("#getWhitelist", () => { it("should return the list", async () => { sandbox.stub(axios, "get").resolves({ data: mockData.mockWhitelist }) @@ -1972,4 +2011,82 @@ describe("#SLP Utils", () => { } }) }) + + describe("#validateTxid3", () => { + it("should invalidate a known invalid TXID", async () => { + const txid = + "f7e5199ef6669ad4d078093b3ad56e355b6ab84567e59ad0f08a5ad0244f783a" + + // Mock live network calls. + sandbox.stub(axios, "post").resolves({ + data: mockData.mockValidateTxid3Invalid + }) + + const result = await slp.Utils.validateTxid3(txid) + // console.log(`result: ${JSON.stringify(result, null, 2)}`) + + assert2.isArray(result) + + assert2.property(result[0], "txid") + assert2.equal(result[0].txid, txid) + + assert2.property(result[0], "valid") + assert2.equal(result[0].valid, false) + }) + + it("should handle an array with a single element", async () => { + sandbox + .stub(axios, "post") + .resolves({ data: mockData.mockValidateTxid3Valid }) + + const txid = [ + "daf4d8b8045e7a90b7af81bfe2370178f687da0e545511bce1c9ae539eba5ffd" + ] + + const result = await slp.Utils.validateTxid3(txid) + + assert2.isArray(result) + + assert2.property(result[0], "txid") + assert2.equal(result[0].txid, txid) + + assert2.property(result[0], "valid") + assert2.equal(result[0].valid, true) + }) + + it("should handle an single string input", async () => { + sandbox + .stub(axios, "post") + .resolves({ data: mockData.mockValidateTxid3Valid }) + + const txid = + "daf4d8b8045e7a90b7af81bfe2370178f687da0e545511bce1c9ae539eba5ffd" + + const result = await slp.Utils.validateTxid3(txid) + + assert2.isArray(result) + + assert2.property(result[0], "txid") + assert2.equal(result[0].txid, txid) + + assert2.property(result[0], "valid") + assert2.equal(result[0].valid, true) + }) + + it("catches and throws an error", async () => { + try { + sandbox.stub(axios, "post").rejects({ + error: + "Network error: Could not communicate with full node or other external service." + }) + + await slp.Utils.validateTxid3() + + assert2.equal(true, false, "Unexpected result") + } catch (err) { + // console.log("err: ", err) + assert2.include(err.error, "Network error") + } + }) + }) })