From ee1ea93195fdf205521064ec2e8b22fec08185cf Mon Sep 17 00:00:00 2001 From: Chris Kleeschulte Date: Thu, 7 Sep 2017 18:59:15 -0400 Subject: [PATCH] Added performance tests. Changed getInfo from header service to block service. --- lib/status.js | 2 +- package-lock.json | 2 +- package.json | 2 +- perf/perf.js | 459 ++++++++++++++++++++++++++++++++++++++++++++++ 4 files changed, 462 insertions(+), 3 deletions(-) create mode 100644 perf/perf.js diff --git a/lib/status.js b/lib/status.js index d7929d9..4a112b3 100644 --- a/lib/status.js +++ b/lib/status.js @@ -48,7 +48,7 @@ StatusController.prototype.show = function(req, res) { }; StatusController.prototype.getInfo = function(callback) { - this._header.getInfo(function(err, result) { + this._block.getInfo(function(err, result) { if (err) { return callback(err); } diff --git a/package-lock.json b/package-lock.json index 6536873..49b3ab4 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,6 +1,6 @@ { "name": "insight-api", - "version": "5.0.0-beta.1", + "version": "5.0.0-beta.3", "lockfileVersion": 1, "requires": true, "dependencies": { diff --git a/package.json b/package.json index 566115e..ccbb08a 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "insight-api", "description": "A Bitcoin blockchain REST and web socket API service for Bitcore Node.", - "version": "5.0.0-beta.2", + "version": "5.0.0-beta.3", "repository": "git://github.com/bitpay/insight-api.git", "bugs": { "url": "https://github.com/bitpay/insight-api/issues" diff --git a/perf/perf.js b/perf/perf.js new file mode 100644 index 0000000..7a13e3c --- /dev/null +++ b/perf/perf.js @@ -0,0 +1,459 @@ +'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('bitcoind-rpc'); +var http = require('http'); +var bitcore = require('bitcore-lib'); +var PrivateKey = bitcore.PrivateKey; +var Transaction = bitcore.Transaction; + +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 bitcoreDataDir = '/tmp/bitcore'; +var bitcoinDataDirs = ['/tmp/bitcoin']; + +var bitcoin = { + args: { + datadir: null, + listen: 1, + regtest: 1, + server: 1, + rpcuser: 'local', + rpcpassword: 'localtest', + //printtoconsole: 1 + rpcport: 58332, + }, + datadir: null, + exec: 'bitcoind', //if this isn't on your PATH, then provide the absolute path, e.g. /usr/local/bin/bitcoind + processes: [] +}; + +var bitcore = { + configFile: { + file: bitcoreDataDir + '/bitcore-node.json', + conf: { + network: 'regtest', + port: 53001, + datadir: bitcoreDataDir, + services: [ + 'p2p', + 'db', + 'header', + 'block', + 'address', + 'transaction', + 'mempool', + 'web', + 'insight-api', + 'fee', + 'timestamp' + ], + servicesConfig: { + 'p2p': { + 'peers': [ + { 'ip': { 'v4': '127.0.0.1' }, port: 18444 } + ] + }, + 'insight-api': { + 'routePrefix': 'api' + } + } + } + }, + httpOpts: { + protocol: 'http:', + hostname: 'localhost', + port: 53001, + }, + opts: { cwd: bitcoreDataDir }, + datadir: bitcoreDataDir, + exec: 'bitcored', //if this isn't on your PATH, then provide the absolute path, e.g. /usr/local/bin/bitcored + args: ['start'], + process: null +}; + +var startBitcoind = function(count, callback) { + + var listenCount = 0; + async.timesSeries(count, function(n, next) { + + var datadir = bitcoinDataDirs.shift(); + + bitcoin.datadir = datadir; + bitcoin.args.datadir = datadir; + + if (listenCount++ > 0) { + bitcoin.args.listen = 0; + bitcoin.args.rpcport++; + bitcoin.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 = bitcoin.args; + var argList = Object.keys(args).map(function(key) { + return '-' + key + '=' + args[key]; + }); + + var bitcoinProcess = spawn(bitcoin.exec, argList, bitcoin.opts); + bitcoin.processes.push(bitcoinProcess); + + bitcoinProcess.stdout.on('data', function(data) { + + if (debug) { + process.stdout.write(data.toString()); + } + + }); + + bitcoinProcess.stderr.on('data', function(data) { + + if (debug) { + process.stderr.write(data.toString()); + } + + }); + + next(); + + }); + + }); + }, function(err) { + + if (err) { + return callback(err); + } + + var pids = bitcoin.processes.map(function(process) { + return process.pid; + }); + + console.log(count + ' bitcoind\'s started at pid(s): ' + pids); + callback(); + }); +}; + + +var request = function(httpOpts, callback) { + + var request = http.request(httpOpts, function(res) { + + if (res.statusCode !== 200 && res.statusCode !== 201) { + return callback('Error from bitcore-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 shutdownBitcoind = function(callback) { + bitcoin.processes.forEach(function(process) { + process.kill(); + }); + setTimeout(callback, 3000); +}; + +var shutdownBitcore = function(callback) { + if (bitcore.process) { + bitcore.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 startBitcore = function(callback) { + + rimraf(bitcoreDataDir, function(err) { + + if(err) { + return callback(err); + } + + mkdirp(bitcoreDataDir, function(err) { + + if(err) { + return callback(err); + } + + fs.writeFileSync(bitcore.configFile.file, JSON.stringify(bitcore.configFile.conf)); + + var args = bitcore.args; + bitcore.process = spawn(bitcore.exec, args, bitcore.opts); + + bitcore.process.stdout.on('data', function(data) { + + if (debug) { + process.stdout.write(data.toString()); + } + + }); + + bitcore.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) { + startBitcoind(bitcoinDataDirs.length, next); + }, + function(next) { + setTimeout(function() { + buildInitialChain(next); + }, 8000); + }, + function(next) { + startBitcore(next); + } + ], function(err) { + + if (err) { + return done(err); + } + + setTimeout(done, 2000); + }); + + }); + + after(function(done) { + shutdownBitcore(function() { + shutdownBitcoind(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); + + }); + +}); + +