diff --git a/bin/lamassu-blacklist b/bin/lamassu-blacklist index 39a8743e..592fac7f 100644 --- a/bin/lamassu-blacklist +++ b/bin/lamassu-blacklist @@ -12,7 +12,7 @@ const rl = readline.createInterface({ }) const sql = `select regexp_replace(crypto_code, '$', ' ') as code,regexp_replace(address, '^', ' ') as address from blacklist` -const insertSql = `insert into blacklist(crypto_code, address) values ($1, $2)` +const insertSql = `insert into blacklist(crypto_code, address, created_by_operator) values ($1, $2, 't')` const makeDimAndReset = '\x1b[2m%s\x1b[0m' db.query(sql) diff --git a/bin/lamassu-remove-from-blacklist b/bin/lamassu-remove-from-blacklist new file mode 100644 index 00000000..3804ea59 --- /dev/null +++ b/bin/lamassu-remove-from-blacklist @@ -0,0 +1,58 @@ +#!/usr/bin/env node + +const os = require('os') +const readline = require('readline') + +const coinUtils = require('../lib/coin-utils') +const db = require('../lib/db') + +const rl = readline.createInterface({ + input: process.stdin, + output: process.stdout +}) + +const sql = `select regexp_replace(crypto_code, '$', ' ') as code,regexp_replace(address, '^', ' ') as address from blacklist` +const deleteSql = `delete from blacklist where crypto_code = $1 and address = $2` +const makeDimAndReset = '\x1b[2m%s\x1b[0m' + +db.query(sql) + .then(many => { + console.log(os.EOL, 'Here is your current list of prohibited addresses:', os.EOL) + console.log(many.map(it => `${it.code} | ${it.address}`).join(os.EOL), os.EOL) + console.log('What address would you like to remove from the list?', os.EOL) + console.log(makeDimAndReset, 'Example:', 'bc1q8pu2zlfxg8jf4cyuf844rl3uxmmw8sj9yaa393', os.EOL) + + rl.question('Address: ', (address) => { + var aphaNumericRegex = /^([0-9]|[a-z])+([0-9a-z]+)$/i + if (!address.match(aphaNumericRegex)) { + return errorHandler(new Error('Invalid address')) + } + + console.log(os.EOL, 'What is the ticker symbol of the coin that this address belongs to?', os.EOL) + console.log(makeDimAndReset, 'Example:', 'BTC', os.EOL) + + rl.question('Ticker symbol: ', (cryptoCode) => { + try { + coinUtils.getCryptoCurrency(cryptoCode) + } catch (err) { + errorHandler(err) + } + + db.none(deleteSql, [cryptoCode, address]) + .then(() => db.query(sql)) + .then(many2 => { + console.log(os.EOL, `Address removed. Here's the new list of prohibited addresses:`, os.EOL) + console.log(many2.map(it => `${it.code} | ${it.address}`).join(os.EOL)) + rl.close() + process.exit(0) + }) + .catch(errorHandler) + }) + }) + }).catch(errorHandler) + +function errorHandler (err) { + rl.close() + console.log(err) + process.exit(1) +} diff --git a/lamassu-admin-elm/src/Config.elm b/lamassu-admin-elm/src/Config.elm index 9d649b00..952a135e 100644 --- a/lamassu-admin-elm/src/Config.elm +++ b/lamassu-admin-elm/src/Config.elm @@ -1149,6 +1149,7 @@ complianceTableView model = , row "Sanctions" "sanctionsVerificationActive" "sanctionsVerificationThreshold" , row "Hard Limit" "hardLimitVerificationActive" "hardLimitVerificationThreshold" , row "Receipt Printing" "receiptPrintingActive" "" + , row "Reject Address Reuse" "rejectAddressReuseActive" "" ] ] diff --git a/lamassu-schema.json b/lamassu-schema.json index f94a2e05..3169522c 100644 --- a/lamassu-schema.json +++ b/lamassu-schema.json @@ -78,7 +78,8 @@ "frontCameraVerificationThreshold", "hardLimitVerificationActive", "hardLimitVerificationThreshold", - "receiptPrintingActive" + "receiptPrintingActive", + "rejectAddressReuseActive" ] }, { @@ -163,6 +164,20 @@ ], "default": false }, + { + "code": "rejectAddressReuseActive", + "displayTop": "Reject Address Reuse", + "displayBottom": "Reject Address Reuse", + "displayTopCount": 2, + "fieldType": "onOff", + "fieldClass": null, + "fieldValidation": [ + { + "code": "required" + } + ], + "default": false + }, { "code": "hardLimitVerificationActive", "displayTop": "Hard Limit", diff --git a/lib/blacklist.js b/lib/blacklist.js index 5c9c89ba..08d2705e 100644 --- a/lib/blacklist.js +++ b/lib/blacklist.js @@ -8,4 +8,15 @@ function blocked (address, cryptoCode) { ]) } -module.exports = { blocked } +function addToUsedAddresses (address, cryptoCode) { + // ETH reuses addresses + if (cryptoCode === 'ETH') return Promise.resolve() + + const sql = `insert into blacklist(crypto_code, address, created_by_operator) values ($1, $2, 'f')` + return db.oneOrNone(sql, [ + cryptoCode, + address + ]) +} + +module.exports = { blocked, addToUsedAddresses } diff --git a/lib/cash-in/cash-in-low.js b/lib/cash-in/cash-in-low.js index 0e000c22..ccb9ab01 100644 --- a/lib/cash-in/cash-in-low.js +++ b/lib/cash-in/cash-in-low.js @@ -8,7 +8,7 @@ const E = require('../error') const PENDING_INTERVAL_MS = 60 * T.minutes -const massage = _.flow(_.omit(['direction', 'cryptoNetwork', 'bills', 'blacklisted']), +const massage = _.flow(_.omit(['direction', 'cryptoNetwork', 'bills', 'blacklisted', 'addressReuse']), convertBigNumFields, _.mapKeys(_.snakeCase)) module.exports = {toObj, upsert, insert, update, massage, isClearToSend} diff --git a/lib/cash-in/cash-in-tx.js b/lib/cash-in/cash-in-tx.js index 16017c24..5147bba6 100644 --- a/lib/cash-in/cash-in-tx.js +++ b/lib/cash-in/cash-in-tx.js @@ -6,6 +6,8 @@ const blacklist = require('../blacklist') const db = require('../db') const plugins = require('../plugins') const logger = require('../logger') +const settingsLoader = require('../settings-loader') +const configManager = require('../config-manager') const cashInAtomic = require('./cash-in-atomic') const cashInLow = require('./cash-in-low') @@ -20,15 +22,24 @@ function post (machineTx, pi) { .then(r => { const updatedTx = r.tx let blacklisted = false + let addressReuse = false return checkForBlacklisted(updatedTx) - .then(isBlacklisted => { - blacklisted = !!isBlacklisted - return postProcess(r, pi, blacklisted) + .then(blacklistItem => { + if (blacklistItem && blacklistItem.created_by_operator) { + blacklisted = true + } + + if (blacklistItem && !blacklistItem.created_by_operator) { + addressReuse = true + } + + return postProcess(r, pi, blacklisted, addressReuse) }) .then(changes => cashInLow.update(db, updatedTx, changes)) .then(tx => _.set('bills', machineTx.bills, tx)) .then(tx => _.set('blacklisted', blacklisted, tx)) + .then(tx => _.set('addressReuse', addressReuse, tx)) }) } @@ -66,7 +77,14 @@ function checkForBlacklisted (tx) { return Promise.resolve(false) } -function postProcess (r, pi, isBlacklisted) { +function postProcess (r, pi, isBlacklisted, addressReuse) { + if (addressReuse) { + return Promise.resolve({ + operatorCompleted: true, + error: 'Address Reused' + }) + } + if (isBlacklisted) { return Promise.resolve({ operatorCompleted: true, @@ -104,6 +122,14 @@ function postProcess (r, pi, isBlacklisted) { } }) .then(sendRec => { + settingsLoader.loadLatest().then(it => { + const config = configManager.unscoped(it.config) + if (config.rejectAddressReuseActive) { + blacklist.addToUsedAddresses(r.tx.toAddress, r.tx.cryptoCode) + .catch(err => logger.error('Failure adding to addressReuse', err)) + } + }) + pi.notifyOperator(r.tx, sendRec) .catch((err) => logger.error('Failure sending transaction notification', err)) return logAction(sendRec, r.tx) diff --git a/migrations/1564485980102-alter-blacklist.js b/migrations/1564485980102-alter-blacklist.js new file mode 100644 index 00000000..976e7d62 --- /dev/null +++ b/migrations/1564485980102-alter-blacklist.js @@ -0,0 +1,13 @@ +const db = require('./db') + +exports.up = function (next) { + var sql = [ + "ALTER TABLE blacklist ADD COLUMN created_by_operator boolean not null default 't' " + ] + + db.multi(sql, next) +} + +exports.down = function (next) { + next() +} diff --git a/public/elm.js b/public/elm.js index fcd136a9..80c247d2 100644 --- a/public/elm.js +++ b/public/elm.js @@ -31189,8 +31189,8 @@ var _user$project$Config$updateSelectize = F3( return _elm_lang$core$Native_Utils.crashCase( 'Config', { - start: {line: 1555, column: 17}, - end: {line: 1560, column: 56} + start: {line: 1556, column: 17}, + end: {line: 1561, column: 56} }, _p4)('Shouldn\'t be here'); } @@ -31386,8 +31386,8 @@ var _user$project$Config$isField = function (fieldValue) { return _elm_lang$core$Native_Utils.crashCase( 'Config', { - start: {line: 1229, column: 5}, - end: {line: 1234, column: 59} + start: {line: 1230, column: 5}, + end: {line: 1235, column: 59} }, _p12)('Referenced field must be boolean'); } @@ -32877,7 +32877,11 @@ var _user$project$Config$complianceTableView = function (model) { _1: { ctor: '::', _0: A3(row, 'Receipt Printing', 'receiptPrintingActive', ''), - _1: {ctor: '[]'} + _1: { + ctor: '::', + _0: A3(row, 'Reject Address Reuse', 'rejectAddressReuseActive', ''), + _1: {ctor: '[]'} + } } } }