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,72 @@
const trimStart = (s, c) => {
let idx = 0
while (idx < s.length && s[idx] === c) idx++
return idx > 0 ? s.substring(idx) : s
}
const optkey = opt => trimStart(opt, '-')
const parse = (default_options, option_arities) => args => {
const positional = []
const parsed_options = {}
for (let i = 0; i < args.length; ) {
const arg = args[i]
i++
const arity = option_arities[arg]
if (typeof arity === 'number') {
if (arity + i > args.length)
return [`${arg}: not enough arguments.`, parsed_options, positional]
const opt = optkey(arg)
switch (arity) {
case 0:
parsed_options[opt] = true
break
case 1:
parsed_options[opt] = args[i]
break
default:
parsed_options[opt] = args.slice(i, i + arity)
break
}
i += arity
} else {
positional.push(arg)
}
}
const options = Object.assign({}, default_options, parsed_options)
return [null, options, positional]
}
const help =
({ grammar, usage }) =>
() => {
if (usage) console.log(usage)
grammar.forEach(([optargs, optdesc, def]) => {
const deftext = def ? ` (default: ${def})` : ''
console.log(`\t${optargs.join(' ')}\t${optdesc}${deftext}`)
})
}
const CLI = ({ grammar, usage }) => {
const details = grammar.map(([[opt, ...optargs], , def]) => [
opt,
optargs.length,
def,
])
const option_arities = Object.fromEntries(
details.map(([opt, arity]) => [opt, arity]),
)
const default_options = Object.fromEntries(
details.map(([opt, , def]) => [optkey(opt), def]),
)
return {
parse: parse(default_options, option_arities),
help: help({ grammar, usage }),
}
}
module.exports = CLI

View file

@ -0,0 +1,10 @@
const EXIT = {
OK: 0,
EXCEPTION: 1,
UNKNOWN: 2,
BADARGS: 3,
}
module.exports = {
EXIT,
}

View file

@ -0,0 +1,78 @@
const { spawnSync } = require('node:child_process')
const { join } = require('node:path')
require('../../lib/environment-helper')
const db = require('../../lib/db')
const { EXIT } = require('./consts')
const CLI = require('./cli')
const help_message =
'Setup the DB according to the previously defined environment.'
const cli = CLI({
grammar: [[['--help'], 'Show this help message']],
})
const help = exit_code => {
console.log('Usage: lamassu-server-stress-testing db ARGS...')
console.log(help_message)
cli.help()
return exit_code
}
const migrate = async () => {
const lamassu_migrate_path = join(__dirname, '../../bin/lamassu-migrate')
const { stdout, stderr, status, signal, error } = spawnSync(
lamassu_migrate_path,
[],
{
cwd: process.cwd(),
encoding: 'utf8',
},
)
if (typeof status !== 'number' || typeof signal === 'string' || error) {
console.error('stdout:', stdout)
console.error('stderr:', stderr)
console.error('status:', status)
console.error('signal:', signal)
console.error('error:', error)
return EXIT.EXCEPTION
} else {
return EXIT.OK
}
}
const configure = async () => {
const config =
'{"config":{"triggersConfig_expirationTime":"Forever","triggersConfig_automation":"Automatic","locale_timezone":"Pacific/Honolulu","cashIn_cashboxReset":"Manual","notifications_email_security":true,"notifications_sms_security":true,"notifications_notificationCenter_security":true,"wallets_advanced_feeMultiplier":"1","wallets_advanced_cryptoUnits":"full","wallets_advanced_allowTransactionBatching":false,"wallets_advanced_id":"c5e3e61e-71b2-4200-9851-0142db7e6797","triggersConfig_customerAuthentication":"SMS","commissions_cashOutFixedFee":1,"machineScreens_rates_active":true,"wallets_BTC_zeroConfLimit":0,"wallets_BTC_coin":"BTC","wallets_BTC_wallet":"mock-wallet","wallets_BTC_ticker":"mock-ticker","wallets_BTC_exchange":"mock-exchange","wallets_BTC_zeroConf":"none","locale_id":"5f18e5ae-4a5d-45b2-8184-a6d69f4cc237","locale_country":"US","locale_fiatCurrency":"USD","locale_languages":["en-US","ja-JP","de-DE"],"locale_cryptoCurrencies":["BTC"],"commissions_minimumTx":2,"commissions_fixedFee":3,"commissions_cashOut":4,"commissions_cashIn":5,"commissions_id":"06d11aaa-34e5-45ab-956b-9728ccfd9330"}}'
await db.none(
"INSERT INTO user_config (type, data, created, valid, schema_version) VALUES ('config', $1, now(), 't', 2)",
[config],
)
return EXIT.OK
}
const run = async args => {
const [err, options] = cli.parse(args)
if (err) {
console.error(err)
return help(EXIT.BADARGS)
}
if (options.help) return help(EXIT.OK)
const funcs = [migrate, configure]
for (let func of funcs) {
const exit_code = await func()
if (exit_code !== EXIT.OK) return exit_code
}
return EXIT.OK
}
module.exports = {
help_message,
run,
}

