fix: CipherTrace scoring for cash-out transactions

This commit is contained in:
André Sá 2022-08-19 15:46:40 +01:00
parent 5ce5950609
commit bcbde40a52
2 changed files with 52 additions and 54 deletions

View file

@ -114,23 +114,23 @@ function processTxStatus (tx, settings) {
} }
function getWalletScore (tx, pi) { 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 // Transaction shows up on the blockchain, we can request the sender address
return pi.getTransactionHash(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(txHashes => pi.getInputAddresses(tx, txHashes))
.then(addresses => { .then(addresses => Promise.all(_.map(it => pi.rateWallet(tx.cryptoCode, it), addresses)))
const addressesPromise = [] .then(_.maxBy(_.get(['score'])))
_.forEach(it => addressesPromise.push(pi.rateWallet(tx.cryptoCode, it)), addresses) .then(highestScore =>
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 // Conservatively assign the highest risk of all input addresses to the risk of this transaction
return highestScore.isValid highestScore.isValid
? _.assign(tx, { walletScore: highestScore.score }) ? _.assign(tx, { walletScore: highestScore.score })
: _.assign(tx, { : _.assign(tx, {
walletScore: highestScore.score, walletScore: highestScore.score,
@ -138,7 +138,7 @@ function getWalletScore (tx, pi) {
errorCode: 'scoreThresholdReached', errorCode: 'scoreThresholdReached',
dispense: true dispense: true
}) })
}) )
.catch(error => _.assign(tx, { .catch(error => _.assign(tx, {
walletScore: 10, walletScore: 10,
error: `Failure getting address score: ${error.message}`, error: `Failure getting address score: ${error.message}`,
@ -147,18 +147,6 @@ function getWalletScore (tx, pi) {
})) }))
} }
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
}
function monitorLiveIncoming (settings) { function monitorLiveIncoming (settings) {
const statuses = ['notSeen', 'published', 'insufficientFunds'] const statuses = ['notSeen', 'published', 'insufficientFunds']
return monitorIncoming(settings, statuses, 0, STALE_LIVE_INCOMING_TX_AGE) return monitorIncoming(settings, statuses, 0, STALE_LIVE_INCOMING_TX_AGE)

View file

@ -35,7 +35,7 @@ function rateWallet (account, cryptoCode, address) {
}) })
.then(res => ({ address, score: res.data.risk, isValid: res.data.risk < threshold })) .then(res => ({ address, score: res.data.risk, isValid: res.data.risk < threshold }))
.then(result => { .then(result => {
console.log(`** DEBUG ** rateWallet RETURN: ${result}`) console.log('** DEBUG ** rateWallet RETURN:', result)
return result return result
}) })
.catch(err => { .catch(err => {
@ -69,32 +69,42 @@ function getAddressTransactionsHashes (receivingAddress, cryptoCode, client, wal
function getTransactionHash (account, cryptoCode, receivingAddress, wallet) { function getTransactionHash (account, cryptoCode, receivingAddress, wallet) {
const client = getClient(account) const client = getClient(account)
if (!_.includes(_.toUpper(cryptoCode), SUPPORTED_COINS) || _.isNil(client)) return Promise.resolve(null) if (!_.includes(_.toUpper(cryptoCode), SUPPORTED_COINS) || _.isNil(client)) return Promise.resolve(null)
getAddressTransactionsHashes(receivingAddress, cryptoCode, client, wallet) return getAddressTransactionsHashes(receivingAddress, cryptoCode, client, wallet)
.then(res => { .then(({ data: { txHistory } }) => {
const data = res.data const txHashes = _.map(_.get(['txHash']), txHistory)
if (_.size(data.txHistory) > 1) {
if (_.size(txHashes) > 1) {
logger.warn('An address generated by this wallet was used in more than one transaction') 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))}`) console.log('** DEBUG ** getTransactionHash RETURN: ', _.join(', ', txHashes))
return _.join(', ', _.map(it => it.txHash, data.txHistory)) return txHashes
}) })
.catch(err => { .catch(err => {
console.log(`** DEBUG ** getTransactionHash from wallet node ERROR: ${err}`) console.log('** DEBUG ** getTransactionHash from wallet node ERROR: ', err)
throw err throw err
}) })
} }
function getInputAddresses (account, cryptoCode, txHashes) { function getInputAddresses (account, cryptoCode, txHashes) {
const client = getClient(account) 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 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}`, { txHashes = _(txHashes).take(10).join(',')
headers: authHeader
}) 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 => { .then(res => {
const data = res.data const data = res.data
if (_.size(data.transactions) > 1) { if (_.size(data.transactions) > 1) {