Merge branch 'dev' into feat/lam-1291/stress-testing

* dev: (85 commits)
  chore: console.log debug leftovers
  fix: third level navigation links
  fix: show subheader on refresh
  fix: machines/:id routing
  fix: customer route
  chore: update wallet nodes
  feat: shorten long addresses in funding page
  feat: shorten long addresses
  refactor: support copied text different from presented text
  chore: udpate react, downshift and routing
  refactor: use Wizard component on first route
  fix: autocomplete component rendering
  feat: skip2fa option on .env
  fix: drop contraint before dropping index
  chore: stop using alias imports
  fix: re-instate urlResolver
  chore: server code formatting
  chore: reformat code
  chore: adding eslint and prettier config
  chore: typo
  ...
This commit is contained in:
siiky 2025-05-20 11:57:32 +01:00
commit e10493abc6
1398 changed files with 60329 additions and 157527 deletions

View file

@ -0,0 +1,149 @@
const path = require('path')
const { utils: coinUtils } = require('@lamassu/coins')
const common = require('./common')
const { isDevMode, isRemoteNode } = require('../environment-helper')
module.exports = { setup, updateCore }
const coinRec = coinUtils.getCryptoCurrency('BTC')
const BLOCKCHAIN_DIR = process.env.BLOCKCHAIN_DIR
const tmpDir = isDevMode() ? path.resolve(BLOCKCHAIN_DIR, 'tmp') : '/tmp'
const usrBinDir = isDevMode()
? path.resolve(BLOCKCHAIN_DIR, 'bin')
: '/usr/local/bin'
function setup(dataDir) {
!isDevMode() && common.firewall([coinRec.defaultPort])
const config = buildConfig()
common.writeFile(path.resolve(dataDir, coinRec.configFile), config)
const cmd = `${usrBinDir}/${coinRec.daemon} -datadir=${dataDir}`
!isDevMode() && common.writeSupervisorConfig(coinRec, cmd)
}
function updateCore(coinRec, isCurrentlyRunning) {
common.logger.info('Updating Bitcoin Core. This may take a minute...')
!isDevMode() && common.es(`sudo supervisorctl stop bitcoin`)
common.es(`curl -#o /tmp/bitcoin.tar.gz ${coinRec.url}`)
if (
common.es(`sha256sum /tmp/bitcoin.tar.gz | awk '{print $1}'`).trim() !==
coinRec.urlHash
) {
common.logger.info(
'Failed to update Bitcoin Core: Package signature do not match!',
)
return
}
common.es(`tar -xzf /tmp/bitcoin.tar.gz -C /tmp/`)
common.logger.info('Updating wallet...')
common.es(`cp ${tmpDir}/${coinRec.dir}/* ${usrBinDir}/`)
common.es(`rm -r ${tmpDir}/${coinRec.dir.replace('/bin', '')}`)
common.es(`rm ${tmpDir}/bitcoin.tar.gz`)
if (
common.es(
`grep "addresstype=p2sh-segwit" ${BLOCKCHAIN_DIR}/bitcoin/bitcoin.conf || true`,
)
) {
common.logger.info(`Enabling bech32 receiving addresses in config file..`)
common.es(
`sed -i 's/addresstype=p2sh-segwit/addresstype=bech32/g' ${BLOCKCHAIN_DIR}/bitcoin/bitcoin.conf`,
)
} else {
common.logger.info(
`bech32 receiving addresses already defined, skipping...`,
)
}
if (
common.es(
`grep "changetype=" ${BLOCKCHAIN_DIR}/bitcoin/bitcoin.conf || true`,
)
) {
common.logger.info(`changetype already defined, skipping...`)
} else {
common.logger.info(`Enabling bech32 change addresses in config file..`)
common.es(
`echo "\nchangetype=bech32" >> ${BLOCKCHAIN_DIR}/bitcoin/bitcoin.conf`,
)
}
if (
common.es(
`grep "listenonion=" ${BLOCKCHAIN_DIR}/bitcoin/bitcoin.conf || true`,
)
) {
common.logger.info(`listenonion already defined, skipping...`)
} else {
common.logger.info(`Setting 'listenonion=0' in config file...`)
common.es(
`echo "\nlistenonion=0" >> ${BLOCKCHAIN_DIR}/bitcoin/bitcoin.conf`,
)
}
if (
common.es(
`grep "fallbackfee=" ${BLOCKCHAIN_DIR}/bitcoin/bitcoin.conf || true`,
)
) {
common.logger.info(`fallbackfee already defined, skipping...`)
} else {
common.logger.info(`Setting 'fallbackfee=0.00005' in config file...`)
common.es(
`echo "\nfallbackfee=0.00005" >> ${BLOCKCHAIN_DIR}/bitcoin/bitcoin.conf`,
)
}
if (
common.es(
`grep "rpcworkqueue=" ${BLOCKCHAIN_DIR}/bitcoin/bitcoin.conf || true`,
)
) {
common.logger.info(`rpcworkqueue already defined, skipping...`)
} else {
common.logger.info(`Setting 'rpcworkqueue=2000' in config file...`)
common.es(
`echo "\nrpcworkqueue=2000" >> ${BLOCKCHAIN_DIR}/bitcoin/bitcoin.conf`,
)
}
if (isCurrentlyRunning && !isDevMode()) {
common.logger.info('Starting wallet...')
common.es(`sudo supervisorctl start bitcoin`)
}
common.logger.info('Bitcoin Core is updated!')
}
function buildConfig() {
return `rpcuser=lamassuserver
rpcpassword=${common.randomPass()}
${isDevMode() ? `regtest=1` : ``}
dbcache=500
server=1
connections=40
keypool=10000
prune=4000
daemon=0
addresstype=bech32
changetype=bech32
walletrbf=1
listenonion=0
fallbackfee=0.00005
rpcworkqueue=2000
${
isDevMode()
? `[regtest]
rpcport=18333
bind=0.0.0.0:18332
${isRemoteNode(coinRec) ? `connect=${process.env.BTC_NODE_HOST}:${process.env.BTC_NODE_PORT}` : ``}`
: `rpcport=8333
bind=0.0.0.0:8332
${isRemoteNode(coinRec) ? `connect=${process.env.BTC_NODE_HOST}:${process.env.BTC_NODE_PORT}` : ``}`
}
`
}

