Skip to content
This repository has been archived by the owner on Dec 11, 2019. It is now read-only.

Add tools for generating a simulated Payment History for testing #4655

Closed
wants to merge 4 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
"port": "8080"
},
"scripts": {
"add-simulated-payment-history": "node ./tools/addSimulatedPaymentHistory.js",
"build-installer": "node ./tools/buildInstaller.js",
"build-package": "node ./tools/buildPackage.js",
"check-security": "nsp check",
Expand Down
10 changes: 10 additions & 0 deletions tools/addSimulatedPaymentHistory.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
let runUtilApp = require('./utilAppRunner')

let cmd = 'addSimulatedLedgerTransactions'

// if user has specified number of simulated transactions to add
if (process.argv[2]) {
cmd += ' ' + process.argv[2]
}

runUtilApp(cmd, undefined, ['inherit', 'inherit', 'inherit'])
16 changes: 2 additions & 14 deletions tools/clean.js
Original file line number Diff line number Diff line change
@@ -1,22 +1,10 @@
const path = require('path')
const proc = require('child_process')
const rimraf = require('./lib/rimraf')

const rootDir = path.join(__dirname, '..')

function runUtilApp (cmd, file) {
process.env.NODE_ENV = process.env.NODE_ENV || 'development'
const utilAppDir = path.join(__dirname, 'lib', 'utilApp')
const options = {
env: process.env,
cwd: utilAppDir
}
cmd = cmd.split(' ')
const utilApp = proc.spawnSync('electron', [utilAppDir].concat(cmd), options)
if (utilApp.error) {
console.log('Could not run utilApp - run `npm install electron-prebuilt` and try again', utilApp.error)
}
}
process.env.NODE_ENV = process.env.NODE_ENV || 'development'
const runUtilApp = require('./utilAppRunner')

module.exports.nodeModules = () => {
console.warn('removing node_modules...')
Expand Down
30 changes: 30 additions & 0 deletions tools/lib/simulateLedgerTransactions.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
const TxHelpers = require('./transactionHelpers')

let currentTimestamp = (new Date()).getTime()

const getNthContributionPeriodBack = function (n) {
return currentTimestamp - (1000 * 3600 * 24 * 30) * n
}

function simulateLedgerTransactions (numTx) {
let numTransactions = numTx || 10

let transactions = (new Array(numTransactions))
.fill(null)
.map(function (nothing, idx) {
let tx = TxHelpers.generateTransaction()
tx.submissionStamp = getNthContributionPeriodBack(idx)
tx.submissionDate = new Date(tx.submissionStamp)

let validatorOutput = TxHelpers.validateTransaction(tx)
if (validatorOutput.error) {
console.error(validatorOutput.error)
}

return tx
})

return transactions
}

module.exports = simulateLedgerTransactions
199 changes: 199 additions & 0 deletions tools/lib/transactionHelpers.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,199 @@
let Joi = require('joi')

const SATOSHIS_PER_BTC = Math.pow(10, 8)

const VALID_CURRENCY_CODE_REGEX = /^[A-Z]+$/
const VALID_HOSTNAME_REGEX = /^(([a-zA-Z0-9]|[a-zA-Z0-9][a-zA-Z0-9\-]*[a-zA-Z0-9])\.)*([A-Za-z0-9]|[A-Za-z0-9][A-Za-z0-9\-]*[A-Za-z0-9])$/

const transactionSchema = Joi.object().keys({
viewingId: Joi.string().guid().required().description('a unique id for the transaction'),
surveyorId: Joi.string().length(32, 'base64').required().description('a unique id for the surveyor. 32 bytes, base64 encoded'),
contribution: Joi.object().keys({
fiat: Joi.object().keys({
amount: Joi.number().min(0).precision(2).required(),
currency: Joi.string().required()
}).required(),
rates: Joi.object().pattern(VALID_CURRENCY_CODE_REGEX, Joi.number().min(0).precision(2)),
satoshis: Joi.number().integer().min(0).required().description('contribution amount in satoshis (10E-8 BTC)'),
fee: Joi.number().integer().min(0).required().description('transaction fee in satoshis for the contribution (10E-8 BTC)')
}).required().description('object describing contribution in fiat and BTC, with exchange rate at time of contribution and network transaction fee'),
submissionStamp: Joi.date().timestamp().required(),
/** submissionId is 32 bytes, 64 chars hex-encoded **/
submissionId: Joi.string().hex().length(32, 'hex').required(),
submissionDate: Joi.date(),
count: Joi.number().integer().min(0),

/** credential:
* complicated JSON BLOB from anonize2, come back to this later
**/
credential: Joi.string(),

/** surveyorIds:
* an array of random 32-byte ids (base64-encoded).
* length should equal sibling value `count`
**/
surveyorIds: Joi.array().items(Joi.string().length(32, 'base64')), // ideally something like .length(Joi.ref('count'))

satoshis: Joi.ref('contribution.satoshis'),
votes: Joi.ref('count'),
ballots: Joi.object().pattern(VALID_HOSTNAME_REGEX, Joi.number().integer().min(0))
})

const validateTransaction = function (tx) {
return Joi.validate(tx, transactionSchema)
}

const generateTransaction = function () {
const count = Math.round(Math.random() * 100)

const viewingId = generateViewingId()
const surveyorId = generateSurveyorId()
const contribution = generateContribution()
const submissionStamp = generateSubmissionStamp()
const submissionDate = new Date(submissionStamp)
const submissionId = generateSubmissionId()
const credential = generateCredential() // what args needed?
const surveyorIds = generateSurveyorIds(count)
const satoshis = contribution.satoshis
const votes = count
const ballots = generateBallots(votes)

return {
viewingId,
surveyorId,
contribution,
submissionStamp,
submissionDate,
submissionId,
credential,
surveyorIds,
count,
satoshis,
votes,
ballots
}
}

/** code for generating transaction object components **/
const uuid = require('node-uuid')
const crypto = require('crypto')
const randomBytes = crypto.randomBytes

const generateViewingId = function () {
return uuid.v4().toLowerCase()
}

const generateSurveyorId = function () {
/**
Random 32 bytes, generated in node-anonize2-relic/anonize2/anon.cpp:createSurvey as a random Big:
---
Big vid;
rand_int(vid);
---
Expressed in base64 everywhere in JS-land
**/
return randomBytes(32).toString('base64')
}

const generateSurveyorIds = function (count) {
if (!count) {
return []
}

return (new Array(count)).fill(null).map(generateSurveyorId)
}

const generateContribution = function (satoshis, currency, rate, fee) {
let plusOrMinusTenPercentFactor = 1 + (2 * (Math.random() - 0.5) * 0.1)
let randomExchangeRateUSDPerBTC = 620 * plusOrMinusTenPercentFactor
let randomExchangeRateSatoshisPerUSD = (1 / randomExchangeRateUSDPerBTC) * SATOSHIS_PER_BTC
let randomContributionAmountUSD = [5, 10, 15][ Math.round(Math.random() * 2) ]

currency = currency || 'USD'
currency = currency.toUpperCase()
rate = rate || randomExchangeRateUSDPerBTC
satoshis = satoshis || Math.round(randomContributionAmountUSD * randomExchangeRateSatoshisPerUSD)
fee = fee || (0.0001 * SATOSHIS_PER_BTC)

let rates = {}
rates[currency] = parseFloat(rate.toFixed(2))

if (currency.toUpperCase() !== 'USD') {
rates.USD = randomExchangeRateUSDPerBTC
}

let contribution = {
fiat: {
amount: parseFloat((satoshis / SATOSHIS_PER_BTC * rate).toFixed(2)),
currency: currency
},
rates: rates,
satoshis: satoshis,
fee: fee
}

return contribution
}

const generateSubmissionStamp = function () {
return (new Date()).getTime()
}

const generateSubmissionId = function () {
return randomBytes(32).toString('hex')
}

// this one is a TODO, as it is a complicated JSON blob from anonize
const generateCredential = function () {
return 'PLACEHOLDER_CREDENTIAL_STRING'
}

const generateBallots = function (votes) {
let ballots = {}

let votesRemaining = votes

while (votesRemaining) {
let votesToCast = Math.min(Math.round(Math.random() * votesRemaining) + 1, votesRemaining)
let host = _generateRandomHost()
ballots[host] = votesToCast
votesRemaining -= votesToCast
}

return ballots
}

const ALPHABET = 'abcdefghijklmnopqrstuvwxyz'
const _chooseRandomLetter = function () {
return ALPHABET[Math.round(Math.random() * (ALPHABET.length - 1))]
}

const _generateRandomString = function (len) {
return (new Array(len)).fill(null).map(_chooseRandomLetter).join('')
}

const TLDS = ['com', 'net', 'org', 'io', 'info']

const _generateRandomHost = function (maxLength, minLength) {
maxLength = maxLength || 10
minLength = minLength || 4

let len = Math.max(Math.round(Math.random() * maxLength), minLength)

let tld = TLDS[Math.round(Math.random() * (TLDS.length - 1))]

let numParts = Math.round(Math.random()) + 1

let host = (new Array(numParts)).fill(null).map(function () {
let partLen = Math.max(Math.round(Math.random() * len), minLength)
return _generateRandomString(partLen)
}).join('.') + '.' + tld

return host
}

module.exports = {
transactionSchema,
validateTransaction,
generateTransaction
}
28 changes: 28 additions & 0 deletions tools/lib/utilApp/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -16,12 +16,40 @@ const cleanUserData = (location) => {
}
}

const simulateLedgerTransactions = require('../simulateLedgerTransactions')
const fs = require('fs')

function addSimulatedLedgerTransactions (numTx) {
let userDataPath = app.getPath('userData')
let ledgerStatePath = path.join(userDataPath, 'ledger-state.json')

try {
let ledgerState = JSON.parse(fs.readFileSync(ledgerStatePath).toString())

let simulatedTransactions = simulateLedgerTransactions(numTx)

ledgerState.transactions = (ledgerState.transactions || []).concat(simulatedTransactions)

fs.writeFileSync(ledgerStatePath, JSON.stringify(ledgerState, null, 2))

console.log(`Updated Ledger data file at ${ledgerStatePath}`)
} catch (exc) {
console.error('ERROR in addSimulatedLedgerTransactions: could not find/open/parse Ledger data file.')
console.error(`Expected path to Ledger data file: ${ledgerStatePath}`)
console.error('Probable solution: Run Brave and enable Payments, then execute this script. Enabling/disabling Payments (or restarting Brave) should then show the generated transactions in Brave.')
}
}

app.on('ready', () => {
const cmd = process.argv[2]
switch (cmd) {
case 'cleanUserData':
cleanUserData(process.argv[3])
break
case 'addSimulatedLedgerTransactions':
addSimulatedLedgerTransactions(process.argv[3])
break
}

process.exit(0)
})
22 changes: 22 additions & 0 deletions tools/utilAppRunner.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
const path = require('path')
const proc = require('child_process')

function runUtilApp (cmd, file, stdioOptions) {
console.log('runUtilApp: ')

process.env.NODE_ENV = process.env.NODE_ENV || 'development'
const utilAppDir = path.join(__dirname, 'lib', 'utilApp')
const options = {
env: process.env,
cwd: utilAppDir,
stdio: stdioOptions
}
cmd = cmd.split(' ')
const utilApp = proc.spawnSync('electron', [utilAppDir].concat(cmd), options)
if (utilApp.error) {
console.log('Could not run utilApp - run `npm install electron-prebuilt` and try again', utilApp.error)
}
return utilApp
}

module.exports = runUtilApp