View file

@ -0,0 +1,93 @@
const { readFileSync, writeFileSync } = require('node:fs')
const { EOL } = require('node:os')
const { resolve } = require('node:path')
const dotenv = require('dotenv')
const { EXIT } = require('./consts')
const CLI = require('./cli')
const help_message = 'Produce an .env file appropriate for stress testing.'
const cli = CLI({
grammar: [
[['--help'], 'Show this help message'],
[['--inenv', 'ENV'], 'Environment file path to read', '.env'],
[['--outenv', 'ENV'], 'Environment file path to write', '.stress.env'],
[['--dbuser', 'DBUSER'], 'Database username', 'postgres'],
[['--dbpass', 'DBPASS'], 'Database password', 'postgres123'],
[['--dbhost', 'DBHOST'], 'Database hostname', 'localhost'],
[['--dbport', 'DBPORT'], 'Database port', '5432'],
[['--dbname', 'DBNAME'], 'Database name', 'lamassu_stress'],
],
})
const help = exit_code => {
console.log('Usage: lamassu-server-stress-testing env ARGS...')
console.log(help_message)
cli.help()
return exit_code
}
const env_read = path => {
const envstr = readFileSync(path, { encoding: 'utf8' })
return dotenv.parse(envstr)
//const entries = envstr
// .split(EOL)
// .flatMap((line) => {
// line = line.trimStart()
// const i = line.indexOf('=')
//
// if (line.startsWith('#') || i <= 0)
// return []
//
// const varname = line.substring(0, i)
// const value = line.substring(i + 1)
// return [[varname, value]]
// })
//return Object.fromEntries(entries)
}
const env_write = (envvars, path) => {
const envcontent =
Object.entries(envvars)
.map(([varname, value]) => [varname, value].join('='))
.join(EOL) + EOL
writeFileSync(path, envcontent)
}
const run = async args => {
const [err, options, positional] = cli.parse(args)
if (err) {
console.error(err)
return help(EXIT.BADARGS)
}
if (options.help) return help(EXIT.OK)
if (positional.length > 0) {
console.error('Unknown arguments:', positional)
return help(EXIT.BADARGS)
}
const inenvpath = resolve(process.cwd(), options.inenv ?? '.env')
const inenvvars = env_read(inenvpath)
const outenvpath = resolve(process.cwd(), options.outenv ?? '.stress.env')
const outenvvars = {
...inenvvars,
POSTGRES_USER: options.dbuser ?? 'postgres',
POSTGRES_PASSWORD: options.dbpass ?? 'postgres123',
POSTGRES_HOST: options.dbhost ?? 'localhost',
POSTGRES_PORT: options.dbport ?? '5432',
POSTGRES_DB: options.dbname ?? 'lamassu_stress',
}
env_write(outenvvars, outenvpath)
return EXIT.OK
}
module.exports = {
help_message,
run,
}

View file

