Merge remote-tracking branch 'upstream/defiant-dingirma' into dev

This commit is contained in:
Rafael Taranto 2019-11-28 22:03:37 +00:00
commit db3a4e4936
25 changed files with 532 additions and 460 deletions

View file

@ -13,6 +13,8 @@ LAMASSU_CA_PATH=$PWD/Lamassu_CA.pem
MIGRATE_STATE_PATH=$CONFIG_DIR/.migrate
POSTGRES_PASS=postgres123
OFAC_DATA_DIR=$CONFIG_DIR/ofac
IDPHOTOCARD_DIR=$CONFIG_DIR/idphotocard
FRONTCAMERA_DIR=$CONFIG_DIR/frontcamera
mkdir -p $CERT_DIR
mkdir -p $CONFIG_DIR >> $LOG_FILE 2>&1
@ -109,7 +111,9 @@ cat <<EOF > $CONFIG_DIR/lamassu.json
"name": "cons_advanced",
"url": "https://www.treasury.gov/ofac/downloads/sanctions/1.0/cons_advanced.xml"
}
]
],
"idPhotoCardDir": "$IDPHOTOCARD_DIR",
"frontCameraDir": "$FRONTCAMERA_DIR"
}
EOF

View file

@ -13,5 +13,5 @@ if (process.argv.length !== 4) {
process.exit(3)
}
const masterSeed = new Buffer(masterSeedHex, 'hex')
console.log(hkdf(masterSeed, 32, {salt: 'lamassu-server-salt', info: label}).toString('hex'))
const masterSeed = Buffer.from(masterSeedHex, 'hex')
console.log(hkdf(masterSeed, 32, { salt: 'lamassu-server-salt', info: label }).toString('hex'))

View file

@ -1,7 +1,5 @@
#!/usr/bin/env bash
SEED="$(cat ~/seeds/seed.txt)"
echo
echo "Here is the 'External ID' of your paired machine(s), for use under the 'ATM / Teller details' of your CoinATMRadar listing:"
echo
@ -9,5 +7,5 @@ su - postgres -c "psql \"lamassu\" -Atc \"select regexp_replace(device_id, '$',
echo
echo "If speaking with CoinATMRadar directly, it may be helpful to let them know your 'Operator ID':"
echo
$(npm root -g)/lamassu-server/bin/hkdf operator-id "$SEED" | cut -c -32
$(npm root -g)/lamassu-server/bin/lamassu-operator
echo

View file

@ -1,9 +1,12 @@
#!/usr/bin/env bash
#!/usr/bin/env node
SEED="$(cat ~/seeds/seed.txt)"
const fs = require('fs')
const hkdf = require('futoin-hkdf')
echo
echo "Your Operator ID for use with CoinATMRadar is:"
echo
/usr/bin/hkdf operator-id "$SEED" | cut -c -32
echo
const options = require('../lib/options')
const mnemonicHelpers = require('../lib/mnemonic-helpers')
const mnemonic = fs.readFileSync(options.mnemonicPath, 'utf8').trim()
const masterSeed = mnemonicHelpers.toEntropyBuffer(mnemonic)
console.log(hkdf(masterSeed, 16, { salt: 'lamassu-server-salt', info: 'operator-id' }).toString('hex'))

View file

@ -262,7 +262,6 @@ view route invalidGroups =
, ( "itBit", AccountRoute "itbit", True )
, ( "Kraken", AccountRoute "kraken", True )
, ( "Mailgun", AccountRoute "mailgun", True )
, ( "QuadrigaCX", AccountRoute "quadrigacx", True )
, ( "Strike", AccountRoute "strike", True )
, ( "Twilio", AccountRoute "twilio", True )
]

View file

