flosight-api/perf/perf.js
2018-01-15 16:35:41 -08:00

455 lines
9.4 KiB
JavaScript

'use strict';
var expect = require('chai').expect;
var spawn = require('child_process').spawn;
var rimraf = require('rimraf');
var mkdirp = require('mkdirp');
var fs = require('fs');
var async = require('async');
var RPC = require('florincoind-rpc');
var http = require('http');
var flocore = require('flocore-lib');
var PrivateKey = flocore.PrivateKey;
var Transaction = flocore.Transaction;
console.log('This test takes a really long time to run, be patient.');
var rpcConfig = {
protocol: 'http',
user: 'local',
pass: 'localtest',
host: '127.0.0.1',
port: 58332,
rejectUnauthorized: false
};
var blocksGenerated = 0;
var startingAddr;
var startingTx;
var amtToSendToEach;
var utxoCount = 3000;
var outputKeys = [];
var rpc1 = new RPC(rpcConfig);
var debug = true;
var flocoreDataDir = '/tmp/flocore';
var florincoinDataDirs = ['/tmp/florincoin'];
var florincoin = {
args: {
datadir: null,
listen: 1,
regtest: 1,
server: 1,
rpcuser: 'local',
rpcpassword: 'localtest',
//printtoconsole: 1
rpcport: 58332,
},
datadir: null,
exec: 'florincoind', //if this isn't on your PATH, then provide the absolute path, e.g. /usr/local/bin/florincoind
processes: []
};
var flocore = {
configFile: {
file: flocoreDataDir + '/flocore-node.json',
conf: {
network: 'regtest',
port: 53001,
datadir: flocoreDataDir,
services: [
'p2p',
'db',
'header',
'block',
'address',
'transaction',
'mempool',
'web',
'flosight-api',
'fee',
'timestamp'
],
servicesConfig: {
'p2p': {
'peers': [
{ 'ip': { 'v4': '127.0.0.1' }, port: 18444 }
]
},
'flosight-api': {
'routePrefix': 'api'
}
}
}
},
httpOpts: {
protocol: 'http:',
hostname: 'localhost',
port: 53001,
},
opts: { cwd: flocoreDataDir },
datadir: flocoreDataDir,
exec: 'flocored', //if this isn't on your PATH, then provide the absolute path, e.g. /usr/local/bin/flocored
args: ['start'],
process: null
};
var startFlorincoind = function(count, callback) {
var listenCount = 0;
async.timesSeries(count, function(n, next) {
var datadir = florincoinDataDirs.shift();
florincoin.datadir = datadir;
florincoin.args.datadir = datadir;
if (listenCount++ > 0) {
florincoin.args.listen = 0;
florincoin.args.rpcport++;
florincoin.args.connect = '127.0.0.1';
}
rimraf(datadir, function(err) {
if(err) {
return next(err);
}
mkdirp(datadir, function(err) {
if(err) {
return next(err);
}
var args = florincoin.args;
var argList = Object.keys(args).map(function(key) {
return '-' + key + '=' + args[key];
});
var florincoinProcess = spawn(florincoin.exec, argList, florincoin.opts);
florincoin.processes.push(florincoinProcess);
florincoinProcess.stdout.on('data', function(data) {
if (debug) {
process.stdout.write(data.toString());
}
});
florincoinProcess.stderr.on('data', function(data) {
if (debug) {
process.stderr.write(data.toString());
}
});
next();
});
});
}, function(err) {
if (err) {
return callback(err);
}
var pids = florincoin.processes.map(function(process) {
return process.pid;
});
console.log(count + ' florincoind\'s started at pid(s): ' + pids);
async.retry({ interval: 1000, times: 1000 }, function(next) {
rpc1.getInfo(next);
}, callback);
});
};
var request = function(httpOpts, callback) {
var request = http.request(httpOpts, function(res) {
if (res.statusCode !== 200 && res.statusCode !== 201) {
return callback('Error from flocore-node webserver: ' + res.statusCode);
}
var resError;
var resData = '';
res.on('error', function(e) {
resError = e;
});
res.on('data', function(data) {
resData += data;
});
res.on('end', function() {
if (resError) {
return callback(resError);
}
var data = JSON.parse(resData);
callback(null, data);
});
});
request.on('error', function(err) {
callback(err);
});
request.write('');
request.end();
};
var shutdownFlorincoind = function(callback) {
florincoin.processes.forEach(function(process) {
process.kill();
});
setTimeout(callback, 3000);
};
var shutdownFlocore = function(callback) {
if (flocore.process) {
flocore.process.kill();
}
callback();
};
var buildInitialTx = function(utxo, key) {
amtToSendToEach = Math.floor(utxo.satoshis / outputKeys.length) - 100;
var tx = new Transaction().from(utxo);
outputKeys.forEach(function(key) {
tx.to(key.toAddress(), amtToSendToEach);
});
tx.sign(key);
return tx;
};
var buildOutputKeys = function() {
for(var i = 0; i < utxoCount; i++) {
outputKeys.push(new PrivateKey('testnet'));
}
};
var buildReturnTxs = function() {
console.log('Building return txs...');
var txs = [];
for(var i = 0; i < outputKeys.length; i++) {
var key = outputKeys[i];
var address = key.toAddress();
var utxo = {
txId: startingTx.hash,
outputIndex: i,
address: address,
script: startingTx.outputs[i].script.toHex(),
satoshis: startingTx.outputs[i].satoshis
};
var tx = new Transaction().from(utxo).to(startingAddr, amtToSendToEach - 1000).sign(key);
txs.push(tx);
}
return txs;
};
var buildInitialChain = function(callback) {
async.waterfall([
function(next) {
buildOutputKeys();
next();
},
function(next) {
blocksGenerated += 101;
rpc1.generate(101, next);
},
function(res, next) {
rpc1.listUnspent(next);
},
function(res, next) {
rpc1.dumpPrivKey(res.result[0].address, function(err, result) {
if (err) {
return next(err);
}
next(null, res.result[0], result.result);
});
},
function(unspent, key, next) {
startingAddr = unspent.address;
var utxo = { txId: unspent.txid, address: startingAddr, satoshis: unspent.amount * 1e8, script: unspent.scriptPubKey, outputIndex: unspent.vout };
startingTx = buildInitialTx(utxo, key, next);
rpc1.sendRawTransaction(startingTx.uncheckedSerialize(), next);
},
function(res, next) {
console.log('generated: ' + outputKeys.length + ' utxos.');
blocksGenerated += 6;
rpc1.generate(6, next);
},
function(res, next) {
// send it all back
var txs = buildReturnTxs();
var txCount = 0;
async.eachSeries(txs, function(tx, next) {
if (++txCount % 100 === 0) {
console.log('sent: ', txCount, ' txs');
}
rpc1.sendRawTransaction(tx.uncheckedSerialize(), next);
}, next);
},
function(next) {
blocksGenerated += 6;
rpc1.generate(6, next);
}
], function(err) {
if (err) {
return callback(err);
}
callback();
});
};
var startFlocore = function(callback) {
rimraf(flocoreDataDir, function(err) {
if(err) {
return callback(err);
}
mkdirp(flocoreDataDir, function(err) {
if(err) {
return callback(err);
}
fs.writeFileSync(flocore.configFile.file, JSON.stringify(flocore.configFile.conf));
var args = flocore.args;
flocore.process = spawn(flocore.exec, args, flocore.opts);
flocore.process.stdout.on('data', function(data) {
if (debug) {
process.stdout.write(data.toString());
}
});
flocore.process.stderr.on('data', function(data) {
if (debug) {
process.stderr.write(data.toString());
}
});
var httpOpts = {
hostname: 'localhost',
port: 53001,
path: '/api/status',
method: 'GET',
headers: {
'Content-Type': 'application/json'
}
};
async.retry({ interval: 1000, times: 100 }, function(next) {
request(httpOpts, function(err, data) {
if (err) {
return next(err);
}
if (data.info.blocks < blocksGenerated) {
return next(data);
}
next();
});
}, callback);
});
});
};
describe('Address Performance', function() {
this.timeout(80000);
before(function(done) {
async.series([
function(next) {
startFlorincoind(florincoinDataDirs.length, next);
},
function(next) {
buildInitialChain(next);
},
function(next) {
startFlocore(next);
}
], done);
});
after(function(done) {
shutdownFlocore(function() {
shutdownFlorincoind(done);
});
});
it('should get address info correctly: /addr/:addr', function(done) {
var httpOpts = {
hostname: 'localhost',
port: 53001,
path: '/api/addr/' + startingAddr,
method: 'GET',
headers: {
'Content-Type': 'application/json'
}
};
async.timesSeries(1, function(n, next) {
var startTime = process.hrtime();
request(httpOpts, function(err) {
if (err) {
return done(err);
}
var endTime = process.hrtime(startTime);
// under 1 sec?
expect(endTime[0]).to.equal(0);
next();
});
}, done);
});
});