View file

@ -0,0 +1,74 @@
const path = require('path')
const { utils: coinUtils } = require('@lamassu/coins')
const common = require('./common')
module.exports = { setup, updateCore }
const coinRec = coinUtils.getCryptoCurrency('BCH')
function setup(dataDir) {
common.firewall([coinRec.defaultPort])
const config = buildConfig()
common.writeFile(path.resolve(dataDir, coinRec.configFile), config)
const cmd = `/usr/local/bin/${coinRec.daemon} -datadir=${dataDir} -conf=${dataDir}/bitcoincash.conf`
common.writeSupervisorConfig(coinRec, cmd)
}
function updateCore(coinRec, isCurrentlyRunning) {
common.logger.info('Updating Bitcoin Cash. This may take a minute...')
common.es(`sudo supervisorctl stop bitcoincash`)
common.es(`curl -#Lo /tmp/bitcoincash.tar.gz ${coinRec.url}`)
if (
common.es(`sha256sum /tmp/bitcoincash.tar.gz | awk '{print $1}'`).trim() !==
coinRec.urlHash
) {
common.logger.info(
'Failed to update Bitcoin Cash: Package signature do not match!',
)
return
}
common.es(`tar -xzf /tmp/bitcoincash.tar.gz -C /tmp/`)
common.logger.info('Updating wallet...')
common.es(`cp /tmp/${coinRec.dir}/bitcoind /usr/local/bin/bitcoincashd`)
common.es(`cp /tmp/${coinRec.dir}/bitcoin-cli /usr/local/bin/bitcoincash-cli`)
common.es(`rm -r /tmp/${coinRec.dir.replace('/bin', '')}`)
common.es(`rm /tmp/bitcoincash.tar.gz`)
if (
common.es(
`grep "listenonion=" /mnt/blockchains/bitcoincash/bitcoincash.conf || true`,
)
) {
common.logger.info(`listenonion already defined, skipping...`)
} else {
common.logger.info(`Setting 'listenonion=0' in config file...`)
common.es(
`echo "\nlistenonion=0" >> /mnt/blockchains/bitcoincash/bitcoincash.conf`,
)
}
if (isCurrentlyRunning) {
common.logger.info('Starting wallet...')
common.es(`sudo supervisorctl start bitcoincash`)
}
common.logger.info('Bitcoin Cash is updated!')
}
function buildConfig() {
return `rpcuser=lamassuserver
rpcpassword=${common.randomPass()}
dbcache=500
server=1
maxconnections=40
keypool=10000
prune=4000
daemon=0
bind=0.0.0.0:8335
rpcport=8336
listenonion=0
`
}

View file