@ -0,0 +1,79 @@
const { EXIT } = require('./consts')
const SUBCMDS = {
env: require('./env'),
db: require('./db'),
machines: require('./machines'),
server: require('./server'),
}
const README = `
This program will help you set the lamassu-server up for stress testing. This
short introduction is meant only as a quickstart guide; the subcommands may
support more options beyond those shown here. Use the --help flag for details
on each subcommand.
First of all, you need to create a suitable .env file. With the following
commands, .env.bak will be used as a starting point, and the result will be
saved in .env. This helps to avoid losing the real configurations.
$ cp .env .env.bak
$ lamassu-server-stress-testing env --inenv .env.bak --outenv .env
The database chosen in the command above (by default 'lamassu_stress') must be
initialized, and must have a bare-bones configuration. The following command
takes care of it:
$ lamassu-server-stress-testing db
You also need to create fake machines that will be used later in the actual
stress tests (including certificates, and pairing each to the server). The
following command creates 10 fake machines, and saves their device IDs in
path/to/machine-ids.txt. path/to/real/machine/code/ is the path to the root of
the machine's code.
$ lamassu-server-stress-testing machines -n 10 --device_ids_path path/to/machine-ids.txt --machine path/to/real/machine/code/ --replace_existing
Finally, use the following to start the lamassu-server in stress-testing mode:
$ lamassu-server-stress-testing server
`
const rightpad = (s, w, c) => s + c.repeat(Math.max(w - s.length, 0))
const help = exit_code => {
console.log('Usage: lamassu-server-stress-testing SUBCMD ARGS...')
console.log('Where SUBCMD is one of the following:')
const max_subcmd_length = Math.max(
...Object.keys(SUBCMDS).map(subcmd => subcmd.length),
)
Object.entries(SUBCMDS).forEach(([subcmd, { help_message }]) => {
console.log(
`\t${rightpad(subcmd, max_subcmd_length, ' ')}\t${help_message ?? ''}`,
)
})
console.log(README)
return exit_code
}
const main = async args => {
try {
const subcmd = SUBCMDS[args[0]]
const exit_code =
args.length === 0
? help(EXIT.OK)
: !subcmd
? help(EXIT.BADARGS)
: await subcmd.run(args.slice(1))
process.exit(exit_code ?? EXIT.UNKNOWN)
} catch (err) {
console.error(err)
process.exit(EXIT.EXCEPTION)
}
}
module.exports = main

View file

@ -0,0 +1,109 @@
const { createHash } = require('node:crypto')
const { createWriteStream } = require('node:fs')
const { EOL } = require('node:os')
const { join } = require('node:path')
require('../../lib/environment-helper')
const db = require('../../lib/db')
const { EXIT } = require('./consts')
const CLI = require('./cli')
const help_message = 'Create and insert fake machines into the DB.'
const cli = CLI({
grammar: [
[['--help'], 'Show this help message'],
[['--machine', 'PATH'], "Path to the machine's source code root"],
[['--device_ids_path', 'PATH'], 'Where to save the list of device IDs'],
[['-n', 'NUMBER'], 'Number of fake machines to create'],
[['--replace_existing'], 'Remove machines of previous runs'],
],
})
const help = exit_code => {
console.log('Usage: lamassu-server-stress-testing machines ARGS...')
console.log(help_message)
cli.help()
return exit_code
}
const close_stream = stream =>
new Promise((resolve, reject) =>
stream.close(err => (err ? reject(err) : resolve())),
)
const compute_machine_id = cert => {
cert = cert.split('\r\n')
const raw = Buffer.from(cert.slice(1, cert.length - 2).join(''), 'base64')
return createHash('sha256').update(raw).digest('hex')
}
const create_fake_machine = async (device_ids_file, self_sign, i) => {
console.log('creating machine', i)
const { cert } = await self_sign.generateCertificate()
const device_id = compute_machine_id(cert)
await db.none(
`INSERT INTO devices (device_id, cassette1, cassette2, paired, display, created, name, last_online, location)
VALUES ($1, 0, 0, 't', 't', now(), $2, now(), '{}'::json)`,
[device_id, `machine_${i}`],
)
device_ids_file.write(device_id + EOL)
console.log('created machine', i, 'with device ID', device_id)
}
const create_fake_machines = async ({
machine,
device_ids_path,
n,
replace_existing,
}) => {
n = parseInt(n)
if (Number.isNaN(n) || n <= 0) {
console.error('Expected n to be a positive number, got', n)
return help(EXIT.BADARGS)
}
const device_ids_file = createWriteStream(device_ids_path, {
flags: replace_existing ? 'w' : 'a',
mode: 0o640,
flush: true,
})
if (replace_existing) await db.none('DELETE FROM devices')
const self_sign = require(join(process.cwd(), machine, 'lib', 'self_sign'))
for (let i = 0; i < n; i++)
await create_fake_machine(device_ids_file, self_sign, i)
await close_stream(device_ids_file)
return EXIT.OK
}
const run = async args => {
const [err, options] = cli.parse(args)
if (err) {
console.error(err)
return help(EXIT.BADARGS)
}
if (options.help) return help(EXIT.OK)
const missing_options = ['n', 'machine', 'device_ids_path'].filter(
opt => !options[opt],
)
if (missing_options.length > 0) {
console.error(
'The following options are required:',
missing_options.join(', '),
)
return help(EXIT.BADARGS)
}
return await create_fake_machines(options)
}
module.exports = {
help_message,
run,
}

