From bcbde40a52d51269edc467e0f855bd80b270f7e2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andr=C3=A9=20S=C3=A1?= Date: Fri, 19 Aug 2022 15:46:40 +0100 Subject: [PATCH] fix: CipherTrace scoring for cash-out transactions --- lib/cash-out/cash-out-tx.js | 70 ++++++++----------- .../wallet-scoring/ciphertrace/ciphertrace.js | 36 ++++++---- 2 files changed, 52 insertions(+), 54 deletions(-) diff --git a/lib/cash-out/cash-out-tx.js b/lib/cash-out/cash-out-tx.js index 99f9604b..bcf3e822 100644 --- a/lib/cash-out/cash-out-tx.js +++ b/lib/cash-out/cash-out-tx.js @@ -114,49 +114,37 @@ function processTxStatus (tx, settings) { } function getWalletScore (tx, pi) { - const statuses = ['published', 'authorized', 'rejected', 'insufficientFunds'] + const statuses = ['published', 'authorized', 'confirmed'] + + if (_.includes(tx.status, statuses) && _.isNil(tx.walletScore)) + return tx - if (_.includes(tx.status, statuses) && _.isNil(tx.walletScore)) { // Transaction shows up on the blockchain, we can request the sender address - return pi.getTransactionHash(tx) - .then(txHashes => pi.getInputAddresses(tx, txHashes)) - .then(addresses => { - const addressesPromise = [] - _.forEach(it => addressesPromise.push(pi.rateWallet(tx.cryptoCode, it)), addresses) - return Promise.all(addressesPromise) - }) - .then(scores => { - if (_.isNil(scores) || _.isEmpty(scores)) return tx - const highestScore = _.maxBy(it => it.score, scores) - - // Conservatively assign the highest risk of all input addresses to the risk of this transaction - return highestScore.isValid - ? _.assign(tx, { walletScore: highestScore.score }) - : _.assign(tx, { - walletScore: highestScore.score, - error: 'Address score is above defined threshold', - errorCode: 'scoreThresholdReached', - dispense: true - }) - }) - .catch(error => _.assign(tx, { - walletScore: 10, - error: `Failure getting address score: ${error.message}`, - errorCode: 'ciphertraceError', - dispense: true - })) - } - - if (_.includes(tx.status, statuses) && !_.isNil(tx.walletScore) && _.get('errorCode', tx) !== 'ciphertraceError') { - return pi.isValidWalletScore(tx.walletScore) - .then(isValid => isValid ? tx : _.assign(tx, { - error: 'Address score is above defined threshold', - errorCode: 'scoreThresholdReached', - dispense: true - })) - } - - return tx + return pi.getTransactionHash(tx) + .then(txHashes => _.isNil(txHashes) || _.isEmpty(txHashes) ? + Promise.reject({ message: "No transaction hashes" }) : + txHashes + ) + .then(txHashes => pi.getInputAddresses(tx, txHashes)) + .then(addresses => Promise.all(_.map(it => pi.rateWallet(tx.cryptoCode, it), addresses))) + .then(_.maxBy(_.get(['score']))) + .then(highestScore => + // Conservatively assign the highest risk of all input addresses to the risk of this transaction + highestScore.isValid + ? _.assign(tx, { walletScore: highestScore.score }) + : _.assign(tx, { + walletScore: highestScore.score, + error: 'Address score is above defined threshold', + errorCode: 'scoreThresholdReached', + dispense: true + }) + ) + .catch(error => _.assign(tx, { + walletScore: 10, + error: `Failure getting address score: ${error.message}`, + errorCode: 'ciphertraceError', + dispense: true + })) } function monitorLiveIncoming (settings) { diff --git a/lib/plugins/wallet-scoring/ciphertrace/ciphertrace.js b/lib/plugins/wallet-scoring/ciphertrace/ciphertrace.js index 02db9562..6fcf64d8 100644 --- a/lib/plugins/wallet-scoring/ciphertrace/ciphertrace.js +++ b/lib/plugins/wallet-scoring/ciphertrace/ciphertrace.js @@ -35,7 +35,7 @@ function rateWallet (account, cryptoCode, address) { }) .then(res => ({ address, score: res.data.risk, isValid: res.data.risk < threshold })) .then(result => { - console.log(`** DEBUG ** rateWallet RETURN: ${result}`) + console.log('** DEBUG ** rateWallet RETURN:', result) return result }) .catch(err => { @@ -69,32 +69,42 @@ function getAddressTransactionsHashes (receivingAddress, cryptoCode, client, wal function getTransactionHash (account, cryptoCode, receivingAddress, wallet) { const client = getClient(account) if (!_.includes(_.toUpper(cryptoCode), SUPPORTED_COINS) || _.isNil(client)) return Promise.resolve(null) - getAddressTransactionsHashes(receivingAddress, cryptoCode, client, wallet) - .then(res => { - const data = res.data - if (_.size(data.txHistory) > 1) { + return getAddressTransactionsHashes(receivingAddress, cryptoCode, client, wallet) + .then(({ data: { txHistory } }) => { + const txHashes = _.map(_.get(['txHash']), txHistory) + + if (_.size(txHashes) > 1) { logger.warn('An address generated by this wallet was used in more than one transaction') } - console.log(`** DEBUG ** getTransactionHash RETURN: ${_.join(', ', _.map(it => it.txHash, data.txHistory))}`) - return _.join(', ', _.map(it => it.txHash, data.txHistory)) + console.log('** DEBUG ** getTransactionHash RETURN: ', _.join(', ', txHashes)) + return txHashes }) .catch(err => { - console.log(`** DEBUG ** getTransactionHash from wallet node ERROR: ${err}`) + console.log('** DEBUG ** getTransactionHash from wallet node ERROR: ', err) throw err }) } function getInputAddresses (account, cryptoCode, txHashes) { const client = getClient(account) - if (!_.includes(_.toUpper(cryptoCode), SUPPORTED_COINS) || _.isNil(client)) return Promise.resolve(null) + if (_.isEmpty(txHashes) || !_.includes(_.toUpper(cryptoCode), SUPPORTED_COINS) || _.isNil(client)) + return Promise.resolve([]) + + /* NOTE: The API accepts at most 10 hashes, and for us here more than 1 is already an exception, not the norm. */ + if (_.size(txHashes) > 10) + return Promise.reject(new Error("Too many tx hashes -- shouldn't happen!")) const { apiVersion, authHeader } = client - console.log(`** DEBUG ** getInputAddresses ENDPOINT: https://rest.ciphertrace.com/api/${apiVersion}/${_.toLower(cryptoCode) !== 'btc' ? `${_.toLower(cryptoCode)}_` : ``}tx?txhashes=${txHashes}`) + cryptoCode = _.toLower(cryptoCode) + const lastPathComp = cryptoCode !== 'btc' ? cryptoCode + '_tx' : 'tx' - return axios.get(`https://rest.ciphertrace.com/api/${apiVersion}/${_.toLower(cryptoCode) !== 'btc' ? `${_.toLower(cryptoCode)}_` : ``}tx?txhashes=${txHashes}`, { - headers: authHeader - }) + txHashes = _(txHashes).take(10).join(',') + + const url = `https://rest.ciphertrace.com/api/${apiVersion}/${lastPathComp}?txhashes=${txHashes}` + console.log('** DEBUG ** getInputAddresses ENDPOINT: ', url) + + return axios.get(url, { headers: authHeader }) .then(res => { const data = res.data if (_.size(data.transactions) > 1) {