@ -0,0 +1,222 @@
const crypto = require('crypto')
const os = require('os')
const path = require('path')
const cp = require('child_process')
const fs = require('fs')
const makeDir = require('make-dir')
const _ = require('lodash/fp')
const logger = require('console-log-level')({ level: 'info' })
const { isDevMode } = require('../environment-helper')
const BLOCKCHAIN_DIR = process.env.BLOCKCHAIN_DIR
module.exports = {
es,
writeSupervisorConfig,
firewall,
randomPass,
fetchAndInstall,
logger,
isInstalledSoftware,
writeFile,
getBinaries,
isUpdateDependent,
}
const BINARIES = {
BTC: {
defaultUrl:
'https://bitcoincore.org/bin/bitcoin-core-0.20.1/bitcoin-0.20.1-x86_64-linux-gnu.tar.gz',
defaultUrlHash:
'376194f06596ecfa40331167c39bc70c355f960280bd2a645fdbf18f66527397',
defaultDir: 'bitcoin-0.20.1/bin',
url: 'https://bitcoincore.org/bin/bitcoin-core-29.0/bitcoin-29.0-x86_64-linux-gnu.tar.gz',
dir: 'bitcoin-29.0/bin',
urlHash: 'a681e4f6ce524c338a105f214613605bac6c33d58c31dc5135bbc02bc458bb6c',
},
ETH: {
url: 'https://gethstore.blob.core.windows.net/builds/geth-linux-amd64-1.15.11-36b2371c.tar.gz',
dir: 'geth-linux-amd64-1.15.11-36b2371c',
urlHash: 'a14a4285daedf75ea04a7a298e6caa48d566a2786c93fc5e86ec2c5998c92455',
},
ZEC: {
url: 'https://download.z.cash/downloads/zcash-6.2.0-linux64-debian-bullseye.tar.gz',
dir: 'zcash-6.2.0/bin',
urlHash: '71cf378c27582a4b9f9d57cafc2b5a57a46e9e52a5eda33be112dc9790c64c6f',
},
DASH: {
defaultUrl:
'https://github.com/dashpay/dash/releases/download/v18.1.0/dashcore-18.1.0-x86_64-linux-gnu.tar.gz',
defaultUrlHash:
'd89c2afd78183f3ee815adcccdff02098be0c982633889e7b1e9c9656fbef219',
defaultDir: 'dashcore-18.1.0/bin',
url: 'https://github.com/dashpay/dash/releases/download/v22.1.2/dashcore-22.1.2-x86_64-linux-gnu.tar.gz',
dir: 'dashcore-22.1.2/bin',
urlHash: '230e871ef55c64c1550f358089a324a1e47e52a9a9c032366162cd82a19fa1af',
},
LTC: {
defaultUrl:
'https://download.litecoin.org/litecoin-0.18.1/linux/litecoin-0.18.1-x86_64-linux-gnu.tar.gz',
defaultUrlHash:
'ca50936299e2c5a66b954c266dcaaeef9e91b2f5307069b9894048acf3eb5751',
defaultDir: 'litecoin-0.18.1/bin',
url: 'https://download.litecoin.org/litecoin-0.21.4/linux/litecoin-0.21.4-x86_64-linux-gnu.tar.gz',
dir: 'litecoin-0.21.4/bin',
urlHash: '857fc41091f2bae65c3bf0fd4d388fca915fc93a03f16dd2578ac3cc92898390',
},
BCH: {
url: 'https://github.com/bitcoin-cash-node/bitcoin-cash-node/releases/download/v28.0.1/bitcoin-cash-node-28.0.1-x86_64-linux-gnu.tar.gz',
dir: 'bitcoin-cash-node-28.0.1/bin',
files: [
['bitcoind', 'bitcoincashd'],
['bitcoin-cli', 'bitcoincash-cli'],
],
urlHash: 'd69ee632147f886ca540cecdff5b1b85512612b4c005e86b09083a63c35b64fa',
},
XMR: {
url: 'https://downloads.getmonero.org/cli/monero-linux-x64-v0.18.4.0.tar.bz2',
dir: 'monero-x86_64-linux-gnu-v0.18.4.0',
files: [
['monerod', 'monerod'],
['monero-wallet-rpc', 'monero-wallet-rpc'],
],
urlHash: '16cb74c899922887827845a41d37c7f3121462792a540843f2fcabcc1603993f',
},
}
const coinsUpdateDependent = ['BTC', 'LTC', 'DASH']
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 generateSupervisorConfig(cryptoCode, command, isWallet = false) {
return `[program:${cryptoCode}${isWallet ? `-wallet` : ``}]
command=nice ${command}
autostart=true
autorestart=true
stderr_logfile=/var/log/supervisor/${cryptoCode}${isWallet ? `-wallet` : ``}.err.log
stdout_logfile=/var/log/supervisor/${cryptoCode}${isWallet ? `-wallet` : ``}.out.log
stderr_logfile_backups=2
stdout_logfile_backups=2
environment=HOME="/root"
`
}
function writeSupervisorConfig(coinRec, cmd, walletCmd = '') {
if (isInstalledSoftware(coinRec)) return
const blockchain = coinRec.code
if (!_.isNil(coinRec.wallet)) {
const supervisorConfigWallet = generateSupervisorConfig(
blockchain,
walletCmd,
true,
)
writeFile(
`/etc/supervisor/conf.d/${coinRec.code}-wallet.conf`,
supervisorConfigWallet,
)
}
const supervisorConfig = generateSupervisorConfig(blockchain, cmd)
writeFile(`/etc/supervisor/conf.d/${coinRec.code}.conf`, supervisorConfig)
}
function isInstalledSoftware(coinRec) {
if (isDevMode()) {
return (
fs.existsSync(
`${BLOCKCHAIN_DIR}/${coinRec.code}/${coinRec.configFile}`,
) && fs.existsSync(`${BLOCKCHAIN_DIR}/bin/${coinRec.daemon}`)
)
}
const nodeInstalled = fs.existsSync(
`/etc/supervisor/conf.d/${coinRec.code}.conf`,
)
const walletInstalled = _.isNil(coinRec.wallet)
? true
: fs.existsSync(`/etc/supervisor/conf.d/${coinRec.code}.wallet.conf`)
return nodeInstalled && walletInstalled
}
function fetchAndInstall(coinRec) {
const requiresUpdate = isUpdateDependent(coinRec.cryptoCode)
if (isInstalledSoftware(coinRec)) return
const binaries = BINARIES[coinRec.cryptoCode]
if (!binaries) throw new Error(`No such coin: ${coinRec.code}`)
const url = requiresUpdate ? binaries.defaultUrl : binaries.url
const hash = requiresUpdate ? binaries.defaultUrlHash : binaries.urlHash
const downloadFile = path.basename(url)
const binDir = requiresUpdate ? binaries.defaultDir : binaries.dir
es(`wget -q ${url}`)
if (es(`sha256sum ${downloadFile} | awk '{print $1}'`).trim() !== hash) {
logger.info(
`Failed to install ${coinRec.code}: Package signature do not match!`,
)
return
}
es(`tar -xf ${downloadFile}`)
const usrBinDir = isDevMode()
? path.resolve(BLOCKCHAIN_DIR, 'bin')
: '/usr/local/bin'
if (isDevMode()) {
makeDir.sync(usrBinDir)
}
if (_.isEmpty(binaries.files)) {
es(`sudo cp ${binDir}/* ${usrBinDir}`)
return
}
_.forEach(([source, target]) => {
es(`sudo cp ${binDir}/${source} ${usrBinDir}/${target}`)
}, binaries.files)
}
function writeFile(path, content) {
try {
fs.writeFileSync(path, content)
} catch (err) {
if (err.code === 'EEXIST') {
logger.info(`${path} exists, skipping.`)
return
}
throw err
}
}
function getBinaries(coinCode) {
const binaries = BINARIES[coinCode]
if (!binaries) throw new Error(`No such coin: ${coinCode}`)
return binaries
}
function isUpdateDependent(coinCode) {
return _.includes(coinCode, coinsUpdateDependent)
}