@ -178,8 +178,8 @@ function fetchData () {
{code: 'bitpay', display: 'Bitpay', class: 'ticker', cryptos: ['BTC', 'BCH']},
{code: 'kraken', display: 'Kraken', class: 'ticker', cryptos: ['BTC', 'ETH', 'LTC', 'DASH', 'ZEC', 'BCH']},
{code: 'bitstamp', display: 'Bitstamp', class: 'ticker', cryptos: ['BTC', 'ETH', 'LTC', 'BCH']},
{code: 'coinbase', display: 'Coinbase', class: 'ticker', cryptos: ['BTC', 'ETH', 'LTC', 'BCH']},
{code: 'itbit', display: 'itBit', class: 'ticker', cryptos: ['BTC']},
{code: 'coinbase', display: 'Coinbase', class: 'ticker', cryptos: ['BTC', 'ETH', 'LTC', 'BCH', 'ZEC', 'DASH']},
{code: 'itbit', display: 'itBit', class: 'ticker', cryptos: ['BTC', 'ETH']},
{code: 'mock-ticker', display: 'Mock (Caution!)', class: 'ticker', cryptos: ALL_CRYPTOS},
{code: 'bitcoind', display: 'bitcoind', class: 'wallet', cryptos: ['BTC']},
{code: 'no-layer2', display: 'No Layer 2', class: 'layer2', cryptos: ALL_CRYPTOS},
@ -191,7 +191,7 @@ function fetchData () {
{code: 'bitcoincashd', display: 'bitcoincashd', class: 'wallet', cryptos: ['BCH']},
{code: 'bitgo', display: 'BitGo', class: 'wallet', cryptos: ['BTC', 'ZEC', 'LTC', 'BCH', 'DASH']},
{code: 'bitstamp', display: 'Bitstamp', class: 'exchange', cryptos: ['BTC', 'ETH', 'LTC', 'BCH']},
{code: 'itbit', display: 'itBit', class: 'exchange', cryptos: ['BTC']},
{code: 'itbit', display: 'itBit', class: 'exchange', cryptos: ['BTC', 'ETH']},
{code: 'kraken', display: 'Kraken', class: 'exchange', cryptos: ['BTC', 'ETH', 'LTC', 'DASH', 'ZEC', 'BCH']},
{code: 'mock-wallet', display: 'Mock (Caution!)', class: 'wallet', cryptos: ALL_CRYPTOS},
{code: 'no-exchange', display: 'No exchange', class: 'exchange', cryptos: ALL_CRYPTOS},

View file

@ -2,7 +2,7 @@ const db = require('./db')
function blocked (address, cryptoCode) {
const sql = `select * from blacklist where address = $1 and crypto_code = $2`
return db.oneOrNone(sql, [
return db.any(sql, [
address,
cryptoCode
])

View file

@ -21,7 +21,7 @@ function buildConfig () {
rpcpassword=${common.randomPass()}
dbcache=500
server=1
connections=40
maxconnections=40
keypool=10000
prune=4000
daemon=0

View file

@ -21,19 +21,19 @@ module.exports = {
const BINARIES = {
BTC: {
url: 'https://bitcoin.org/bin/bitcoin-core-0.18.0/bitcoin-0.18.0-x86_64-linux-gnu.tar.gz',
dir: 'bitcoin-0.18.0/bin'
url: 'https://bitcoin.org/bin/bitcoin-core-0.18.1/bitcoin-0.18.1-x86_64-linux-gnu.tar.gz',
dir: 'bitcoin-0.18.1/bin'
},
ETH: {
url: 'https://gethstore.blob.core.windows.net/builds/geth-linux-amd64-1.9.1-b7b2f60f.tar.gz',
dir: 'geth-linux-amd64-1.9.1-b7b2f60f'
url: 'https://gethstore.blob.core.windows.net/builds/geth-linux-amd64-1.9.7-a718daa6.tar.gz',
dir: 'geth-linux-amd64-1.9.7-a718daa6'
},
ZEC: {
url: 'https://z.cash/downloads/zcash-2.0.6-linux64.tar.gz',
dir: 'zcash-2.0.6/bin'
url: 'https://z.cash/downloads/zcash-2.1.0-1-linux64-debian-jessie.tar.gz',
dir: 'zcash-2.1.0-1/bin'
},
DASH: {
url: 'https://github.com/dashpay/dash/releases/download/v0.14.0.2/dashcore-0.14.0.2-x86_64-linux-gnu.tar',
url: 'https://github.com/dashpay/dash/releases/download/v0.14.0.3/dashcore-0.14.0.3-x86_64-linux-gnu.tar.gz',
dir: 'dashcore-0.14.0/bin'
},
LTC: {
@ -41,8 +41,8 @@ const BINARIES = {
dir: 'litecoin-0.17.1/bin'
},
BCH: {
url: 'https://download.bitcoinabc.org/0.19.10/linux/bitcoin-abc-0.19.10-x86_64-linux-gnu.tar.gz',
dir: 'bitcoin-abc-0.19.10/bin',
url: 'https://download.bitcoinabc.org/0.20.5/linux/bitcoin-abc-0.20.5-x86_64-linux-gnu.tar.gz',
dir: 'bitcoin-abc-0.20.5/bin',
files: [['bitcoind', 'bitcoincashd'], ['bitcoin-cli', 'bitcoincash-cli']]
}
}

View file

@ -24,13 +24,13 @@ function post (machineTx, pi) {
let blacklisted = false
let addressReuse = false
return checkForBlacklisted(updatedTx)
.then(blacklistItem => {
if (blacklistItem && blacklistItem.created_by_operator) {
blacklisted = true
}
return Promise.all([settingsLoader.loadLatest(), checkForBlacklisted(updatedTx)])
.then(([{ config }, blacklistItems]) => {
const rejectAddressReuseActive = configManager.unscoped(config).rejectAddressReuseActive
if (blacklistItem && !blacklistItem.created_by_operator) {
if (_.some(it => it.created_by_operator === true)(blacklistItems)) {
blacklisted = true
} else if (_.some(it => it.created_by_operator === false)(blacklistItems) && rejectAddressReuseActive) {
addressReuse = true
}

View file

@ -5,6 +5,8 @@ const configManager = require('./config-manager')
const logger = require('./logger')
const schema = require('../lamassu-schema.json')
const REMOVED_FIELDS = ['crossRefVerificationActive', 'crossRefVerificationThreshold']
function allScopes (cryptoScopes, machineScopes) {
const scopes = []
cryptoScopes.forEach(c => {
@ -128,6 +130,8 @@ function ensureConstraints (config) {
.then(() => {
config.every(fieldInstance => {
const fieldCode = fieldInstance.fieldLocator.code
if (_.includes(fieldCode, REMOVED_FIELDS)) return
const field = pickField(fieldCode)
if (!field) {
logger.warn('No such field: %s, %j', fieldCode, fieldInstance.fieldLocator.fieldScope)

View file

@ -347,7 +347,7 @@ function plugins (settings, deviceId) {
if (!transactionNotificationsEnabled()) return Promise.resolve()
const isCashOut = tx.direction === 'cashOut'
const zeroConf = isZeroConf(tx)
const zeroConf = isCashOut && isZeroConf(tx)
// 0-conf cash-out should only send notification on redemption
if (zeroConf && isCashOut && !rec.isRedemption && !rec.error) return Promise.resolve()

View file

@ -1,91 +0,0 @@
const axios = require('axios')
const crypto = require('crypto')
const _ = require('lodash/fp')
const API_ENDPOINT = 'https://api.quadrigacx.com/v2'
let counter = -1
let lastTimestamp = Date.now()
function pad (num) {
const asString = num.toString(10)
if (num < 10) return '00' + asString
if (num < 100) return '0' + asString
return asString
}
function generateNonce () {
const timestamp = Date.now()
if (timestamp !== lastTimestamp) counter = -1
lastTimestamp = timestamp
counter = (counter + 1) % 1000
return timestamp.toString(10) + pad(counter)
}
function authRequest (config, path, data) {
if (!config.key || !config.secret || !config.clientId) {
const err = new Error('Must provide key, secret and client ID')
return Promise.reject(err)
}
data = data || {}
const nonce = generateNonce()
const msg = [nonce, config.clientId, config.key].join('')
const signature = crypto
.createHmac('sha256', Buffer.from(config.secret))
.update(msg)
.digest('hex')
.toLowerCase()
const signedData = _.merge(data, {
key: config.key,
signature,
nonce
})
return request(path, 'POST', signedData)
}
function buildMarket (fiatCode, cryptoCode) {
if (!_.includes(cryptoCode, ['BTC', 'ETH', 'LTC', 'BCH'])) {
throw new Error(`Unsupported crypto: ${cryptoCode}`)
}
if (!_.includes(fiatCode, ['USD', 'CAD'])) {
throw new Error(`Unsupported fiat: ${fiatCode}`)
}
let market = `${cryptoCode.toLowerCase()}_${fiatCode.toLowerCase()}`
if (fiatCode === 'USD' && cryptoCode !== 'BTC') {
throw new Error(`Unsupported market: ${market}`)
}
return market
}
function request (path, method, data) {
const options = {
method,
data,
url: API_ENDPOINT + path,
headers: {
'User-Agent': 'Mozilla/4.0 (compatible; Lamassu client)',
'Content-Type': 'application/json; charset=utf-8'
}
}
return axios(options)
.then(r => {
if (r.data.error) throw new Error(r.data.error.message)
return r.data
})
}
module.exports = {
buildMarket,
authRequest,
request
}

View file

@ -1,28 +0,0 @@
const common = require('../../common/quadrigacx')
const coinUtils = require('../../../coin-utils')
function buy (account, cryptoAtoms, fiatCode, cryptoCode) {
return trade('buy', account, cryptoAtoms, fiatCode, cryptoCode)
}
function sell (account, cryptoAtoms, fiatCode, cryptoCode) {
return trade('sell', account, cryptoAtoms, fiatCode, cryptoCode)
}
function trade (type, account, cryptoAtoms, fiatCode, cryptoCode) {
return Promise.resolve()
.then(() => {
const market = common.buildMarket(fiatCode, cryptoCode)
const options = {
book: market,
amount: coinUtils.toUnit(cryptoAtoms, cryptoCode).toFixed(8)
}
return common.authRequest(account, '/' + type, options)
})
}
module.exports = {
buy,
sell
}

View file

@ -2,9 +2,9 @@ const axios = require('axios')
const BN = require('../../../bn')
function ticker (account, fiatCode, cryptoCode) {
return axios.get('https://bitpay.com/api/rates/' + cryptoCode + '/' + fiatCode)
return axios.get('https://bitpay.com/rates/' + cryptoCode + '/' + fiatCode)
.then(r => {
const data = r.data
const data = r.data.data
const price = BN(data.rate.toString())
return {
rates: {

View file

@ -18,9 +18,9 @@ exports.ticker = function ticker (account, fiatCode, cryptoCode) {
return getCurrencyRates(fiatCode, cryptoCode)
}
return axios.get('https://bitpay.com/api/rates')
return axios.get('https://bitpay.com/rates')
.then(response => {
const fxRates = response.data
const fxRates = response.data.data
const usdRate = findCurrency(fxRates, 'USD')
const fxRate = findCurrency(fxRates, fiatCode).div(usdRate)

View file

@ -28,7 +28,7 @@ function getSellPrice (obj) {
function ticker (account, fiatCode, cryptoCode) {
return Promise.resolve()
.then(() => {
if (!_.includes(cryptoCode, ['BTC', 'ETH', 'LTC', 'BCH'])) {
if (!_.includes(cryptoCode, ['BTC', 'ETH', 'LTC', 'BCH', 'ZEC', 'DASH'])) {
throw new Error('Unsupported crypto: ' + cryptoCode)
}
})

View file

@ -1,49 +0,0 @@
const axios = require('axios')
const _ = require('lodash/fp')
const BN = require('../../../bn')
const common = require('../../common/quadrigacx')
exports.NAME = 'QuadrigaCX'
exports.SUPPORTED_MODULES = ['ticker']
function findCurrency (fxRates, fiatCode) {
const rates = _.find(_.matchesProperty('code', fiatCode), fxRates)
if (!rates || !rates.rate) throw new Error(`Unsupported currency: ${fiatCode}`)
return BN(rates.rate.toString())
}
exports.ticker = function ticker (account, fiatCode, cryptoCode) {
if (fiatCode === 'USD' && cryptoCode === 'BTC' || fiatCode === 'CAD') {
return getCurrencyRates(fiatCode, cryptoCode)
}
return axios.get('https://bitpay.com/api/rates')
.then(response => {
const fxRates = response.data
const cadRate = findCurrency(fxRates, 'CAD')
const fxRate = findCurrency(fxRates, fiatCode).div(cadRate)
return getCurrencyRates('CAD', cryptoCode)
.then(res => ({
rates: {
ask: res.rates.ask.times(fxRate),
bid: res.rates.bid.times(fxRate)
}
}))
})
}
function getCurrencyRates (fiatCode, cryptoCode) {
return Promise.resolve()
.then(() => {
const market = common.buildMarket(fiatCode, cryptoCode)
return common.request(`/ticker?book=${market}`, 'GET')
})
.then(r => ({
rates: {
ask: BN(r.ask),
bid: BN(r.bid)
}
}))
}

View file

@ -54,7 +54,7 @@ function sendCoins (account, toAddress, cryptoAtoms, cryptoCode) {
.then(tx => {
if (!tx) return { txid }
const fee = BN(tx.gas).multipliedBy(BN(tx.gasPrice)).round()
const fee = BN(tx.gas).mul(BN(tx.gasPrice)).round()
return { txid, fee }
})

View file

@ -62,7 +62,7 @@ function updateCoinAtmRadar () {
const config = settings().config
return pi().getRawRates()
.then(rates => coinAtmRadar.update({ rates, config }))
.then(rates => coinAtmRadar.update({ rates, config }, settings()))
}
function start (__settings) {

View file

@ -26,10 +26,16 @@ const argv = require('minimist')(process.argv.slice(2))
const CLOCK_SKEW = 60 * 1000
const REQUEST_TTL = 3 * 60 * 1000
const THROTTLE_LOGS_QUERY = 30 * 1000
const THROTTLE_CLOCK_SKEW = 60 * 1000
const SETTINGS_CACHE_REFRESH = 60 * 60 * 1000
const pids = {}
const reboots = {}
const restartServicesMap = {}
const canGetLastSeenMap = {}
const canLogClockSkewMap = {}
const settingsCache = {}
const devMode = argv.dev || options.http
@ -167,13 +173,6 @@ function stateChange (req, res, next) {
.catch(next)
}
function deviceEvent (req, res, next) {
const pi = plugins(req.settings, req.deviceId)
pi.logEvent(req.body)
.then(() => respond(req, res))
.catch(next)
}
function verifyUser (req, res, next) {
const pi = plugins(req.settings, req.deviceId)
pi.verifyUser(req.body)
@ -245,9 +244,19 @@ function updateCustomer (req, res, next) {
}
function getLastSeen (req, res, next) {
return logs.getLastSeen(req.deviceId)
.then(r => res.json(r))
.catch(next)
const deviceId = req.deviceId
const timestamp = Date.now()
const shouldTrigger = !canGetLastSeenMap[deviceId] ||
timestamp - canGetLastSeenMap[deviceId] >= THROTTLE_LOGS_QUERY
if (shouldTrigger) {
canGetLastSeenMap[deviceId] = timestamp
return logs.getLastSeen(deviceId)
.then(r => res.json(r))
.catch(next)
}
return res.status(408).json({})
}
function updateLogs (req, res, next) {
@ -310,9 +319,15 @@ function httpError (msg, code) {
function filterOldRequests (req, res, next) {
const deviceTime = req.deviceTime
const delta = Date.now() - Date.parse(deviceTime)
const deviceId = req.deviceId
const timestamp = Date.now()
const delta = timestamp - Date.parse(deviceTime)
if (delta > CLOCK_SKEW) {
const shouldTrigger = !canLogClockSkewMap[deviceId] ||
timestamp - canLogClockSkewMap[deviceId] >= THROTTLE_CLOCK_SKEW
if (delta > CLOCK_SKEW && shouldTrigger) {
canLogClockSkewMap[deviceId] = timestamp
logger.error('Clock skew with lamassu-machine[%s] too high [%ss], adjust lamassu-machine clock',
req.deviceName, (delta / 1000).toFixed(2))
}
@ -337,8 +352,7 @@ function authorize (req, res, next) {
.catch(next)
}
const skip = (req, res) => _.includes(req.path, ['/poll', '/state', '/logs']) &&
res.statusCode === 200
const skip = (req, res) => _.includes(req.path, ['/poll', '/state', '/logs']) && _.includes(res.statusCode, [200, 408])
const configRequiredRoutes = [
'/poll',
@ -367,7 +381,6 @@ app.use(filterOldRequests)
app.get('/poll', poll)
app.post('/state', stateChange)
app.post('/event', deviceEvent)
app.post('/verify_user', verifyUser)
app.post('/verify_transaction', verifyTx)
@ -416,6 +429,7 @@ localApp.post('/restartServices', (req, res) => {
})
localApp.post('/dbChange', (req, res, next) => {
settingsCache.cache = null
return settingsLoader.loadLatest()
.then(poller.reload)
.then(() => logger.info('Config reloaded'))
@ -452,9 +466,23 @@ function populateSettings (req, res, next) {
oldVersionId = versionId
}
if (!versionId) {
// Clear cache every hour
if (Date.now() - settingsCache.timestamp > SETTINGS_CACHE_REFRESH) {
settingsCache.cache = null
}
if (!versionId && settingsCache.cache) {
req.settings = settingsCache.cache
return next()
}
if (!versionId && !settingsCache.cache) {
return settingsLoader.loadLatest()
.then(settings => { req.settings = settings })
.then(settings => {
settingsCache.cache = settings
settingsCache.timestamp = Date.now()
req.settings = settings
})
.then(() => next())
.catch(next)
}

639
package-lock.json generated

File diff suppressed because it is too large Load diff

View file

@ -2,7 +2,7 @@
"name": "lamassu-server",
"description": "bitcoin atm client server protocol module",
"keywords": [],
"version": "7.3.2",
"version": "7.5.0-beta.0",
"license": "Unlicense",
"author": "Lamassu (https://lamassu.is)",
"dependencies": {

View file

@ -1,27 +0,0 @@
{
"code": "quadrigacx",
"display": "QuadrigaCX",
"fields": [
{
"code": "clientId",
"display": "Client ID",
"fieldType": "string",
"required": true,
"value": ""
},
{
"code": "key",
"display": "API key",
"fieldType": "string",
"required": true,
"value": ""
},
{
"code": "secret",
"display": "API secret",
"fieldType": "password",
"required": true,
"value": ""
}
]
}

View file

@ -8,7 +8,7 @@ const data = {
accounts: [
{code: 'mock-ticker', display: 'Mock ticker', class: 'ticker', cryptos: ALL_CRYPTOS},
{code: 'bitcoind', display: 'bitcoind', class: 'wallet', cryptos: ['BTC']},
{code: 'quadrigacx', display: 'QuadrigaCX', class: 'exchange', cryptos: ['BTC', 'ETH', 'LTC', 'BCH']},
{code: 'kraken', display: 'Kraken', class: 'exchange', cryptos: ['BTC', 'ZEC', 'ETH', 'LTC', 'BCH', 'DASH']},
{code: 'mock-wallet', display: 'Mock (Caution!)', class: 'wallet', cryptos: ALL_CRYPTOS}
]
}