diff --git a/package.json b/package.json index 8406a97..3bd5509 100644 --- a/package.json +++ b/package.json @@ -16,7 +16,7 @@ "test:integration:local": "RESTURL=http://localhost:3000/v4/ mocha --timeout 30000 test/integration", "test:integration:local:bchn": "RESTURL=http://localhost:3000/v4/ mocha --timeout 30000 test/integration/ && mocha --timeout 30000 test/integration/chains/bchn/", "test:integration:local:testnet": "RESTURL=http://localhost:4000/v4/ mocha --timeout 30000 test/integration/testnet", - "test:temp": "export RESTURL=https://bchn.fullstack.cash/v4/ && mocha --timeout 30000 -g '#getTxDataSlp' test/integration/", + "test:temp": "export RESTURL=https://bchn.fullstack.cash/v4/ && mocha --timeout 30000 -g '#waterfallValidateTxid' test/integration/", "test:temp2": "mocha --timeout=30000 -g '#getTxData' test/unit/", "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 b11a597..37090ff 100644 --- a/src/slp/utils.js +++ b/src/slp/utils.js @@ -1426,6 +1426,115 @@ class Utils { } } + // This function aggregates all the available SLP token validation sources. + // It starts with the fastest, most-efficient source first, and continues + // to other validation sources until the txid is validated (true or false). + // If the txid goes through all sources and can't be validated, it will + // return null. + // + // Validation sources from most efficient to least efficient: + // - SLPDB whitelist + // - SLPDB general + // - slp-api + // + // Currently only supports a single txid at a time. + async waterfallValidateTxid (txid) { + try { + const cachedTxValidation = {} + + // If the value has been cached, use the cached version first. + let isValid = cachedTxValidation[txid] + if (!isValid) { + isValid = null + } else { + return isValid + } + + // There are two possible responses from SLPDB. If SLPDB is functioning + // correctly, then validateTxid() will return this: + // isValid: [ + // { + // "txid": "ff0c0354f8d3ddb34fa36f73494eb58ea24f8b8da6904aa8ed43b7a74886c583", + // "valid": true + // } + // ] + // + // If SLPDB has fallen behind real-time processing, it will return this: + // isValid: [ + // null + // ] + // + // Note: validateTxid3() has the same output as validateTxid(). + // validateTxid2() uses slp-validate, which has a different output format. + + // Validate against the whitelist SLPDB first. + const whitelistResult = await this.validateTxid3(txid) + // console.log( + // `whitelist-SLPDB for ${txid}: ${JSON.stringify( + // isValid, + // null, + // 2 + // )}` + // ) + + // Safely retrieve the returned value. + if (whitelistResult[0] !== null) isValid = whitelistResult[0].valid + + // Exit if isValid is not null. + if (isValid !== null) { + // Save to the cache. + cachedTxValidation[txid] = isValid + + return isValid + } + + // Try the general SLPDB, if the whitelist returned null. + const generalResult = await this.validateTxid(txid) + // console.log( + // `validateTxid() isValid: ${JSON.stringify(isValid, null, 2)}` + // ) + + // Safely retrieve the returned value. + if (generalResult[0] === null) isValid = generalResult[0].valid + + // Exit if isValid is not null. + if (isValid !== null) { + // Save to the cache. + cachedTxValidation[txid] = isValid + + return isValid + } + + // If still null, as a last resort, check it against slp-validate + let slpValidateResult = null + try { + slpValidateResult = await this.validateTxid2(txid) + } catch (err) { + /* exit quietly */ + } + // console.log( + // `slp-validate isValid: ${JSON.stringify(isValid, null, 2)}` + // ) + + // Exit if isValid is not null. + if (slpValidateResult !== null) { + isValid = slpValidateResult.isValid + + // Save to the cache. + cachedTxValidation[txid] = isValid + + return isValid + } + + // If isValid is still null, return that value, signaling that the txid + // could not be validated. + return isValid + } catch (error) { + if (error.response && error.response.data) throw error.response.data + throw error + } + } + /** * @api SLP.Utils.hydrateUtxos() hydrateUtxos() * @apiName hydrateUtxos diff --git a/test/integration/slp.js b/test/integration/slp.js index 405fcd7..3b03253 100644 --- a/test/integration/slp.js +++ b/test/integration/slp.js @@ -735,6 +735,27 @@ describe('#SLP', () => { assert.property(result, 'slpProcessedBlockHeight') }) }) + + describe('#waterfallValidateTxid', () => { + it('should validate known good txid not in whitelist', async () => { + const txid = '3a4b628cbcc183ab376d44ce5252325f042268307ffa4a53443e92b6d24fb488' + + const result = await bchjs.SLP.Utils.waterfallValidateTxid(txid) + // console.log(`result: ${JSON.stringify(result, null, 2)}`) + + assert.equal(result, true) + }) + + it('should invalidate a known invalid TXID', async () => { + const txid = + 'f7e5199ef6669ad4d078093b3ad56e355b6ab84567e59ad0f08a5ad0244f783a' + + const result = await bchjs.SLP.Utils.waterfallValidateTxid(txid) + // console.log(`result: ${JSON.stringify(result, null, 2)}`) + + assert.equal(result, false) + }) + }) }) describe('#tokentype1', () => {