View file

@ -0,0 +1,105 @@
const path = require('path')
const { utils: coinUtils } = require('@lamassu/coins')
const common = require('./common')
module.exports = { setup, updateCore }
const coinRec = coinUtils.getCryptoCurrency('DASH')
function setup(dataDir) {
common.firewall([coinRec.defaultPort])
const config = buildConfig()
common.writeFile(path.resolve(dataDir, coinRec.configFile), config)
const cmd = `/usr/local/bin/${coinRec.daemon} -datadir=${dataDir}`
common.writeSupervisorConfig(coinRec, cmd)
}
function updateCore(coinRec, isCurrentlyRunning) {
common.logger.info('Updating Dash Core. This may take a minute...')
common.es(`sudo supervisorctl stop dash`)
common.es(`curl -#Lo /tmp/dash.tar.gz ${coinRec.url}`)
if (
common.es(`sha256sum /tmp/dash.tar.gz | awk '{print $1}'`).trim() !==
coinRec.urlHash
) {
common.logger.info(
'Failed to update Dash Core: Package signature do not match!',
)
return
}
common.es(`tar -xzf /tmp/dash.tar.gz -C /tmp/`)
common.logger.info('Updating wallet...')
common.es(`cp /tmp/${coinRec.dir}/* /usr/local/bin/`)
common.es(`rm -r /tmp/${coinRec.dir.replace('/bin', '')}`)
common.es(`rm /tmp/dash.tar.gz`)
if (
common.es(
`grep "enableprivatesend=" /mnt/blockchains/dash/dash.conf || true`,
)
) {
common.logger.info(`Switching from 'PrivateSend' to 'CoinJoin'...`)
common.es(
`sed -i 's/enableprivatesend/enablecoinjoin/g' /mnt/blockchains/dash/dash.conf`,
)
} else if (
common.es(`grep "enablecoinjoin=" /mnt/blockchains/dash/dash.conf || true`)
) {
common.logger.info(`enablecoinjoin already defined, skipping...`)
} else {
common.logger.info(`Enabling CoinJoin in config file...`)
common.es(`echo "\nenablecoinjoin=1" >> /mnt/blockchains/dash/dash.conf`)
}
if (
common.es(
`grep "privatesendautostart=" /mnt/blockchains/dash/dash.conf || true`,
)
) {
common.logger.info(`Switching from 'PrivateSend' to 'CoinJoin'...`)
common.es(
`sed -i 's/privatesendautostart/coinjoinautostart/g' /mnt/blockchains/dash/dash.conf`,
)
} else if (
common.es(
`grep "coinjoinautostart=" /mnt/blockchains/dash/dash.conf || true`,
)
) {
common.logger.info(`coinjoinautostart already defined, skipping...`)
} else {
common.logger.info(`Enabling CoinJoin AutoStart in config file...`)
common.es(`echo "\ncoinjoinautostart=1" >> /mnt/blockchains/dash/dash.conf`)
}
if (common.es(`grep "litemode=" /mnt/blockchains/dash/dash.conf || true`)) {
common.logger.info(`Switching from 'LiteMode' to 'DisableGovernance'...`)
common.es(
`sed -i 's/litemode/disablegovernance/g' /mnt/blockchains/dash/dash.conf`,
)
} else {
common.es(`echo "\ndisablegovernance already defined, skipping..."`)
}
if (isCurrentlyRunning) {
common.logger.info('Starting wallet...')
common.es(`sudo supervisorctl start dash`)
}
common.logger.info('Dash Core is updated!')
}
function buildConfig() {
return `rpcuser=lamassuserver
rpcpassword=${common.randomPass()}
dbcache=500
keypool=10000
disablegovernance=1
prune=4000
txindex=0
enablecoinjoin=1
coinjoinautostart=1
`
}

View file

