add blockchain install scripts

This commit is contained in:
Josh Harvey 2017-07-01 16:02:46 +03:00
parent 0e9e27b97b
commit 178f576cfb
39 changed files with 3938 additions and 750 deletions

View file

@ -1,12 +1,44 @@
const _ = require('lodash/fp')
const BN = require('../bn')
const settingsLoader = require('../settings-loader')
const configManager = require('../config-manager')
const wallet = require('../wallet')
const ticker = require('../ticker')
const coinUtils = require('../coin-utils')
const machineLoader = require('../machine-loader')
module.exports = {getFunding}
function allScopes (cryptoScopes, machineScopes) {
const scopes = []
cryptoScopes.forEach(c => {
machineScopes.forEach(m => scopes.push([c, m]))
})
return scopes
}
function allMachineScopes (machineList, machineScope) {
const machineScopes = []
if (machineScope === 'global' || machineScope === 'both') machineScopes.push('global')
if (machineScope === 'specific' || machineScope === 'both') machineList.forEach(r => machineScopes.push(r))
return machineScopes
}
function getCryptos (config, machineList) {
const scopes = allScopes(['global'], allMachineScopes(machineList, 'both'))
const scoped = scope => configManager.scopedValue(scope[0], scope[1], 'cryptoCurrencies', config)
return _.uniq(_.flatten(_.map(scoped, scopes)))
}
function fetchMachines () {
return machineLoader.getMachines()
.then(machineList => machineList.map(r => r.deviceId))
}
function computeCrypto (cryptoCode, _balance) {
const unitScale = coinUtils.coins[cryptoCode].unitScale
@ -19,15 +51,17 @@ function computeFiat (rate, cryptoCode, _balance) {
return BN(_balance).shift(-unitScale).mul(rate).round(5)
}
function getFunding (cryptoCode) {
cryptoCode = cryptoCode || 'BTC'
const cryptoDisplays = coinUtils.cryptoDisplays
if (!coinUtils.coins[cryptoCode]) throw new Error(`Unsupported coin: ${cryptoCode}`)
return settingsLoader.loadLatest()
.then(settings => {
function getFunding (_cryptoCode) {
return Promise.all([settingsLoader.loadLatest(), fetchMachines()])
.then(([settings, machineList]) => {
const config = configManager.unscoped(settings.config)
const cryptoCodes = getCryptos(settings.config, machineList)
const cryptoCode = _cryptoCode || cryptoCodes[0]
const fiatCode = config.fiatCurrency
const pareCoins = c => _.includes(c.cryptoCode, cryptoCodes)
const cryptoDisplays = _.filter(pareCoins, coinUtils.cryptoDisplays)
if (!coinUtils.coins[cryptoCode]) throw new Error(`Unsupported coin: ${cryptoCode}`)
const promises = [
wallet.newFunding(settings, cryptoCode),

33
lib/blockchain/bitcoin.js Normal file
View file

@ -0,0 +1,33 @@
const fs = require('fs')
const path = require('path')
const coinUtils = require('../coin-utils')
const common = require('./common')
module.exports = {setup}
const es = common.es
function setup (dataDir) {
const coinRec = coinUtils.getCryptoCurrency('BTC')
common.firewall([coinRec.defaultPort])
const config = buildConfig()
fs.writeFileSync(path.resolve(dataDir, coinRec.configFile), config)
setupPm2(dataDir)
}
function buildConfig () {
return `rpcuser=lamassuserver
rpcpassword=${common.randomPass()}
dbcache=500
server=1
connections=40
keypool=10000
prune=4000
daemon=0`
}
function setupPm2 (dataDir) {
es(`pm2 start /usr/local/bin/bitcoind -- -datadir=${dataDir}`)
}

61
lib/blockchain/common.js Normal file
View file

@ -0,0 +1,61 @@
const crypto = require('crypto')
const os = require('os')
const path = require('path')
const cp = require('child_process')
const logger = require('console-log-level')({level: 'info'})
module.exports = {es, firewall, randomPass, fetchAndInstall, logger}
const BINARIES = {
BTC: {
url: 'https://bitcoin.org/bin/bitcoin-core-0.14.2/bitcoin-0.14.2-x86_64-linux-gnu.tar.gz',
dir: 'bitcoin-0.14.2/bin'
},
ETH: {
url: 'https://gethstore.blob.core.windows.net/builds/geth-linux-amd64-1.6.6-10a45cb5.tar.gz',
dir: 'geth-linux-amd64-1.6.6-10a45cb5'
},
ZEC: {
url: 'https://z.cash/downloads/zcash-1.0.10-1-linux64.tar.gz',
dir: 'zcash-1.0.10-1/bin'
},
DASH: {
url: 'https://www.dash.org/binaries/dashcore-0.12.1.5-linux64.tar.gz',
dir: 'dashcore-0.12.1/bin'
},
LTC: {
url: 'https://download.litecoin.org/litecoin-0.13.2/linux/litecoin-0.13.2-x86_64-linux-gnu.tar.gz',
dir: 'litecoin-0.13.2/bin'
}
}
function firewall (ports) {
if (!ports || ports.length === 0) throw new Error('No ports supplied')
const portsString = ports.join(',')
es(`sudo ufw allow ${portsString}`)
}
function randomPass () {
return crypto.randomBytes(32).toString('hex')
}
function es (cmd) {
const env = {HOME: os.userInfo().homedir}
const options = {encoding: 'utf8', env}
const res = cp.execSync(cmd, options)
logger.debug(res)
return res.toString()
}
function fetchAndInstall (crypto) {
const binaries = BINARIES[crypto.cryptoCode]
if (!binaries) throw new Error(`No such coin: ${crypto.code}`)
const url = binaries.url
const downloadFile = path.basename(url)
const binDir = binaries.dir
es(`wget -q ${url}`)
es(`tar -xzf ${downloadFile}`)
es(`sudo cp ${binDir}/* /usr/local/bin`)
}

28
lib/blockchain/dash.js Normal file
View file

@ -0,0 +1,28 @@
const fs = require('fs')
const path = require('path')
const coinUtils = require('../coin-utils')
const common = require('./common')
module.exports = {setup}
const es = common.es
function setup (dataDir) {
const coinRec = coinUtils.getCryptoCurrency('DASH')
common.firewall([coinRec.defaultPort])
const config = buildConfig()
fs.writeFileSync(path.resolve(dataDir, coinRec.configFile), config)
setupPm2(dataDir)
}
function buildConfig () {
return `rpcuser=lamassuserver
rpcpassword=${common.randomPass()}
dbcache=500`
}
function setupPm2 (dataDir) {
es(`pm2 start /usr/local/bin/dashd -- -datadir=${dataDir}`)
}

0
lib/blockchain/dashd.js Normal file
View file

View file

@ -0,0 +1,67 @@
const fs = require('fs')
const common = require('./common')
const MOUNT_POINT = '/mnt/blockchains'
module.exports = {prepareVolume}
const logger = common.logger
function isMounted () {
return fs.existsSync(MOUNT_POINT)
}
function isFormatted (volumePath) {
const res = common.es(`file --dereference -s ${volumePath}`)
return res !== `${volumePath}: data`
}
function formatVolume (volumePath) {
if (isFormatted(volumePath)) {
logger.info('Volume is already formatted.')
return
}
logger.info('Formatting...')
common.es(`sudo mkfs.ext4 ${volumePath}`)
}
function mountVolume (volumePath) {
if (isMounted()) {
logger.info('Volume is already mounted.')
return
}
logger.info('Mounting...')
common.es(`sudo mkdir -p ${MOUNT_POINT}`)
common.es(`sudo mount -o discard,defaults ${volumePath} ${MOUNT_POINT}`)
common.es(`echo ${volumePath} ${MOUNT_POINT} ext4 defaults,nofail,discard 0 0 | sudo tee -a /etc/fstab`)
}
function locateVolume () {
const res = common.es('ls /dev/disk/by-id/*')
const lines = res.trim().split('\n')
if (lines.length > 1) {
logger.error('More than one volume present, cannot prepare.')
return null
}
if (lines.length === 0) {
logger.error('No available volumes. You might need to attach one.')
return null
}
return lines[0].trim()
}
function prepareVolume () {
const volumePath = locateVolume()
if (!volumePath) return false
formatVolume(volumePath)
mountVolume(volumePath)
return true
}

View file

@ -0,0 +1,17 @@
const coinUtils = require('../coin-utils')
const common = require('./common')
module.exports = {setup}
const es = common.es
function setup (dataDir) {
const coinRec = coinUtils.getCryptoCurrency('ETH')
common.firewall([coinRec.defaultPort])
setupPm2(dataDir)
}
function setupPm2 (dataDir) {
es(`pm2 start /usr/local/bin/geth -- --datadir "${dataDir}" --cache 500`)
}

95
lib/blockchain/install.js Normal file
View file

@ -0,0 +1,95 @@
const fs = require('fs')
const path = require('path')
const process = require('process')
const makeDir = require('make-dir')
const inquirer = require('inquirer')
const _ = require('lodash/fp')
const options = require('../options')
const coinUtils = require('../coin-utils')
const common = require('./common')
const doVolume = require('./do-volume')
const cryptos = coinUtils.cryptoCurrencies()
const logger = common.logger
const PLUGINS = {
BTC: require('./bitcoin.js'),
LTC: require('./litecoin.js'),
ETH: require('./ethereum.js'),
DASH: require('./dash.js'),
ZEC: require('./zcash.js')
}
function installedFilePath (crypto) {
return path.resolve(options.blockchainDir, crypto.code, '.installed')
}
function isInstalled (crypto) {
return fs.existsSync(installedFilePath(crypto))
}
const choices = _.map(c => {
const checked = isInstalled(c)
return {
name: c.display,
value: c.code,
checked,
disabled: checked && 'Installed'
}
}, cryptos)
const questions = []
questions.push({
type: 'checkbox',
name: 'crypto',
message: 'Which cryptocurrencies would you like to install?',
choices
})
function processCryptos (codes) {
if (_.isEmpty(codes)) {
logger.info('No cryptos selected. Exiting.')
process.exit(0)
}
doVolume.prepareVolume()
logger.info('Thanks! Installing: %s. Will take a while...', _.join(', ', codes))
const selectedCryptos = _.map(code => _.find(['code', code], cryptos), codes)
_.forEach(setupCrypto, selectedCryptos)
common.es('pm2 save')
logger.info('Installation complete.')
}
inquirer.prompt(questions)
.then(answers => processCryptos(answers.crypto))
function plugin (crypto) {
const plugin = PLUGINS[crypto.cryptoCode]
if (!plugin) throw new Error(`No such plugin: ${crypto.cryptoCode}`)
return plugin
}
function setupCrypto (crypto) {
logger.info(`Installing ${crypto.display}...`)
const cryptoDir = path.resolve(options.blockchainDir, crypto.code)
makeDir.sync(cryptoDir)
const cryptoPlugin = plugin(crypto)
const oldDir = process.cwd()
const tmpDir = '/tmp/blockchain-install'
makeDir.sync(tmpDir)
process.chdir(tmpDir)
common.es('rm -rf *')
common.fetchAndInstall(crypto)
cryptoPlugin.setup(cryptoDir)
fs.writeFileSync(installedFilePath(crypto), '')
process.chdir(oldDir)
}

View file

@ -0,0 +1,33 @@
const fs = require('fs')
const path = require('path')
const coinUtils = require('../coin-utils')
const common = require('./common')
module.exports = {setup}
const es = common.es
function setup (dataDir) {
const coinRec = coinUtils.getCryptoCurrency('LTC')
common.firewall([coinRec.defaultPort])
const config = buildConfig()
fs.writeFileSync(path.resolve(dataDir, coinRec.configFile), config)
setupPm2(dataDir)
}
function buildConfig () {
return `rpcuser=lamassuserver
rpcpassword=${common.randomPass()}
dbcache=500
server=1
connections=40
keypool=10000
prune=4000
daemon=0`
}
function setupPm2 (dataDir) {
es(`pm2 start /usr/local/bin/litecoind -- -datadir=${dataDir}`)
}

37
lib/blockchain/zcash.js Normal file
View file

@ -0,0 +1,37 @@
const fs = require('fs')
const path = require('path')
const coinUtils = require('../coin-utils')
const common = require('./common')
module.exports = {setup}
const es = common.es
const logger = common.logger
function setup (dataDir) {
es('sudo apt-get update')
es('sudo apt-get install libgomp1 -y')
const coinRec = coinUtils.getCryptoCurrency('ZEC')
common.firewall([coinRec.defaultPort])
logger.info('Fetching Zcash proofs, will take a while...')
es('zcash-fetch-params 2>&1')
logger.info('Finished fetching proofs.')
const config = buildConfig()
fs.writeFileSync(path.resolve(dataDir, 'zcash.conf'), config)
setupPm2(dataDir)
}
function buildConfig () {
return `mainnet=1
addnode=mainnet.z.cash
rpcuser=lamassuserver
rpcpassword=${common.randomPass()}
dbcache=500`
}
function setupPm2 (dataDir) {
es(`pm2 start /usr/local/bin/zcashd -- -datadir=${dataDir}`)
}

0
lib/blockchain/zcashd.js Normal file
View file

View file

@ -1,22 +1,64 @@
const _ = require('lodash/fp')
const coins = {
BTC: {unitScale: 8},
ETH: {unitScale: 18},
ZEC: {unitScale: 8},
LTC: {unitScale: 8},
DASH: {unitScale: 8}
}
const cryptoDisplays = [
{cryptoCode: 'BTC', display: 'Bitcoin'},
{cryptoCode: 'ETH', display: 'Ethereum'},
{cryptoCode: 'ZEC', display: 'Zcash'},
{cryptoCode: 'LTC', display: 'Litecoin'},
{cryptoCode: 'DASH', display: 'Dash'}
const CRYPTO_CURRENCIES = [
{
cryptoCode: 'BTC',
display: 'Bitcoin',
code: 'bitcoin',
configFile: 'bitcoin.conf',
daemon: 'bitcoind',
defaultPort: 8332,
unitScale: 8
},
{
cryptoCode: 'ETH',
display: 'Ethereum',
code: 'ethereum',
configFile: 'geth.conf',
daemon: 'geth',
defaultPort: 8545,
unitScale: 18
},
{
cryptoCode: 'LTC',
display: 'Litecoin',
code: 'litecoin',
configFile: 'litecoin.conf',
daemon: 'litecoind',
defaultPort: 9332,
unitScale: 8
},
{
cryptoCode: 'DASH',
display: 'Dash',
code: 'dash',
configFile: 'dash.conf',
daemon: 'dashd',
defaultPort: 9998,
unitScale: 8
},
{
cryptoCode: 'ZEC',
display: 'Zcash',
code: 'zcash',
configFile: 'zcash.conf',
daemon: 'zcashd',
defaultPort: 8232,
unitScale: 8
}
]
module.exports = {coins, cryptoDisplays, buildUrl, unitScale, display}
module.exports = {buildUrl, cryptoCurrencies, getCryptoCurrency}
function getCryptoCurrency (cryptoCode) {
const cryptoCurrency = _.find(['cryptoCode', cryptoCode], CRYPTO_CURRENCIES)
if (!cryptoCurrency) throw new Error(`Unsupported crypto: ${cryptoCode}`)
return cryptoCurrency
}
function cryptoCurrencies () {
return CRYPTO_CURRENCIES
}
function buildUrl (cryptoCode, address) {
switch (cryptoCode) {
@ -28,15 +70,3 @@ function buildUrl (cryptoCode, address) {
default: throw new Error(`Unsupported crypto: ${cryptoCode}`)
}
}
function display (cryptoCode) {
const rec = _.find(['cryptoCode', cryptoCode], cryptoDisplays)
if (!rec) throw new Error(`Unsupported crypto: ${cryptoCode}`)
return rec.display
}
function unitScale (cryptoCode) {
const scaleRec = coins[cryptoCode]
if (!scaleRec) throw new Error(`Unsupported crypto: ${cryptoCode}`)
return scaleRec.unitScale
}

View file

@ -7,12 +7,12 @@ const argv = require('minimist')(process.argv.slice(2))
let serverConfig
try {
const homeConfigPath = path.resolve(os.homedir(), '.lamassu', 'lamassu.json')
serverConfig = JSON.parse(fs.readFileSync(homeConfigPath))
const globalConfigPath = path.resolve('/etc', 'lamassu', 'lamassu.json')
serverConfig = JSON.parse(fs.readFileSync(globalConfigPath))
} catch (_) {
try {
const globalConfigPath = path.resolve('/etc', 'lamassu', 'lamassu.json')
serverConfig = JSON.parse(fs.readFileSync(globalConfigPath))
const homeConfigPath = path.resolve(os.homedir(), '.lamassu', 'lamassu.json')
serverConfig = JSON.parse(fs.readFileSync(homeConfigPath))
} catch (_) {
console.error("Couldn't open lamassu.json config file.")
process.exit(1)

View file

@ -171,7 +171,9 @@ function plugins (settings, deviceId) {
.then(row => row.id)
}
function mapCoinSettings (coin) {
function mapCoinSettings (coinParams) {
const coin = coinParams[0]
const cryptoNetwork = coinParams[1]
const config = configManager.scoped(coin, deviceId, settings.config)
const minimumTx = BN(config.minimumTx)
const cashInFee = BN(config.cashInFee)
@ -180,7 +182,8 @@ function plugins (settings, deviceId) {
cryptoCode: coin,
display: coinUtils.display(coin),
minimumTx: BN.max(minimumTx, cashInFee),
cashInFee: cashInFee
cashInFee: cashInFee,
cryptoNetwork
}
}
@ -191,6 +194,7 @@ function plugins (settings, deviceId) {
const tickerPromises = cryptoCodes.map(c => ticker.getRates(settings, fiatCode, c))
const balancePromises = cryptoCodes.map(c => fiatBalance(fiatCode, c))
const testnetPromises = cryptoCodes.map(c => wallet.cryptoNetwork(settings, c))
const pingPromise = recordPing(serialNumber, deviceTime, deviceRec)
const currentConfigVersionPromise = fetchCurrentConfigVersion()
@ -198,20 +202,25 @@ function plugins (settings, deviceId) {
buildAvailableCassettes(),
pingPromise,
currentConfigVersionPromise
].concat(tickerPromises, balancePromises)
].concat(tickerPromises, balancePromises, testnetPromises)
return Promise.all(promises)
.then(arr => {
const cassettes = arr[0]
const configVersion = arr[2]
const tickers = arr.slice(3, cryptoCodes.length + 3)
const balances = arr.slice(cryptoCodes.length + 3)
const cryptoCodesCount = cryptoCodes.length
const tickers = arr.slice(3, cryptoCodesCount + 3)
const balances = arr.slice(cryptoCodesCount + 3, 2 * cryptoCodesCount + 3)
const testNets = arr.slice(2 * cryptoCodesCount + 3)
const coinParams = _.zip(cryptoCodes, testNets)
console.log('DEBUG200: %j', cryptoCodes)
console.log('DEBUG201: %j', coinParams)
return {
cassettes,
rates: buildRates(tickers),
balances: buildBalances(balances),
coins: _.map(mapCoinSettings, cryptoCodes),
coins: _.map(mapCoinSettings, coinParams),
configVersion
}
})

View file

@ -23,18 +23,27 @@ function fetch (account, method, params) {
return r.data.result
})
.catch(err => {
console.log(err.response.data.error)
console.log(err.message)
try {
console.log(err.response.data.error)
} catch (__) {}
throw err
})
}
function split (str) {
const i = str.indexOf('=')
if (i === -1) return []
return [str.slice(0, i), str.slice(i + 1)]
}
function parseConf (confPath) {
const conf = fs.readFileSync(confPath)
const lines = conf.toString().split('\n', 2)
const lines = conf.toString().split('\n')
const res = {}
for (let i = 0; i < lines.length; i++) {
const keyVal = lines[i].split('=')
const keyVal = split(lines[i])
// skip when value is empty
if (!keyVal[1]) continue

View file

@ -39,11 +39,10 @@ function balance (account, cryptoCode) {
}
function sendCoins (account, address, cryptoAtoms, cryptoCode) {
const confirmations = 1
const bitcoins = cryptoAtoms.shift(-SATOSHI_SHIFT).toFixed(8)
const coins = cryptoAtoms.shift(-SATOSHI_SHIFT).toFixed(8)
return checkCryptoCode(cryptoCode)
.then(() => fetch('sendfrom', [address, bitcoins, confirmations]))
.then(() => fetch('sendtoaddress', [address, coins]))
.catch(err => {
if (err.code === -6) throw new E.InsufficientFundsError()
throw err
@ -103,10 +102,16 @@ function newFunding (account, cryptoCode) {
}))
}
function cryptoNetwork (account, cryptoCode) {
return checkCryptoCode(cryptoCode)
.then(() => parseInt(rpcConfig.port, 10) === 18332 ? 'test' : 'main')
}
module.exports = {
balance,
sendCoins,
newAddress,
getStatus,
newFunding
newFunding,
cryptoNetwork
}

View file

@ -103,11 +103,17 @@ function newFunding (account, cryptoCode) {
})
}
function cryptoNetwork (account, cryptoCode) {
return checkCryptoCode(cryptoCode)
.then(() => account.environment === 'test' ? 'test' : 'main')
}
module.exports = {
NAME,
balance,
sendCoins,
newAddress,
getStatus,
newFunding
newFunding,
cryptoNetwork
}

View file

@ -22,7 +22,8 @@ module.exports = {
sweep,
defaultAddress,
supportsHd: true,
newFunding
newFunding,
privateKey
}
if (!web3.isConnected()) {
@ -31,6 +32,10 @@ if (!web3.isConnected()) {
const hex = bigNum => '0x' + bigNum.truncated().toString(16)
function privateKey (account) {
return defaultWallet(account).getPrivateKey()
}
function sendCoins (account, toAddress, cryptoAtoms, cryptoCode) {
return generateTx(toAddress, defaultWallet(account), cryptoAtoms, false)
.then(_.tap(r => console.log('DEBUG113: %s', r)))

View file

@ -18,6 +18,9 @@ const rpcConfig = {
port: config.rpcport || DEFAULT_PORT
}
console.log('DEBUG101: %j', configPath)
console.log('DEBUG100: %j', rpcConfig)
function fetch (method, params) {
return jsonRpc.fetch(rpcConfig, method, params)
}
@ -40,11 +43,10 @@ function balance (account, cryptoCode) {
}
function sendCoins (account, address, cryptoAtoms, cryptoCode) {
const confirmations = 1
const bitcoins = cryptoAtoms.shift(-SATOSHI_SHIFT).toFixed(8)
const coins = cryptoAtoms.shift(-SATOSHI_SHIFT).toFixed(8)
return checkCryptoCode(cryptoCode)
.then(() => fetch('sendfrom', [address, bitcoins, confirmations]))
.then(() => fetch('sendtoaddress', [address, coins]))
.catch(err => {
if (err.code === -6) throw new E.InsufficientFundsError()
throw err

View file

@ -40,11 +40,10 @@ function balance (account, cryptoCode) {
}
function sendCoins (account, address, cryptoAtoms, cryptoCode) {
const confirmations = 1
const bitcoins = cryptoAtoms.shift(-SATOSHI_SHIFT).toFixed(8)
const coins = cryptoAtoms.shift(-SATOSHI_SHIFT).toFixed(8)
return checkCryptoCode(cryptoCode)
.then(() => fetch('sendfrom', [address, bitcoins, confirmations]))
.then(() => fetch('sendtoaddress', [address, coins]))
.catch(err => {
if (err.code === -6) throw new E.InsufficientFundsError()
throw err

View file

@ -139,6 +139,15 @@ function isHd (settings, cryptoCode) {
.then(r => r.wallet.supportsHd)
}
function cryptoNetwork (settings, cryptoCode) {
const plugin = configManager.cryptoScoped(cryptoCode, settings.config).wallet
const wallet = ph.load(ph.WALLET, plugin)
const account = settings.accounts[plugin]
if (!wallet.cryptoNetwork) return Promise.resolve(false)
return wallet.cryptoNetwork(account, cryptoCode)
}
module.exports = {
balance: mem(balance, {maxAge: FETCH_INTERVAL}),
sendCoins,
@ -146,5 +155,6 @@ module.exports = {
getStatus,
sweep,
isHd,
newFunding
newFunding,
cryptoNetwork
}