View file

@ -0,0 +1,50 @@
#!/bin/bash
set -e
if [ $# -eq 0 ]
then
echo "usage: ./build-machines [number_of_machines] /path/to/server/cert/lamassu_op_root_ca.pem /path/to/machine/" && exit 1
fi
SERVER_CERT=$(perl -pe 's/\n/\\n/' < $2)
if [ -z "$SERVER_CERT" ]
then
echo "Lamassu-op-root-ca.pem is empty" && exit 1
fi
# Create stress database
sudo -u postgres psql postgres -c "drop database if exists lamassu_stress"
sudo -u postgres psql postgres -c "create database lamassu_stress with template lamassu"
START=1
END=$1
for (( c=$START; c<=$END; c++ ))
do
echo "Creating machine $c out of $END..."
NUMBER=$c
mkdir -p ./machines/$NUMBER/
cp "$3"/data/client.sample.pem ./machines/$NUMBER/
cp "$3"/data/client.sample.key ./machines/$NUMBER/
cat > ./machines/$NUMBER/connection_info.json << EOF
{"host":"localhost","ca":"$SERVER_CERT"}
EOF
# Get device_id
DEVICE_ID=`openssl x509 -outform der -in ./machines/$NUMBER/client.pem | sha256sum | cut -d ' ' -f 1`
# Update db config
NEW_CONFIG=$(node ./utils/save-config.js $NUMBER $DEVICE_ID)
sudo -u postgres psql "lamassu_stress" << EOF
insert into user_config(type, data, created, valid)
values('config', '$NEW_CONFIG', now(), 't')
EOF
# Add device on db
sudo -u postgres psql "lamassu_stress" << EOF
insert into devices(device_id, cashbox, cassette1, cassette2, paired, display, created, name, last_online, location)
values ('$DEVICE_ID', 0, 0, 0, 't', 't', now(), $NUMBER, now(), '{}'::json)
EOF
done
echo "Done!"

View file

@ -0,0 +1,56 @@
const { fork } = require('node:child_process')
const { join } = require('node:path')
const { EXIT } = require('./consts')
const CLI = require('./cli')
const help_message = 'Start the server configured for stress testing.'
const cli = CLI({
grammar: [[['--help'], 'Show this help message']],
})
const help = exit_code => {
console.log('Usage: lamassu-server-stress-testing server ARGS...')
console.log(help_message)
cli.help()
return exit_code
}
const start_server = args =>
new Promise(resolve => {
const lamassu_server = join(__dirname, '../../bin/lamassu-server')
const ls = fork(lamassu_server, args, {
cwd: process.cwd(),
encoding: 'utf8',
env: { LAMASSU_STRESS_TESTING: 'YES' },
})
ls.on('error', error => {
console.log(error)
resolve(EXIT.EXCEPTION)
})
ls.on('exit', (code, signal) => {
console.error('lamassu-server code:', code)
console.error('lamassu-server signal:', signal)
resolve(typeof code === 'number' ? code : EXIT.EXCEPTION)
})
})
const run = async args => {
const [err, options, positional] = cli.parse(args)
if (err) {
console.error(err)
return help(EXIT.BADARGS)
}
if (options.help) return help(EXIT.OK)
return await start_server(positional)
}
module.exports = {
help_message,
run,
}

View file

@ -0,0 +1,19 @@
{
"config": {
"wallets_BTC_coin": "BTC",
"wallets_BTC_wallet": "mock-wallet",
"wallets_BTC_ticker": "mock-ticker",
"wallets_BTC_exchange": "mock-exchange",
"wallets_BTC_zeroConf": "all-zero-conf",
"locale_id": "32cc539a-78e6-4a1d-96d8-31b7aa628e1f",
"locale_country": "US",
"locale_fiatCurrency": "USD",
"locale_languages": ["en-US"],
"locale_cryptoCurrencies": ["BTC"],
"commissions_minimumTx": 1,
"commissions_fixedFee": 1,
"commissions_cashOut": 1,
"commissions_cashIn": 1,
"commissions_id": "719b9dd9-1444-42fc-918a-f8b2265513ac"
}
}

View file

@ -0,0 +1,7 @@
const SERVER_CERT_PATH = `../../certs/Lamassu_OP_Root_CA.pem`
const MACHINE_PATH = `../../../lamassu-machine`
// Request timers
const POLLING_INTERVAL = 5000
module.exports = { SERVER_CERT_PATH, MACHINE_PATH, POLLING_INTERVAL }