@ -0,0 +1,76 @@
const fs = require('fs')
const common = require('./common')
const BLOCKCHAIN_DIR = process.env.BLOCKCHAIN_DIR
const MOUNT_POINT = BLOCKCHAIN_DIR
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}`).trim()
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() {
if (isMounted()) {
logger.info('Volume is already mounted.')
return true
}
const volumePath = locateVolume()
if (!volumePath) return false
formatVolume(volumePath)
mountVolume(volumePath)
return true
}

View file

@ -0,0 +1,40 @@
const { utils: coinUtils } = require('@lamassu/coins')
const common = require('./common')
module.exports = { setup, updateCore }
function updateCore(coinRec, isCurrentlyRunning) {
common.logger.info(
'Updating the Geth Ethereum wallet. This may take a minute...',
)
common.es(`sudo supervisorctl stop ethereum`)
common.es(`curl -#o /tmp/ethereum.tar.gz ${coinRec.url}`)
if (
common.es(`sha256sum /tmp/ethereum.tar.gz | awk '{print $1}'`).trim() !==
coinRec.urlHash
) {
common.logger.info('Failed to update Geth: Package signature do not match!')
return
}
common.es(`tar -xzf /tmp/ethereum.tar.gz -C /tmp/`)
common.logger.info('Updating wallet...')
common.es(`cp /tmp/${coinRec.dir}/geth /usr/local/bin/geth`)
common.es(`rm -r /tmp/${coinRec.dir}`)
common.es(`rm /tmp/ethereum.tar.gz`)
if (isCurrentlyRunning) {
common.logger.info('Starting wallet...')
common.es(`sudo supervisorctl start ethereum`)
}
common.logger.info('Geth is updated!')
}
function setup(dataDir) {
const coinRec = coinUtils.getCryptoCurrency('ETH')
common.firewall([coinRec.defaultPort])
const cmd = `/usr/local/bin/${coinRec.daemon} --datadir "${dataDir}" --syncmode="light" --cache 2048 --maxpeers 40 --http`
common.writeSupervisorConfig(coinRec, cmd)
}

View file

@ -0,0 +1,336 @@
const fs = require('fs')
const path = require('path')
const process = require('process')
const os = require('os')
const makeDir = require('make-dir')
const inquirer = require('inquirer')
const _ = require('lodash/fp')
const { utils: coinUtils } = require('@lamassu/coins')
const settingsLoader = require('../new-settings-loader')
const wallet = require('../wallet')
const {
isDevMode,
isRemoteNode,
isRemoteWallet,
} = require('../environment-helper')
const common = require('./common')
const doVolume = require('./do-volume')
const cryptos = coinUtils.cryptoCurrencies()
const logger = common.logger
const PLUGINS = {
BTC: require('./bitcoin.js'),
BCH: require('./bitcoincash.js'),
DASH: require('./dash.js'),
LTC: require('./litecoin.js'),
XMR: require('./monero.js'),
}
const BLOCKCHAIN_DIR = process.env.BLOCKCHAIN_DIR
module.exports = {
isEnvironmentValid,
run,
}
function installedVolumeFilePath(crypto) {
return path.resolve(coinUtils.cryptoDir(crypto, BLOCKCHAIN_DIR), '.installed')
}
function isInstalledVolume(crypto) {
return fs.existsSync(installedVolumeFilePath(crypto))
}
function isInstalledSoftware(crypto) {
return common.isInstalledSoftware(crypto)
}
function processCryptos(codes) {
if (_.isEmpty(codes)) {
logger.info('No cryptos selected. Exiting.')
process.exit(0)
}
logger.info(
'Thanks! Installing: %s. Will take a while...',
_.join(', ', codes),
)
const selectedCryptos = _.map(code => _.find(['code', code], cryptos), codes)
if (isDevMode()) {
_.forEach(setupCrypto, selectedCryptos)
} else {
const goodVolume = doVolume.prepareVolume()
if (!goodVolume) {
logger.error('There was an error preparing the disk volume. Exiting.')
process.exit(1)
}
_.forEach(setupCrypto, selectedCryptos)
common.es('sudo supervisorctl reread')
common.es('sudo supervisorctl update')
const blockchainDir = BLOCKCHAIN_DIR
const backupDir = path.resolve(os.homedir(), 'backups')
const rsyncCmd = `( \
(crontab -l 2>/dev/null || echo -n "") | grep -v "@daily rsync ".*"wallet.dat"; \
echo "@daily rsync -r --prune-empty-dirs --include='*/' \
--include='wallet.dat' \
--exclude='*' ${blockchainDir} ${backupDir} > /dev/null" \
) | crontab -`
common.es(rsyncCmd)
_.forEach(c => {
updateCrypto(c)
common.es(`sudo supervisorctl start ${c.code}`)
}, selectedCryptos)
}
logger.info('Installation complete.')
}
function isEnvironmentValid(crypto) {
if (_.isEmpty(process.env[`${crypto.cryptoCode}_NODE_LOCATION`]))
throw new Error(
`The environment variable for ${crypto.cryptoCode}_NODE_LOCATION is not set!`,
)
if (_.isEmpty(process.env[`${crypto.cryptoCode}_WALLET_LOCATION`]))
throw new Error(
`The environment variable for ${crypto.cryptoCode}_WALLET_LOCATION is not set!`,
)
if (isRemoteWallet(crypto) && !isRemoteNode(crypto))
throw new Error(
`Invalid environment setup for ${crypto.display}: It's not possible to use a remote wallet without using a remote node!`,
)
if (isRemoteNode(crypto) && !isRemoteWallet(crypto)) {
if (_.isEmpty(process.env[`${crypto.cryptoCode}_NODE_HOST`]))
throw new Error(
`The environment variable for ${crypto.cryptoCode}_NODE_HOST is not set!`,
)
if (_.isEmpty(process.env[`${crypto.cryptoCode}_NODE_PORT`]))
throw new Error(
`The environment variable for ${crypto.cryptoCode}_NODE_PORT is not set!`,
)
if (_.isEmpty(process.env.BLOCKCHAIN_DIR))
throw new Error(`The environment variable for BLOCKCHAIN_DIR is not set!`)
}
if (isRemoteWallet(crypto)) {
if (_.isEmpty(process.env[`${crypto.cryptoCode}_NODE_RPC_HOST`]))
throw new Error(
`The environment variable for ${crypto.cryptoCode}_NODE_RPC_HOST is not set!`,
)
if (_.isEmpty(process.env[`${crypto.cryptoCode}_NODE_RPC_PORT`]))
throw new Error(
`The environment variable for ${crypto.cryptoCode}_NODE_RPC_PORT is not set!`,
)
if (_.isEmpty(process.env[`${crypto.cryptoCode}_NODE_USER`]))
throw new Error(
`The environment variable for ${crypto.cryptoCode}_NODE_USER is not set!`,
)
if (_.isEmpty(process.env[`${crypto.cryptoCode}_NODE_PASSWORD`]))
throw new Error(
`The environment variable for ${crypto.cryptoCode}_NODE_PASSWORD is not set!`,
)
}
return true
}
function setupCrypto(crypto) {
logger.info(`Installing ${crypto.display}...`)
if (!isEnvironmentValid(crypto))
throw new Error(`Environment error for ${crypto.display}`)
if (isRemoteWallet(crypto)) {
logger.info(
`Environment variable ${crypto.cryptoCode}_WALLET_LOCATION is set as 'remote', so there's no need to install a node in the system. Exiting...`,
)
return
}
const cryptoDir = coinUtils.cryptoDir(crypto, BLOCKCHAIN_DIR)
makeDir.sync(cryptoDir)
const cryptoPlugin = plugin(crypto)
const oldDir = process.cwd()
const tmpDir = isDevMode()
? path.resolve(BLOCKCHAIN_DIR, 'tmp', 'blockchain-install')
: '/tmp/blockchain-install'
makeDir.sync(tmpDir)
process.chdir(tmpDir)
common.es('rm -rf *')
common.fetchAndInstall(crypto)
cryptoPlugin.setup(cryptoDir)
if (!isDevMode()) {
common.writeFile(installedVolumeFilePath(crypto), '')
}
process.chdir(oldDir)
}
function updateCrypto(crypto) {
if (!common.isUpdateDependent(crypto.cryptoCode)) return
const cryptoPlugin = plugin(crypto)
// TODO: we need to refactor the way we retrieve this status, p.e Monero uses two
// services with specific names, so each coin should have its implementation.
// Currently, it's not a breaking change because only BTC is update dependent
const status = common
.es(`sudo supervisorctl status ${crypto.code} | awk '{ print $2 }'`)
.trim()
const isCurrentlyRunning = _.includes(status, ['RUNNING', 'STARTING'])
cryptoPlugin.updateCore(
common.getBinaries(crypto.cryptoCode),
isCurrentlyRunning,
)
}
function plugin(crypto) {
const plugin = PLUGINS[crypto.cryptoCode]
if (!plugin) throw new Error(`No such plugin: ${crypto.cryptoCode}`)
return plugin
}
function getBlockchainSyncStatus(cryptoList) {
return settingsLoader.loadLatest().then(settings => {
if (isDevMode()) return new Array(_.size(cryptoList)).fill('ready')
const blockchainStatuses = _.reduce(
(acc, value) => {
const processStatus = common
.es(`sudo supervisorctl status ${value.code} | awk '{ print $2 }'`)
.trim()
return acc.then(a => {
if (processStatus === 'RUNNING') {
return wallet
.checkBlockchainStatus(settings, value.cryptoCode)
.then(res => Promise.resolve({ ...a, [value.cryptoCode]: res }))
}
return Promise.resolve({ ...a })
})
},
Promise.resolve({}),
cryptoList,
)
return blockchainStatuses
})
}
function isInstalled(crypto) {
return isDevMode()
? isInstalledSoftware(crypto)
: isInstalledSoftware(crypto) && isInstalledVolume(crypto)
}
function isDisabled(crypto) {
switch (crypto.cryptoCode) {
case 'XMR':
return (
(isInstalled(crypto) && 'Installed') ||
(isInstalled(_.find(it => it.code === 'zcash', cryptos)) &&
'Insufficient resources. Contact support.')
)
default:
return isInstalled(crypto) && 'Installed'
}
}
function run() {
const choices = _.flow([
_.filter(c => !c.hideFromInstall),
_.map(c => {
return {
name: c.display,
value: c.code,
checked: isInstalled(c),
disabled: isDisabled(c),
}
}),
])(cryptos)
const questions = []
const validateAnswers = async answers => {
if (_.size(answers) > 2)
return {
message: `Please insert a maximum of two coins to install.`,
isValid: false,
}
if (
_.isEmpty(_.difference(['monero', 'zcash'], answers)) ||
(_.includes('monero', answers) &&
isInstalled(_.find(it => it.code === 'zcash', cryptos))) ||
(_.includes('zcash', answers) &&
isInstalled(_.find(it => it.code === 'monero', cryptos)))
) {
return {
message: `Zcash and Monero installations are temporarily mutually exclusive, given the space needed for their blockchains. Contact support for more information.`,
isValid: false,
}
}
return getBlockchainSyncStatus(cryptos).then(blockchainStatuses => {
const result = _.reduce(
(acc, value) => ({
...acc,
[value]: _.isNil(acc[value]) ? 1 : acc[value] + 1,
}),
{},
_.values(blockchainStatuses),
)
if (_.size(answers) + result.syncing > 2) {
return {
message: `Installing these coins would pass the 2 parallel blockchain synchronization limit. Please try again with fewer coins or try again later.`,
isValid: false,
}
}
if (result.syncing > 2) {
return {
message: `There are currently more than 2 blockchains in their initial synchronization. Please try again later.`,
isValid: false,
}
}
return { message: null, isValid: true }
})
}
questions.push({
type: 'checkbox',
name: 'crypto',
message:
'Which cryptocurrencies would you like to install?\nTo prevent server resource overloading, only TWO coins should be syncing simultaneously.\nMore coins can be installed after this process is over.',
choices,
})
inquirer
.prompt(questions)
.then(answers => Promise.all([validateAnswers(answers.crypto), answers]))
.then(([res, answers]) => {
if (res.isValid) {
return processCryptos(answers.crypto)
}
logger.error(res.message)
})
}

View file

@ -0,0 +1,100 @@
const path = require('path')
const { utils: coinUtils } = require('@lamassu/coins')
const common = require('./common')
module.exports = { setup, updateCore }
const coinRec = coinUtils.getCryptoCurrency('LTC')
function setup(dataDir) {
common.firewall([coinRec.defaultPort])
const config = buildConfig()
common.writeFile(path.resolve(dataDir, coinRec.configFile), config)
const cmd = `/usr/local/bin/${coinRec.daemon} -datadir=${dataDir}`
common.writeSupervisorConfig(coinRec, cmd)
}
function updateCore(coinRec, isCurrentlyRunning) {
common.logger.info('Updating Litecoin Core. This may take a minute...')
common.es(`sudo supervisorctl stop litecoin`)
common.es(`curl -#o /tmp/litecoin.tar.gz ${coinRec.url}`)
if (
common.es(`sha256sum /tmp/litecoin.tar.gz | awk '{print $1}'`).trim() !==
coinRec.urlHash
) {
common.logger.info(
'Failed to update Litecoin Core: Package signature do not match!',
)
return
}
common.es(`tar -xzf /tmp/litecoin.tar.gz -C /tmp/`)
common.logger.info('Updating wallet...')
common.es(`cp /tmp/${coinRec.dir}/* /usr/local/bin/`)
common.es(`rm -r /tmp/${coinRec.dir.replace('/bin', '')}`)
common.es(`rm /tmp/litecoin.tar.gz`)
if (
common.es(
`grep "changetype=" /mnt/blockchains/litecoin/litecoin.conf || true`,
)
) {
common.logger.info(`changetype already defined, skipping...`)
} else {
common.logger.info(`Enabling bech32 change addresses in config file..`)
common.es(
`echo "\nchangetype=bech32" >> /mnt/blockchains/litecoin/litecoin.conf`,
)
}
if (
common.es(
`grep "blockfilterindex=" /mnt/blockchains/litecoin/litecoin.conf || true`,
)
) {
common.logger.info(`blockfilterindex already defined, skipping...`)
} else {
common.logger.info(`Disabling blockfilterindex in config file..`)
common.es(
`echo "\nblockfilterindex=0" >> /mnt/blockchains/litecoin/litecoin.conf`,
)
}
if (
common.es(
`grep "peerblockfilters=" /mnt/blockchains/litecoin/litecoin.conf || true`,
)
) {
common.logger.info(`peerblockfilters already defined, skipping...`)
} else {
common.logger.info(`Disabling peerblockfilters in config file..`)
common.es(
`echo "\npeerblockfilters=0" >> /mnt/blockchains/litecoin/litecoin.conf`,
)
}
if (isCurrentlyRunning) {
common.logger.info('Starting wallet...')
common.es(`sudo supervisorctl start litecoin`)
}
common.logger.info('Litecoin Core is updated!')
}
function buildConfig() {
return `rpcuser=lamassuserver
rpcpassword=${common.randomPass()}
dbcache=500
server=1
connections=40
keypool=10000
prune=4000
daemon=0
addresstype=p2sh-segwit
changetype=bech32
blockfilterindex=0
peerblockfilters=0
`
}

View file

@ -0,0 +1,61 @@
const path = require('path')
const { utils } = require('@lamassu/coins')
const common = require('./common')
module.exports = { setup, updateCore }
const coinRec = utils.getCryptoCurrency('XMR')
function setup(dataDir) {
common.firewall([coinRec.defaultPort])
const auth = `lamassuserver:${common.randomPass()}`
const config = buildConfig(auth)
common.writeFile(path.resolve(dataDir, coinRec.configFile), config)
const cmd = `/usr/local/bin/${coinRec.daemon} --no-zmq --data-dir ${dataDir} --config-file ${dataDir}/${coinRec.configFile}`
const walletCmd = `/usr/local/bin/${coinRec.wallet} --rpc-login ${auth} --daemon-host 127.0.0.1 --daemon-port 18081 --trusted-daemon --daemon-login ${auth} --rpc-bind-port 18082 --wallet-dir ${dataDir}/wallets`
common.writeSupervisorConfig(coinRec, cmd, walletCmd)
}
function updateCore(coinRec, isCurrentlyRunning) {
common.logger.info('Updating Monero. This may take a minute...')
common.es(`sudo supervisorctl stop monero monero-wallet`)
common.es(`curl -#o /tmp/monero.tar.gz ${coinRec.url}`)
if (
common.es(`sha256sum /tmp/monero.tar.gz | awk '{print $1}'`).trim() !==
coinRec.urlHash
) {
common.logger.info(
'Failed to update Monero: Package signature do not match!',
)
return
}
common.es(`tar -xf /tmp/monero.tar.gz -C /tmp/`)
common.logger.info('Updating wallet...')
common.es(`cp /tmp/${coinRec.dir}/monerod /usr/local/bin/monerod`)
common.es(
`cp /tmp/${coinRec.dir}/monero-wallet-rpc /usr/local/bin/monero-wallet-rpc`,
)
common.es(`rm -r /tmp/${coinRec.dir.replace('/bin', '')}`)
common.es(`rm /tmp/monero.tar.gz`)
if (isCurrentlyRunning) {
common.logger.info('Starting wallet...')
common.es(`sudo supervisorctl start monero monero-wallet`)
}
common.logger.info('Monero is updated!')
}
function buildConfig(auth) {
return `rpc-login=${auth}
stagenet=0
restricted-rpc=1
db-sync-mode=safe
out-peers=20
in-peers=20
prune-blockchain=1
`
}

View file

@ -0,0 +1,94 @@
const path = require('path')
const { utils: coinUtils } = require('@lamassu/coins')
const common = require('./common')
module.exports = { setup, updateCore }
const es = common.es
const logger = common.logger
function updateCore(coinRec, isCurrentlyRunning) {
common.logger.info('Updating your Zcash wallet. This may take a minute...')
common.es(`sudo supervisorctl stop zcash`)
common.es(`curl -#Lo /tmp/zcash.tar.gz ${coinRec.url}`)
if (
common.es(`sha256sum /tmp/zcash.tar.gz | awk '{print $1}'`).trim() !==
coinRec.urlHash
) {
common.logger.info(
'Failed to update Zcash: Package signature do not match!',
)
return
}
common.es(`tar -xzf /tmp/zcash.tar.gz -C /tmp/`)
common.logger.info('Updating wallet...')
common.es(`cp /tmp/${coinRec.dir}/* /usr/local/bin/`)
common.es(`rm -r /tmp/${coinRec.dir.replace('/bin', '')}`)
common.es(`rm /tmp/zcash.tar.gz`)
if (
common.es(
`grep "walletrequirebackup=" /mnt/blockchains/zcash/zcash.conf || true`,
)
) {
common.logger.info(`walletrequirebackup already defined, skipping...`)
} else {
common.logger.info(`Setting 'walletrequirebackup=false' in config file...`)
common.es(
`echo "\nwalletrequirebackup=false" >> /mnt/blockchains/zcash/zcash.conf`,
)
}
if (
common.es(
`grep "i-am-aware-zcashd-will-be-replaced-by-zebrad-and-zallet-in-2025=" /mnt/blockchains/zcash/zcash.conf || true`,
)
) {
common.logger.info(
`i-am-aware-zcashd-will-be-replaced-by-zebrad-and-zallet-in-2025 already defined, skipping...`,
)
} else {
common.logger.info(
`Setting 'i-am-aware-zcashd-will-be-replaced-by-zebrad-and-zallet-in-2025=1' in config file...`,
)
common.es(
`echo "\ni-am-aware-zcashd-will-be-replaced-by-zebrad-and-zallet-in-2025=1" >> /mnt/blockchains/zcash/zcash.conf`,
)
}
if (isCurrentlyRunning) {
common.logger.info('Starting wallet...')
common.es(`sudo supervisorctl start zcash`)
}
common.logger.info('Zcash is updated!')
}
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()
common.writeFile(path.resolve(dataDir, coinRec.configFile), config)
const cmd = `/usr/local/bin/${coinRec.daemon} -datadir=${dataDir}`
common.writeSupervisorConfig(coinRec, cmd)
}
function buildConfig() {
return `mainnet=1
addnode=mainnet.z.cash
rpcuser=lamassuserver
rpcpassword=${common.randomPass()}
dbcache=500
keypool=10000
walletrequirebackup=false
`
}