From e68faa19f4e8ec033d4c87549c51526a99b5ceb1 Mon Sep 17 00:00:00 2001 From: Maciej Baj Date: Thu, 23 Nov 2017 17:28:22 +0100 Subject: [PATCH 01/14] Restructure peers integration tests --- Gruntfile.js | 2 +- test/integration/index.js | 4 +- test/integration/peers.integration.js | 495 ------------------ test/integration/pm2.integration.json | 65 +-- test/integration/scenarios/index.js | 11 + test/integration/scenarios/network/peers.js | 119 +++++ .../scenarios/propagation/blocks.js | 1 + .../scenarios/propagation/transactions.js | 1 + test/integration/setup/index.js | 45 ++ test/integration/setup/network.js | 34 ++ test/integration/setup/pm2.js | 36 ++ test/integration/setup/shell.js | 30 ++ test/integration/setup/sync.js | 74 +++ test/integration/transport.js | 74 +++ test/integration/utils/http.js | 142 +++++ test/integration/utils/index.js | 10 + test/integration/utils/transactions.js | 12 + test/integration/utils/ws.js | 37 ++ 18 files changed, 635 insertions(+), 557 deletions(-) delete mode 100644 test/integration/peers.integration.js create mode 100644 test/integration/scenarios/index.js create mode 100644 test/integration/scenarios/network/peers.js create mode 100644 test/integration/scenarios/propagation/blocks.js create mode 100644 test/integration/scenarios/propagation/transactions.js create mode 100644 test/integration/setup/index.js create mode 100644 test/integration/setup/network.js create mode 100644 test/integration/setup/pm2.js create mode 100644 test/integration/setup/shell.js create mode 100644 test/integration/setup/sync.js create mode 100644 test/integration/transport.js create mode 100644 test/integration/utils/http.js create mode 100644 test/integration/utils/index.js create mode 100644 test/integration/utils/transactions.js create mode 100644 test/integration/utils/ws.js diff --git a/Gruntfile.js b/Gruntfile.js index bf1888dd7ba..9d21cdcd340 100644 --- a/Gruntfile.js +++ b/Gruntfile.js @@ -103,7 +103,7 @@ module.exports = function (grunt) { }, testIntegration: { - command: './node_modules/.bin/_mocha --bail test/integration/peers.integration.js ', + command: './node_modules/.bin/_mocha --bail test/integration/index.js ', maxBuffer: maxBufferSize }, diff --git a/test/integration/index.js b/test/integration/index.js index c759a7ffb8e..f48d6de5e5a 100644 --- a/test/integration/index.js +++ b/test/integration/index.js @@ -1 +1,3 @@ -require('./peers.integration'); +'use strict'; + +require('./transport'); diff --git a/test/integration/peers.integration.js b/test/integration/peers.integration.js deleted file mode 100644 index 2bdb68a56ae..00000000000 --- a/test/integration/peers.integration.js +++ /dev/null @@ -1,495 +0,0 @@ -'use strict'; - -var _ = require('lodash'); -var async = require('async'); -var child_process = require('child_process'); -var chai = require('chai'); -var expect = require('chai').expect; -var fs = require('fs'); -var popsicle = require('popsicle'); -var Promise = require('bluebird'); -var scClient = require('socketcluster-client'); -var waitUntilBlockchainReady = require('../common/globalBefore').waitUntilBlockchainReady; -var WAMPClient = require('wamp-socket-cluster/WAMPClient'); - -var baseConfig = require('../../test/config.json'); -var WSClient = require('../common/wsClient'); -var Logger = require('../../logger'); -var logger = new Logger({filename: 'integrationTestsLogger.logs', echo: 'log'}); - -var SYNC_MODE = { - RANDOM: 0, - ALL_TO_FIRST: 1, - ALL_TO_GROUP: 2 -}; - -var SYNC_MODE_DEFAULT_ARGS = { - RANDOM: { - PROBABILITY: 0.5 //range 0 - 1 - }, - ALL_TO_GROUP: { - INDICES: [] - } -}; - -var WAIT_BEFORE_CONNECT_MS = 60000; - -SYNC_MODE_DEFAULT_ARGS.ALL_TO_GROUP.INDICES = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]; -var testNodeConfigs = generateNodesConfig(10, SYNC_MODE.ALL_TO_GROUP, [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]); - -var monitorWSClient = { - protocol: 'http', - hostname: '127.0.0.1', - port: 'toOverwrite', - autoReconnect: true, - query: WSClient.generatePeerHeaders() -}; - -monitorWSClient.query.port = 9999; - -function generateNodePeers (numOfPeers, syncMode, syncModeArgs) { - syncModeArgs = syncModeArgs || SYNC_MODE_DEFAULT_ARGS; - - var peersList = []; - switch (syncMode) { - case SYNC_MODE.RANDOM: - if (typeof syncModeArgs.RANDOM.PROBABILITY !== 'number') { - throw new Error('Probability parameter not specified to random sync mode'); - } - var isPickedWithProbability = function (n) { - return !!n && Math.random() <= n; - }; - - Array.apply(null, new Array(numOfPeers)).forEach(function (val, index) { - if (isPickedWithProbability(syncModeArgs.RANDOM.PROBABILITY)) { - peersList.push({ - ip: '127.0.0.1', - port: 5000 + index - }); - } - }); - break; - - case SYNC_MODE.ALL_TO_FIRST: - peersList = [{ - ip: '127.0.0.1', - port: 5001 - }]; - break; - - case SYNC_MODE.ALL_TO_GROUP: - if (!Array.isArray(syncModeArgs.ALL_TO_GROUP.INDICES)) { - throw new Error('Provide peers indices to sync with as an array'); - } - Array.apply(null, new Array(numOfPeers)).forEach(function (val, index) { - if (syncModeArgs.ALL_TO_GROUP.INDICES.indexOf(index) !== -1) { - peersList.push({ - ip: '127.0.0.1', - port: 5000 + index - }); - } - }); - } - return peersList; -} - -function generateNodesConfig (numOfPeers, syncMode, forgingNodesIndices) { - var secretsInChunk = Math.ceil(baseConfig.forging.secret.length / forgingNodesIndices.length); - var secretsChunks = forgingNodesIndices.map(function (val, index) { - return baseConfig.forging.secret.slice(index * secretsInChunk, (index + 1) * secretsInChunk); - }); - - return Array.apply(null, new Array(numOfPeers)).map(function (val, index) { - var isForging = forgingNodesIndices.indexOf(index) !== -1; - return { - ip: '127.0.0.1', - port: 5000 + index, - database: 'lisk_local_' + index, - peers: { - list: generateNodePeers(numOfPeers, syncMode) - }, - forging: isForging, - secrets: isForging ? secretsChunks[index] : [] - }; - }); -} - -function generatePM2NodesConfig (testNodeConfigs) { - - var pm2Config = { - apps: [] - }; - - function insertNewNode (index, nodeConfig) { - - function peersAsString (peersList) { - return peersList.reduce(function (acc, peer) { - acc += peer.ip + ':' + peer.port + ','; - return acc; - }, '').slice(0, -1); - } - - var nodePM2Config = { - 'exec_mode': 'fork', - 'script': 'app.js', - 'name': 'node_' + index, - 'args': ' -p ' + nodeConfig.port + - ' -h ' + (nodeConfig.port - 1000) + - ' -x ' + peersAsString(nodeConfig.peers.list) + - ' -d ' + nodeConfig.database, - 'env': { - 'NODE_ENV': 'test' - }, - 'error_file': './test/integration/logs/lisk-test-node-' + index + '.err.log', - 'out_file': './test/integration/logs/lisk-test-node-' + index + '.out.log' - }; - - if (!nodeConfig.forging) { - nodePM2Config.args += ' -c ./test/integration/configs/config.non-forge.json'; - } else { - var currentNodeConfig = _.clone(baseConfig); - currentNodeConfig.forging.force = false; - currentNodeConfig.forging.secret = nodeConfig.secrets; - fs.writeFileSync(__dirname + '/configs/config.node-' + index + '.json', JSON.stringify(currentNodeConfig, null, 4)); - nodePM2Config.args += ' -c ./test/integration/configs/config.node-' + index + '.json'; - } - pm2Config.apps.push(nodePM2Config); - } - - testNodeConfigs.forEach(function (testNodeConfig, index) { - insertNewNode(index, testNodeConfig); - }); - - fs.writeFileSync(__dirname + '/pm2.integration.json', JSON.stringify(pm2Config, null, 4)); -} - -function clearLogs (cb) { - child_process.exec('rm -rf test/integration/logs/*', function (err) { - return cb(err); - }); -} - -function launchTestNodes (cb) { - child_process.exec('node_modules/.bin/pm2 start test/integration/pm2.integration.json', function (err) { - return cb(err); - }); -} - -function killTestNodes (cb) { - child_process.exec('node_modules/.bin/pm2 delete all', function (err) { - return cb(err); - }); -} - -function runFunctionalTests (cb) { - var child = child_process.spawn('node_modules/.bin/_mocha', ['--timeout', (8 * 60 * 1000).toString(), '--exit', - 'test/functional/http/get/blocks.js', 'test/functional/http/get/transactions.js'], { - cwd: __dirname + '/../..' - }); - - child.stdout.pipe(process.stdout); - - child.on('close', function (code) { - if (code === 0) { - return cb(); - } else { - return cb('Functional tests failed'); - } - }); - - child.on('error', function (err) { - return cb(err); - }); -} - -function recreateDatabases (done) { - async.forEachOf(testNodeConfigs, function (nodeConfig, index, eachCb) { - child_process.exec('dropdb ' + nodeConfig.database + '; createdb ' + nodeConfig.database, eachCb); - }, done); -} - -function enableForgingOnDelegates (done) { - var enableForgingPromises = []; - - testNodeConfigs.forEach(function (testNodeConfig) { - testNodeConfig.secrets.forEach(function (keys) { - var enableForgingPromise = popsicle.put({ - url: 'http://' + testNodeConfig.ip + ':' + (testNodeConfig.port - 1000) + '/api/node/status/forging', - headers: { - 'Accept': 'application/json', - 'Content-Type': 'application/json' - }, - body: { - decryptionKey: 'elephant tree paris dragon chair galaxy', - publicKey: keys.publicKey - } - }); - enableForgingPromises.push(enableForgingPromise); - }); - }); - Promise.all(enableForgingPromises).then(function () { - done(); - }).catch(function () { - done('Failed to enable forging on delegates'); - }); -} - -function waitForAllNodesToBeReady (done) { - async.forEachOf(testNodeConfigs, function (nodeConfig, index, eachCb) { - waitUntilBlockchainReady(eachCb, 20, 2000, 'http://' + nodeConfig.ip + ':' + (nodeConfig.port - 1000)); - }, done); -} - -function establishWSConnectionsToNodes (sockets, done) { - var connectedTo = 0; - var wampClient = new WAMPClient(); - - setTimeout(function () { - testNodeConfigs.forEach(function (testNodeConfig) { - monitorWSClient.port = testNodeConfig.port; - var socket = scClient.connect(monitorWSClient); - wampClient.upgradeToWAMP(socket); - socket.on('connect', function () { - sockets.push(socket); - connectedTo += 1; - if (connectedTo === testNodeConfigs.length) { - done(); - } - }); - socket.on('error', function (err) {}); - socket.on('connectAbort', function (err) { - done('Unable to establish WS connection with ' + testNodeConfig.ip + ':' + testNodeConfig.port); - }); - }, WAIT_BEFORE_CONNECT_MS); - }); -} - -describe('integration', function () { - - var self = this; - var sockets = []; - generatePM2NodesConfig(testNodeConfigs); - - before(function (done) { - async.series([ - function (cbSeries) { - clearLogs(cbSeries); - }, - function (cbSeries) { - recreateDatabases(cbSeries); - }, - function (cbSeries) { - launchTestNodes(cbSeries); - }, - function (cbSeries) { - waitForAllNodesToBeReady(cbSeries); - }, - function (cbSeries) { - enableForgingOnDelegates(cbSeries); - }, - function (cbSeries) { - establishWSConnectionsToNodes(sockets, cbSeries); - self.timeout(WAIT_BEFORE_CONNECT_MS * 2); - } - ], done); - }); - - after(function (done) { - killTestNodes(done); - }); - - describe('Peers mutual connections', function () { - - it('should return a list of peer mutually interconnected', function () { - return Promise.all(sockets.map(function (socket) { - return socket.wampSend('list', {}); - })).then(function (results) { - results.forEach(function (result) { - expect(result).to.have.property('success').to.be.ok; - expect(result).to.have.property('peers').to.be.a('array'); - var peerPorts = result.peers.map(function (p) { - return p.port; - }); - var allPeerPorts = testNodeConfigs.map(function (testNodeConfig) { - return testNodeConfig.port; - }); - expect(_.intersection(allPeerPorts, peerPorts)).to.be.an('array').and.not.to.be.empty; - }); - }); - }); - }); - - describe('forging', function () { - - function getNetworkStatus (cb) { - Promise.all(sockets.map(function (socket) { - return socket.wampSend('status'); - })).then(function (results) { - var maxHeight = 1; - var heightSum = 0; - results.forEach(function (result) { - expect(result).to.have.property('success').to.be.ok; - expect(result).to.have.property('height').to.be.a('number'); - if (result.height > maxHeight) { - maxHeight = result.height; - } - heightSum += result.height; - }); - return cb(null, { - height: maxHeight, - averageHeight: heightSum / results.length - }); - - }).catch(function (err) { - cb(err); - }); - } - - before(function (done) { - // Expect some blocks to forge after 30 seconds - var timesToCheckNetworkStatus = 30; - var timesNetworkStatusChecked = 0; - var checkNetworkStatusInterval = 1000; - - var checkingInterval = setInterval(function () { - getNetworkStatus(function (err, res) { - timesNetworkStatusChecked += 1; - if (err) { - clearInterval(checkingInterval); - return done(err); - } - logger.log('network status: height - ' + res.height + ', average height - ' + res.averageHeight); - if (timesNetworkStatusChecked === timesToCheckNetworkStatus) { - clearInterval(checkingInterval); - return done(null, res); - } - }); - }, checkNetworkStatusInterval); - }); - - describe('network status after 30 seconds', function () { - - var getNetworkStatusError; - var networkHeight; - var networkAverageHeight; - - before(function (done) { - getNetworkStatus(function (err, res) { - getNetworkStatusError = err; - networkHeight = res.height; - networkAverageHeight = res.averageHeight; - done(); - }); - }); - - it('should have no error', function () { - expect(getNetworkStatusError).not.to.exist; - }); - - it('should have height > 1', function () { - expect(networkHeight).to.be.above(1); - }); - - it('should have average height above 1', function () { - expect(networkAverageHeight).to.be.above(1); - }); - - it('should have different peers heights propagated correctly on peers lists', function () { - return Promise.all(sockets.map(function (socket) { - return socket.wampSend('list'); - })).then(function (results) { - expect(results.some(function (peersList) { - return peersList.peers.some(function (peer) { - return peer.height > 1; - }); - })); - }); - }); - }); - }); - - describe('propagation', function () { - - before(function (done) { - runFunctionalTests(done); - }); - - describe('blocks', function () { - - var nodesBlocks; - - before(function () { - return Promise.all(testNodeConfigs.map(function (testNodeConfig) { - return popsicle.get({ - url: 'http://' + testNodeConfig.ip + ':' + (testNodeConfig.port - 1000) + '/api/blocks', - headers: { - 'Accept': 'application/json', - 'Content-Type': 'application/json' - } - }); - })).then(function (results) { - nodesBlocks = results.map(function (res) { - return JSON.parse(res.body).data; - }); - expect(nodesBlocks).to.have.lengthOf(testNodeConfigs.length); - }); - }); - - it('should contain non empty blocks after running functional tests', function () { - nodesBlocks.forEach(function (blocks) { - expect(blocks).to.be.an('array').and.not.empty; - }); - }); - - it('should have all peers at the same height', function () { - var uniquePeersHeights = _(nodesBlocks).map('length').uniq().value(); - expect(uniquePeersHeights).to.have.lengthOf.at.least(1); - }); - - it('should have all blocks the same at all peers', function () { - var patternBlocks = nodesBlocks[0]; - for (var i = 0; i < patternBlocks.length; i += 1) { - for (var j = 1; j < nodesBlocks.length; j += 1) { - expect(_.isEqual(nodesBlocks[j][i], patternBlocks[i])); - } - } - }); - }); - - describe('transactions', function () { - - var nodesTransactions = []; - - before(function () { - return Promise.all(sockets.map(function (socket) { - return socket.wampSend('blocks'); - })).then(function (results) { - nodesTransactions = results.map(function (res) { - return res.blocks; - }); - expect(nodesTransactions).to.have.lengthOf(testNodeConfigs.length); - }); - }); - - it('should contain non empty transactions after running functional tests', function () { - nodesTransactions.forEach(function (transactions) { - expect(transactions).to.be.an('array').and.not.empty; - }); - }); - - it('should have all peers having same amount of confirmed transactions', function () { - var uniquePeersTransactionsNumber = _(nodesTransactions).map('length').uniq().value(); - expect(uniquePeersTransactionsNumber).to.have.lengthOf.at.least(1); - }); - - it('should have all transactions the same at all peers', function () { - var patternTransactions = nodesTransactions[0]; - for (var i = 0; i < patternTransactions.length; i += 1) { - for (var j = 1; j < nodesTransactions.length; j += 1) { - expect(_.isEqual(nodesTransactions[j][i], patternTransactions[i])); - } - } - }); - }); - }); -}); diff --git a/test/integration/pm2.integration.json b/test/integration/pm2.integration.json index d1a220ae405..e74c13718d8 100644 --- a/test/integration/pm2.integration.json +++ b/test/integration/pm2.integration.json @@ -4,7 +4,7 @@ "exec_mode": "fork", "script": "app.js", "name": "node_0", - "args": " -p 5000 -h 4000 -x 127.0.0.1:5001 -d lisk_local_0 -c ./test/integration/configs/config.node-0.json", + "args": " -c ./test/integration/configs/config.node-0.json", "env": { "NODE_ENV": "test" }, @@ -15,7 +15,7 @@ "exec_mode": "fork", "script": "app.js", "name": "node_1", - "args": " -p 5001 -h 4001 -x 127.0.0.1:5001 -d lisk_local_1 -c ./test/integration/configs/config.node-1.json", + "args": " -c ./test/integration/configs/config.node-1.json", "env": { "NODE_ENV": "test" }, @@ -26,7 +26,7 @@ "exec_mode": "fork", "script": "app.js", "name": "node_2", - "args": " -p 5002 -h 4002 -x 127.0.0.1:5001 -d lisk_local_2 -c ./test/integration/configs/config.node-2.json", + "args": " -c ./test/integration/configs/config.node-2.json", "env": { "NODE_ENV": "test" }, @@ -37,7 +37,7 @@ "exec_mode": "fork", "script": "app.js", "name": "node_3", - "args": " -p 5003 -h 4003 -x 127.0.0.1:5001 -d lisk_local_3 -c ./test/integration/configs/config.node-3.json", + "args": " -c ./test/integration/configs/config.node-3.json", "env": { "NODE_ENV": "test" }, @@ -48,67 +48,12 @@ "exec_mode": "fork", "script": "app.js", "name": "node_4", - "args": " -p 5004 -h 4004 -x 127.0.0.1:5001 -d lisk_local_4 -c ./test/integration/configs/config.node-4.json", + "args": " -c ./test/integration/configs/config.node-4.json", "env": { "NODE_ENV": "test" }, "error_file": "./test/integration/logs/lisk-test-node-4.err.log", "out_file": "./test/integration/logs/lisk-test-node-4.out.log" - }, - { - "exec_mode": "fork", - "script": "app.js", - "name": "node_5", - "args": " -p 5005 -h 4005 -x 127.0.0.1:5001 -d lisk_local_5 -c ./test/integration/configs/config.node-5.json", - "env": { - "NODE_ENV": "test" - }, - "error_file": "./test/integration/logs/lisk-test-node-5.err.log", - "out_file": "./test/integration/logs/lisk-test-node-5.out.log" - }, - { - "exec_mode": "fork", - "script": "app.js", - "name": "node_6", - "args": " -p 5006 -h 4006 -x 127.0.0.1:5001 -d lisk_local_6 -c ./test/integration/configs/config.node-6.json", - "env": { - "NODE_ENV": "test" - }, - "error_file": "./test/integration/logs/lisk-test-node-6.err.log", - "out_file": "./test/integration/logs/lisk-test-node-6.out.log" - }, - { - "exec_mode": "fork", - "script": "app.js", - "name": "node_7", - "args": " -p 5007 -h 4007 -x 127.0.0.1:5001 -d lisk_local_7 -c ./test/integration/configs/config.node-7.json", - "env": { - "NODE_ENV": "test" - }, - "error_file": "./test/integration/logs/lisk-test-node-7.err.log", - "out_file": "./test/integration/logs/lisk-test-node-7.out.log" - }, - { - "exec_mode": "fork", - "script": "app.js", - "name": "node_8", - "args": " -p 5008 -h 4008 -x 127.0.0.1:5001 -d lisk_local_8 -c ./test/integration/configs/config.node-8.json", - "env": { - "NODE_ENV": "test" - }, - "error_file": "./test/integration/logs/lisk-test-node-8.err.log", - "out_file": "./test/integration/logs/lisk-test-node-8.out.log" - }, - { - "exec_mode": "fork", - "script": "app.js", - "name": "node_9", - "args": " -p 5009 -h 4009 -x 127.0.0.1:5001 -d lisk_local_9 -c ./test/integration/configs/config.node-9.json", - "env": { - "NODE_ENV": "test" - }, - "error_file": "./test/integration/logs/lisk-test-node-9.err.log", - "out_file": "./test/integration/logs/lisk-test-node-9.out.log" } ] } \ No newline at end of file diff --git a/test/integration/scenarios/index.js b/test/integration/scenarios/index.js new file mode 100644 index 00000000000..e7586f24ce4 --- /dev/null +++ b/test/integration/scenarios/index.js @@ -0,0 +1,11 @@ +'use strict'; + +module.exports = { + network: { + peers: require('./network/peers') + }, + propagation: { + blocks: require('./propagation/blocks'), + transactions: require('./propagation/transactions') + } +}; diff --git a/test/integration/scenarios/network/peers.js b/test/integration/scenarios/network/peers.js new file mode 100644 index 00000000000..c37ef05bcfe --- /dev/null +++ b/test/integration/scenarios/network/peers.js @@ -0,0 +1,119 @@ +'use strict'; + +var _ = require('lodash'); +var expect = require('chai').expect; +var Promise = require('bluebird'); +var utils = require('../../utils'); + +module.exports = function (params) { + + describe('Peers mutual connections', function () { + + it('should return a list of peers mutually interconnected', function () { + return Promise.all(params.sockets.map(function (socket) { + return socket.wampSend('list', {}); + })).then(function (results) { + results.forEach(function (result) { + expect(result).to.have.property('success').to.be.true; + expect(result).to.have.property('peers').to.be.a('array'); + var peerPorts = result.peers.map(function (peer) { + return peer.port; + }); + var allPorts = params.configurations.map(function (configuration) { + return configuration.port; + }); + expect(_.intersection(allPorts, peerPorts)).to.be.an('array').and.not.to.be.empty; + }); + }); + }); + }); + + describe('forging', function () { + + function getNetworkStatus (cb) { + Promise.all(params.sockets.map(function (socket) { + return socket.wampSend('status'); + })).then(function (results) { + var maxHeight = 1; + var heightSum = 0; + results.forEach(function (result) { + expect(result).to.have.property('success').to.be.true; + expect(result).to.have.property('height').to.be.a('number'); + if (result.height > maxHeight) { + maxHeight = result.height; + } + heightSum += result.height; + }); + return cb(null, { + height: maxHeight, + averageHeight: heightSum / results.length + }); + + }).catch(function (err) { + cb(err); + }); + } + + before(function (done) { + // Expect some blocks to forge after 30 seconds + var timesToCheckNetworkStatus = 30; + var timesNetworkStatusChecked = 0; + var checkNetworkStatusInterval = 1000; + + var checkingInterval = setInterval(function () { + getNetworkStatus(function (err, res) { + timesNetworkStatusChecked += 1; + if (err) { + clearInterval(checkingInterval); + return done(err); + } + utils.logger.log('network status: height - ' + res.height + ', average height - ' + res.averageHeight); + if (timesNetworkStatusChecked === timesToCheckNetworkStatus) { + clearInterval(checkingInterval); + return done(null, res); + } + }); + }, checkNetworkStatusInterval); + }); + + describe('network status after 30 seconds', function () { + + var getNetworkStatusError; + var networkHeight; + var networkAverageHeight; + + before(function (done) { + getNetworkStatus(function (err, res) { + getNetworkStatusError = err; + networkHeight = res.height; + networkAverageHeight = res.averageHeight; + done(); + }); + }); + + it('should have no error', function () { + expect(getNetworkStatusError).not.to.exist; + }); + + it('should have height > 1', function () { + expect(networkHeight).to.be.above(1); + }); + + it('should have average height above 1', function () { + expect(networkAverageHeight).to.be.above(1); + }); + + it('should have different peers heights propagated correctly on peers lists', function () { + return Promise.all(params.sockets.map(function (socket) { + return socket.wampSend('list', {}); + })).then(function (results) { + expect(results.some(function (peersList) { + return peersList.peers.some(function (peer) { + return peer.height > 1; + }); + })); + }); + }); + }); + }); +}; diff --git a/test/integration/scenarios/propagation/blocks.js b/test/integration/scenarios/propagation/blocks.js new file mode 100644 index 00000000000..ad9a93a7c16 --- /dev/null +++ b/test/integration/scenarios/propagation/blocks.js @@ -0,0 +1 @@ +'use strict'; diff --git a/test/integration/scenarios/propagation/transactions.js b/test/integration/scenarios/propagation/transactions.js new file mode 100644 index 00000000000..ad9a93a7c16 --- /dev/null +++ b/test/integration/scenarios/propagation/transactions.js @@ -0,0 +1 @@ +'use strict'; diff --git a/test/integration/setup/index.js b/test/integration/setup/index.js new file mode 100644 index 00000000000..9d283e1e067 --- /dev/null +++ b/test/integration/setup/index.js @@ -0,0 +1,45 @@ +'use strict'; + +var async = require('async'); +var network = require('./network'); +var pm2 = require('./pm2'); +var shell = require('./shell'); +var sync = require('./sync'); + +var WAIT_BEFORE_CONNECT_MS = 20000; + +module.exports = { + + setupNetwork: function (configurations, cb) { + async.series([ + function (cbSeries) { + pm2.generatePM2Configuration(configurations, cbSeries); + }, + function (cbSeries) { + shell.recreateDatabases(configurations, cbSeries); + }, + function (cbSeries) { + shell.launchTestNodes(cbSeries); + }, + function (cbSeries) { + network.waitForAllNodesToBeReady(configurations, cbSeries); + }, + function (cbSeries) { + network.enableForgingOnDelegates(configurations, cbSeries); + }, + function (cbSeries) { + setTimeout(cbSeries, WAIT_BEFORE_CONNECT_MS); + } + ], function (err, res) { + return cb(err, res); + }); + }, + + exit: function (cb) { + shell.killTestNodes(cb); + }, + network: network, + pm2: pm2, + shell: shell, + sync: sync +}; diff --git a/test/integration/setup/network.js b/test/integration/setup/network.js new file mode 100644 index 00000000000..d4f4dd06003 --- /dev/null +++ b/test/integration/setup/network.js @@ -0,0 +1,34 @@ +'use strict'; + +var async = require('async'); +var Promise = require('bluebird'); +var waitUntilBlockchainReady = require('../../common/globalBefore').waitUntilBlockchainReady; +var utils = require('../utils'); + +module.exports = { + + waitForAllNodesToBeReady: function (configurations, cb) { + async.forEachOf(configurations, function (configuration, index, eachCb) { + waitUntilBlockchainReady(eachCb, 20, 2000, 'http://' + configuration.ip + ':' + configuration.httpPort); + }, cb); + }, + + enableForgingOnDelegates: function (configurations, cb) { + var enableForgingPromises = []; + configurations.forEach(function (configuration) { + configuration.forging.secret.map(function (keys) { + var enableForgingPromise = utils.http.enableForging(keys, configuration.httpPort); + enableForgingPromises.push(enableForgingPromise); + }); + }); + Promise.all(enableForgingPromises).then(function (forgingResults) { + return cb(forgingResults.map(function (result) { + return result.forging; + }).some(function (isForging) { + return !isForging; + }) ? 'Enabling forging failed for some of delegates' : null); + }).catch(function (error) { + return cb(error); + }); + } +}; diff --git a/test/integration/setup/pm2.js b/test/integration/setup/pm2.js new file mode 100644 index 00000000000..55203bc9c58 --- /dev/null +++ b/test/integration/setup/pm2.js @@ -0,0 +1,36 @@ +'use strict'; + +var fs = require('fs'); + +module.exports = { + + generatePM2Configuration: function (configurations, cb) { + var pm2Config = configurations.reduce(function (pm2Config, configuration) { + var index = pm2Config.apps.length; + configuration.db.database = configuration.db.database + '_' + index; + try { + fs.writeFileSync(__dirname + '/../configs/config.node-' + index + '.json', JSON.stringify(configuration, null, 4)); + } catch (ex) { + return cb(ex); + } + pm2Config.apps.push({ + 'exec_mode': 'fork', + 'script': 'app.js', + 'name': 'node_' + index, + 'args': ' -c ./test/integration/configs/config.node-' + index + '.json', + 'env': { + 'NODE_ENV': 'test' + }, + 'error_file': './test/integration/logs/lisk-test-node-' + index + '.err.log', + 'out_file': './test/integration/logs/lisk-test-node-' + index + '.out.log' + }); + return pm2Config; + }, {apps: []}); + try { + fs.writeFileSync(__dirname + '/../pm2.integration.json', JSON.stringify(pm2Config, null, 4)); + } catch (ex) { + return cb(ex); + } + return cb(); + } +}; diff --git a/test/integration/setup/shell.js b/test/integration/setup/shell.js new file mode 100644 index 00000000000..2679aa32581 --- /dev/null +++ b/test/integration/setup/shell.js @@ -0,0 +1,30 @@ +'use strict'; + +var async = require('async'); +var child_process = require('child_process'); + +module.exports = { + + recreateDatabases: function (configurations, cb) { + async.forEachOf(configurations, function (configuration, index, eachCb) { + child_process.exec('dropdb ' + configuration.db.database + '; createdb ' + configuration.db.database, eachCb); + }, cb); + }, + + launchTestNodes: function (cb) { + child_process.exec('node_modules/.bin/pm2 start test/integration/pm2.integration.json', function (err) { + return cb(err); + }); + }, + + killTestNodes: function (cb) { + child_process.exec('node_modules/.bin/pm2 kill', function (err) { + if (err) { + console.warn('Failed to killed PM2 process. Please execute command "pm2 kill" manually'); + } else { + console.info('PM2 process killed gracefully'); + } + return cb(err); + }); + } +}; diff --git a/test/integration/setup/sync.js b/test/integration/setup/sync.js new file mode 100644 index 00000000000..f13da2ce555 --- /dev/null +++ b/test/integration/setup/sync.js @@ -0,0 +1,74 @@ +'use strict'; + +var SYNC_MODES = { + RANDOM: 0, + ALL_TO_FIRST: 1, + ALL_TO_GROUP: 2 +}; + +var SYNC_MODE_DEFAULT_ARGS = { + RANDOM: { + probability: 0.5 // (0 - 1) + }, + ALL_TO_GROUP: { + indices: [] + } +}; + +module.exports = { + + SYNC_MODES: SYNC_MODES, + + // generatePeersSynchronizedWithSpecifiedGroup(), + // + // generatePeersSynchronizedWithFirst(), + // + // generatePeersSynchronizedWithRandomGroup(), + + generatePeers: function (configurations, syncMode, syncModeArgs) { + syncModeArgs = syncModeArgs || SYNC_MODE_DEFAULT_ARGS[syncMode]; + var peersList = []; + switch (syncMode) { + case SYNC_MODES.RANDOM: + if (typeof syncModeArgs.probability !== 'number') { + throw new Error('Probability parameter not specified to random sync mode'); + } + var isPickedWithProbability = function (n) { + return !!n && Math.random() <= n; + }; + configurations.forEach(function (configuration) { + if (isPickedWithProbability(syncModeArgs.probability)) { + peersList.push({ + ip: configuration.ip, + port: configuration.port + }); + } + }); + break; + + case SYNC_MODES.ALL_TO_FIRST: + if (configurations.length === 0) { + throw new Error('No configurations provided'); + } + peersList = [{ + ip: configurations[0].ip, + port: configurations[0].port + }]; + break; + + case SYNC_MODES.ALL_TO_GROUP: + if (!Array.isArray(syncModeArgs.indices)) { + throw new Error('Provide peers indices to sync with as an array'); + } + configurations.forEach(function (configuration, index) { + if (syncModeArgs.indices.indexOf(index) !== -1) { + peersList.push({ + ip: configuration.ip, + port: configuration.port + }); + } + }); + } + return peersList; + } +}; diff --git a/test/integration/transport.js b/test/integration/transport.js new file mode 100644 index 00000000000..b993ac1fd89 --- /dev/null +++ b/test/integration/transport.js @@ -0,0 +1,74 @@ +'use strict'; + +var _ = require('lodash'); +var devConfig = require('../config.json'); +var utils = require('./utils'); +var setup = require('./setup'); +var scenarios = require('./scenarios'); + +describe('given configurations for 10 nodes with address "127.0.0.1", WS ports 500[0-9] and HTTP ports 400[0-9] using separate databases', function () { + + var configurations; + + before(function () { + utils.http.setVersion('1.0.0'); + configurations = _.range(10).map(function (index) { + var devConfigCopy = _.cloneDeep(devConfig); + devConfigCopy.ip = '127.0.0.1'; + devConfigCopy.port = 5000 + index; + devConfigCopy.httpPort = 4000 + index; + return devConfigCopy; + }); + }); + + describe('when every peers contains the others on the peers list', function () { + + before(function () { + configurations.forEach(function (configuration) { + configuration.peers.list = setup.sync.generatePeers(configurations, setup.sync.SYNC_MODES.ALL_TO_GROUP, {indices: _.range(10)}); + }); + }); + + describe('when every peer forges with separate subset of genesis delegates and forging.force = false', function () { + + before(function () { + var secretsMaxLength = Math.ceil(devConfig.forging.secret.length / configurations.length); + var secrets = _.clone(devConfig.forging.secret); + configurations.forEach(function (configuration, index) { + configuration.forging.force = false; + configuration.forging.secret = secrets.slice(index * secretsMaxLength, (index + 1) * secretsMaxLength); + console.log('configuration_ ', configuration.port, configuration.forging.secret.length); + }); + }); + + describe('when network is set up', function () { + + before(function (done) { + setup.setupNetwork(configurations, done); + }); + + after(function (done) { + setup.exit(done); + }); + + describe('when WS connections to all nodes all established', function () { + + var params = {}; + + before(function (done) { + utils.ws.establishWSConnectionsToNodes(configurations, function (err, socketsResult) { + if (err) { + return done(err); + } + params.sockets = socketsResult; + params.configurations = configurations; + done(); + }); + }); + + scenarios.network.peers(params); + }); + }); + }); + }); +}); diff --git a/test/integration/utils/http.js b/test/integration/utils/http.js new file mode 100644 index 00000000000..c0cf9f91f0e --- /dev/null +++ b/test/integration/utils/http.js @@ -0,0 +1,142 @@ +'use strict'; + +var popsicle = require('popsicle'); +var apiCodes = require('../../../helpers/apiCodes'); + +var headers = { + 'Accept': 'application/json', + 'Content-Type': 'application/x-www-form-urlencoded' +}; + +var endpoints = { + versions: { + '0.9.*': { + getBlocks: function (ip, port) { + return 'http://' + ip + ':' + port + '/api/blocks'; + }, + getHeight: function (ip, port) { + return 'http://' + ip + ':' + port + '/api/blocks/getHeight'; + }, + getTransactions: function (ip, port) { + return 'http://' + ip + ':' + port + '/peer/blocks'; + }, + postTransaction: function (ip, port) { + return 'http://' + ip + ':' + port + '/peer/transactions'; + }, + enableForging: function (ip, port) { + return 'http://' + ip + ':' + port + '/api/delegates/forging/enable'; + } + }, + '1.0.0': { + getBlocks: function (ip, port) { + return 'http://' + ip + ':' + port + '/api/blocks'; + }, + getHeight: function (ip, port) { + return 'http://' + ip + ':' + port + '/api/node/status'; + }, + postTransaction: function (ip, port) { + return 'http://' + ip + ':' + port + '/api/transactions'; + }, + enableForging: function (ip, port) { + return 'http://' + ip + ':' + port + '/api/delegates/forging'; + }, + getTransactions: function (ip, port) { + return 'http://' + ip + ':' + port + '/api/transactions'; + } + } + } +}; + +var currentVersion = '1.0.0'; + +module.exports = { + + getVersion: function () { + return currentVersion; + }, + + setVersion: function (version) { + currentVersion = version; + }, + + getBlocks: function (port, ip) { + return popsicle.get({ + url: endpoints.versions[currentVersion].getBlocks(ip || '127.0.0.1', port || 4000), + headers: headers + }).then(function (res) { + return res.body.blocks; + }); + }, + + getHeight: function (port, ip) { + return popsicle.get({ + url: endpoints.versions[currentVersion].getHeight(ip || '127.0.0.1', port || 4000), + headers: headers + }).then(function (res) { + return res.body.height; + }); + }, + + getTransactions: function (port, ip) { + return popsicle.get({ + url: endpoints.versions[currentVersion].getTransactions(ip || '127.0.0.1', port || 4000), + headers: headers + }).then(function (res) { + return res.body.blocks; + }); + }, + + postTransaction: function (transaction, port, ip) { + return popsicle.post({ + url: endpoints.versions[currentVersion].postTransaction(ip || '127.0.0.1', port || 4000), + headers: headers, + data: { + transaction: transaction + } + }).then(function (res) { + return res.body.blocks; + }); + }, + + enableForging: function (keys, port, ip) { + return popsicle.put({ + url: endpoints.versions[currentVersion].enableForging(ip || '127.0.0.1', port || 4000), + headers: headers, + body: { + key: 'elephant tree paris dragon chair galaxy', + publicKey: keys.publicKey + } + }).then(function (res) { + console.log('forging enabled res:', res.body, res.status); + if (res.status !== apiCodes.OK) { + throw new Error('Unable to enable forging for delagate with publicKey: ' + keys.publicKey); + } + return JSON.parse(res.body); + }).catch(function (err) { + console.error(err); + throw new Error('Unable to enable forging for delagate with publicKey: ' + keys.publicKey); + }); + } +}; + +// var enableForging = function (keys, port, ip) { +// return popsicle.put({ +// url: endpoints.versions[currentVersion].enableForging(ip || '127.0.0.1', port || 4000), +// headers: headers, +// body: { +// key: 'elephant tree paris dragon chair galaxy', +// publicKey: keys.publicKey +// } +// }).then(function (res) { +// if (res.statusCode !== apiCodes.OK) { +// throw new Error('Unable to enable forging for delagate with publicKey: ' + keys.publicKey); +// } +// return res.body.forging; +// }).catch(function () { +// throw new Error('Unable to enable forging for delagate with publicKey: ' + keys.publicKey); +// }); +// }; +// +// enableForging({ +// "publicKey": "3ff32442bb6da7d60c1b7752b24e6467813c9b698e0f278d48c43580da972135" +// }); diff --git a/test/integration/utils/index.js b/test/integration/utils/index.js new file mode 100644 index 00000000000..25aec5e5799 --- /dev/null +++ b/test/integration/utils/index.js @@ -0,0 +1,10 @@ +'use strict'; + +var Logger = require('../../../logger'); + +module.exports = { + http: require('./http'), + ws: require('./ws'), + transactions: require('./transactions'), + logger: new Logger({filename: __dirname + '/integrationTestsLogger.logs', echo: 'log'}) +}; diff --git a/test/integration/utils/transactions.js b/test/integration/utils/transactions.js new file mode 100644 index 00000000000..690ab45d0af --- /dev/null +++ b/test/integration/utils/transactions.js @@ -0,0 +1,12 @@ +'use strict'; + +var lisk = require('lisk-js'); + +module.exports = { + + generateValidTransaction: function () { + var gAccountPassphrase = 'wagon stock borrow episode laundry kitten salute link globe zero feed marble'; + var randomAddress = lisk.crypto.getAddress(lisk.crypto.getKeys(Math.random().toString(36).substring(7)).publicKey); + return lisk.transaction.createTransaction(randomAddress, 1, gAccountPassphrase); + } +}; diff --git a/test/integration/utils/ws.js b/test/integration/utils/ws.js new file mode 100644 index 00000000000..d0b6102a222 --- /dev/null +++ b/test/integration/utils/ws.js @@ -0,0 +1,37 @@ +'use strict'; + +var WAMPClient = require('wamp-socket-cluster/WAMPClient'); +var WSClient = require('../../common/wsClient'); +var scClient = require('socketcluster-client'); + +module.exports = { + + establishWSConnectionsToNodes: function (configurations, cb) { + var wampClient = new WAMPClient(); + var sockets = []; + var monitorWSClient = { + protocol: 'http', + hostname: '127.0.0.1', + port: null, + autoReconnect: true, + query: WSClient.generatePeerHeaders() + }; + var connectedTo = 0; + configurations.forEach(function (configuration) { + monitorWSClient.port = configuration.port; + var socket = scClient.connect(monitorWSClient); + wampClient.upgradeToWAMP(socket); + socket.on('connect', function () { + sockets.push(socket); + connectedTo += 1; + if (connectedTo === configurations.length) { + cb(null, sockets); + } + }); + socket.on('error', function (err) {}); + socket.on('connectAbort', function () { + cb('Unable to establish WS connection with ' + configuration.ip + ':' + configuration.port); + }); + }); + } +}; From ee5c4ff2473f0af3063eb9f81ef243ccd0077cb5 Mon Sep 17 00:00:00 2001 From: Maciej Baj Date: Thu, 23 Nov 2017 18:16:29 +0100 Subject: [PATCH 02/14] Add propagation setup in transport integration tests --- test/integration/setup/shell.js | 20 ++++++++++++++++++++ test/integration/transport.js | 13 ++++++++++++- test/integration/utils/http.js | 28 ++++------------------------ 3 files changed, 36 insertions(+), 25 deletions(-) diff --git a/test/integration/setup/shell.js b/test/integration/setup/shell.js index 2679aa32581..d9292a14b9d 100644 --- a/test/integration/setup/shell.js +++ b/test/integration/setup/shell.js @@ -17,6 +17,26 @@ module.exports = { }); }, + runMochaTests: function (testsPaths, cb) { + var child = child_process.spawn('node_modules/.bin/_mocha', ['--timeout', (8 * 60 * 1000).toString(), '--exit'].concat(testsPaths), { + cwd: __dirname + '/../../..' + }); + + child.stdout.pipe(process.stdout); + + child.on('close', function (code) { + if (code === 0) { + return cb(); + } else { + return cb('Functional tests failed'); + } + }); + + child.on('error', function (err) { + return cb(err); + }); + }, + killTestNodes: function (cb) { child_process.exec('node_modules/.bin/pm2 kill', function (err) { if (err) { diff --git a/test/integration/transport.js b/test/integration/transport.js index b993ac1fd89..7fd78ec626b 100644 --- a/test/integration/transport.js +++ b/test/integration/transport.js @@ -1,6 +1,7 @@ 'use strict'; var _ = require('lodash'); +var sinon = require('sinon'); var devConfig = require('../config.json'); var utils = require('./utils'); var setup = require('./setup'); @@ -37,7 +38,6 @@ describe('given configurations for 10 nodes with address "127.0.0.1", WS ports 5 configurations.forEach(function (configuration, index) { configuration.forging.force = false; configuration.forging.secret = secrets.slice(index * secretsMaxLength, (index + 1) * secretsMaxLength); - console.log('configuration_ ', configuration.port, configuration.forging.secret.length); }); }); @@ -67,6 +67,17 @@ describe('given configurations for 10 nodes with address "127.0.0.1", WS ports 5 }); scenarios.network.peers(params); + + describe('when functional tests are successfully executed against 127.0.0.1:5000', function () { + + before(function (done) { + setup.shell.runMochaTests(['test/functional/http/get/blocks.js'], done); + }); + + scenarios.propagation.blocks(params); + + scenarios.propagation.transactions(params); + }); }); }); }); diff --git a/test/integration/utils/http.js b/test/integration/utils/http.js index c0cf9f91f0e..b9f7bdc06f4 100644 --- a/test/integration/utils/http.js +++ b/test/integration/utils/http.js @@ -64,7 +64,10 @@ module.exports = { url: endpoints.versions[currentVersion].getBlocks(ip || '127.0.0.1', port || 4000), headers: headers }).then(function (res) { - return res.body.blocks; + if (res.status !== apiCodes.OK) { + throw new Error('Unable to get blocks from peer: ' + res.body); + } + return JSON.parse(res.body).blocks; }); }, @@ -107,7 +110,6 @@ module.exports = { publicKey: keys.publicKey } }).then(function (res) { - console.log('forging enabled res:', res.body, res.status); if (res.status !== apiCodes.OK) { throw new Error('Unable to enable forging for delagate with publicKey: ' + keys.publicKey); } @@ -118,25 +120,3 @@ module.exports = { }); } }; - -// var enableForging = function (keys, port, ip) { -// return popsicle.put({ -// url: endpoints.versions[currentVersion].enableForging(ip || '127.0.0.1', port || 4000), -// headers: headers, -// body: { -// key: 'elephant tree paris dragon chair galaxy', -// publicKey: keys.publicKey -// } -// }).then(function (res) { -// if (res.statusCode !== apiCodes.OK) { -// throw new Error('Unable to enable forging for delagate with publicKey: ' + keys.publicKey); -// } -// return res.body.forging; -// }).catch(function () { -// throw new Error('Unable to enable forging for delagate with publicKey: ' + keys.publicKey); -// }); -// }; -// -// enableForging({ -// "publicKey": "3ff32442bb6da7d60c1b7752b24e6467813c9b698e0f278d48c43580da972135" -// }); From d396d8fd8f37d39b6c63a861a272e15fbe346cc1 Mon Sep 17 00:00:00 2001 From: Maciej Baj Date: Thu, 23 Nov 2017 18:17:01 +0100 Subject: [PATCH 03/14] Add integration tests for blocks propagation --- .../scenarios/propagation/blocks.js | 45 +++++++++++++++++++ 1 file changed, 45 insertions(+) diff --git a/test/integration/scenarios/propagation/blocks.js b/test/integration/scenarios/propagation/blocks.js index ad9a93a7c16..4294f1637a4 100644 --- a/test/integration/scenarios/propagation/blocks.js +++ b/test/integration/scenarios/propagation/blocks.js @@ -1 +1,46 @@ 'use strict'; + +var _ = require('lodash'); +var expect = require('chai').expect; +var Promise = require('bluebird'); +var utils = require('../../utils'); + +module.exports = function (params) { + + describe('blocks', function () { + + var nodesBlocks; + + before(function () { + return Promise.all(params.configurations.map(function (configuration) { + return utils.http.getBlocks(configuration.httpPort).then(function (blocksResults) { + nodesBlocks = blocksResults; + }); + })); + }); + + it('should be able to get blocks list from every peer', function () { + expect(nodesBlocks).to.have.lengthOf(params.configurations.length); + }); + + it('should contain non empty blocks after running functional tests', function () { + nodesBlocks.forEach(function (blocks) { + expect(blocks).to.be.an('array').and.not.empty; + }); + }); + + it('should have all peers at the same height', function () { + var uniquePeersHeights = _(nodesBlocks).map('length').uniq().value(); + expect(uniquePeersHeights).to.have.lengthOf.at.least(1); + }); + + it('should have all blocks the same at all peers', function () { + var patternBlocks = nodesBlocks[0]; + for (var i = 0; i < patternBlocks.length; i += 1) { + for (var j = 1; j < nodesBlocks.length; j += 1) { + expect(_.isEqual(nodesBlocks[j][i], patternBlocks[i])); + } + } + }); + }); +}; From 97d0111db1a6cef5e5751b482185391e14190744 Mon Sep 17 00:00:00 2001 From: Maciej Baj Date: Thu, 23 Nov 2017 18:17:24 +0100 Subject: [PATCH 04/14] Add integration tests for transactions propagation --- .../scenarios/propagation/transactions.js | 43 +++++++++++++++++++ 1 file changed, 43 insertions(+) diff --git a/test/integration/scenarios/propagation/transactions.js b/test/integration/scenarios/propagation/transactions.js index ad9a93a7c16..fd507fb636f 100644 --- a/test/integration/scenarios/propagation/transactions.js +++ b/test/integration/scenarios/propagation/transactions.js @@ -1 +1,44 @@ 'use strict'; + +var _ = require('lodash'); +var expect = require('chai').expect; +var Promise = require('bluebird'); + +module.exports = function (params) { + + describe('blocks', function () { + + var nodesTransactions = []; + + before(function () { + return Promise.all(params.sockets.map(function (socket) { + return socket.wampSend('blocks'); + })).then(function (results) { + nodesTransactions = results.map(function (res) { + return res.blocks; + }); + expect(nodesTransactions).to.have.lengthOf(params.configurations.length); + }); + }); + + it('should contain non empty transactions after running functional tests', function () { + nodesTransactions.forEach(function (transactions) { + expect(transactions).to.be.an('array').and.not.empty; + }); + }); + + it('should have all peers having same amount of confirmed transactions', function () { + var uniquePeersTransactionsNumber = _(nodesTransactions).map('length').uniq().value(); + expect(uniquePeersTransactionsNumber).to.have.lengthOf.at.least(1); + }); + + it('should have all transactions the same at all peers', function () { + var patternTransactions = nodesTransactions[0]; + for (var i = 0; i < patternTransactions.length; i += 1) { + for (var j = 1; j < nodesTransactions.length; j += 1) { + expect(_.isEqual(nodesTransactions[j][i], patternTransactions[i])); + } + } + }); + }); +}; From 5fd953607e7b8c0ba8bd62a8a19e63615a7d35f9 Mon Sep 17 00:00:00 2001 From: Maciej Baj Date: Thu, 23 Nov 2017 18:29:03 +0100 Subject: [PATCH 05/14] Add transactions functional tests to be executed during integration --- test/integration/scenarios/propagation/blocks.js | 2 +- test/integration/transport.js | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/test/integration/scenarios/propagation/blocks.js b/test/integration/scenarios/propagation/blocks.js index 4294f1637a4..8241cf660da 100644 --- a/test/integration/scenarios/propagation/blocks.js +++ b/test/integration/scenarios/propagation/blocks.js @@ -25,7 +25,7 @@ module.exports = function (params) { it('should contain non empty blocks after running functional tests', function () { nodesBlocks.forEach(function (blocks) { - expect(blocks).to.be.an('array').and.not.empty; + expect(blocks).to.be.an('array').and.not.to.be.empty; }); }); diff --git a/test/integration/transport.js b/test/integration/transport.js index 7fd78ec626b..91780dcbc77 100644 --- a/test/integration/transport.js +++ b/test/integration/transport.js @@ -71,7 +71,7 @@ describe('given configurations for 10 nodes with address "127.0.0.1", WS ports 5 describe('when functional tests are successfully executed against 127.0.0.1:5000', function () { before(function (done) { - setup.shell.runMochaTests(['test/functional/http/get/blocks.js'], done); + setup.shell.runMochaTests(['test/functional/http/get/blocks.js', 'test/functional/http/get/transactions.js'], done); }); scenarios.propagation.blocks(params); From 7f89358fdb9ecb029134525be1fecc31d87045f6 Mon Sep 17 00:00:00 2001 From: Maciej Baj Date: Thu, 23 Nov 2017 19:04:25 +0100 Subject: [PATCH 06/14] Add logs during network setup steps --- test/integration/setup/index.js | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/test/integration/setup/index.js b/test/integration/setup/index.js index 9d283e1e067..0474b71fc5e 100644 --- a/test/integration/setup/index.js +++ b/test/integration/setup/index.js @@ -5,7 +5,7 @@ var network = require('./network'); var pm2 = require('./pm2'); var shell = require('./shell'); var sync = require('./sync'); - +var utils = require('../utils'); var WAIT_BEFORE_CONNECT_MS = 20000; module.exports = { @@ -13,21 +13,27 @@ module.exports = { setupNetwork: function (configurations, cb) { async.series([ function (cbSeries) { + utils.logger.log('Generating PM2 configuration'); pm2.generatePM2Configuration(configurations, cbSeries); }, function (cbSeries) { + utils.logger.log('Recreating databases'); shell.recreateDatabases(configurations, cbSeries); }, function (cbSeries) { + utils.logger.log('Launching network'); shell.launchTestNodes(cbSeries); }, function (cbSeries) { + utils.logger.log('Waiting for nodes to load the blockchain'); network.waitForAllNodesToBeReady(configurations, cbSeries); }, function (cbSeries) { + utils.logger.log('Enabling forging with registered delegates'); network.enableForgingOnDelegates(configurations, cbSeries); }, function (cbSeries) { + utils.logger.log('Waiting ' + WAIT_BEFORE_CONNECT_MS / 1000 + ' seconds for nodes to establish connections'); setTimeout(cbSeries, WAIT_BEFORE_CONNECT_MS); } ], function (err, res) { From 02099603943ba29394cbd4d87b58af009e5592df Mon Sep 17 00:00:00 2001 From: Maciej Baj Date: Thu, 23 Nov 2017 19:07:37 +0100 Subject: [PATCH 07/14] Fix nodesBlocks assignment in blocks propagation integration tests --- test/integration/scenarios/propagation/blocks.js | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/test/integration/scenarios/propagation/blocks.js b/test/integration/scenarios/propagation/blocks.js index 8241cf660da..7d9c4cf26c4 100644 --- a/test/integration/scenarios/propagation/blocks.js +++ b/test/integration/scenarios/propagation/blocks.js @@ -13,10 +13,10 @@ module.exports = function (params) { before(function () { return Promise.all(params.configurations.map(function (configuration) { - return utils.http.getBlocks(configuration.httpPort).then(function (blocksResults) { - nodesBlocks = blocksResults; - }); - })); + return utils.http.getBlocks(configuration.httpPort); + })).then(function (blocksResults) { + nodesBlocks = blocksResults; + }); }); it('should be able to get blocks list from every peer', function () { From 4c48a55121fa26bf39c41fa04c5e9f7b12e96a9c Mon Sep 17 00:00:00 2001 From: Maciej Baj Date: Thu, 23 Nov 2017 19:14:16 +0100 Subject: [PATCH 08/14] Fix delegate spellings --- test/integration/utils/http.js | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/test/integration/utils/http.js b/test/integration/utils/http.js index b9f7bdc06f4..19b95f79ea1 100644 --- a/test/integration/utils/http.js +++ b/test/integration/utils/http.js @@ -111,12 +111,11 @@ module.exports = { } }).then(function (res) { if (res.status !== apiCodes.OK) { - throw new Error('Unable to enable forging for delagate with publicKey: ' + keys.publicKey); + throw new Error('Unable to enable forging for delegate with publicKey: ' + keys.publicKey); } return JSON.parse(res.body); - }).catch(function (err) { - console.error(err); - throw new Error('Unable to enable forging for delagate with publicKey: ' + keys.publicKey); + }).catch(function () { + throw new Error('Unable to enable forging for delegate with publicKey: ' + keys.publicKey); }); } }; From 26758ccbf559b0b15ba9fce40e481f4be08838fb Mon Sep 17 00:00:00 2001 From: Maciej Baj Date: Thu, 23 Nov 2017 19:15:26 +0100 Subject: [PATCH 09/14] Remove unnecessary comments from setup/sync.js --- test/integration/setup/sync.js | 6 ------ 1 file changed, 6 deletions(-) diff --git a/test/integration/setup/sync.js b/test/integration/setup/sync.js index f13da2ce555..252038c1ae6 100644 --- a/test/integration/setup/sync.js +++ b/test/integration/setup/sync.js @@ -19,12 +19,6 @@ module.exports = { SYNC_MODES: SYNC_MODES, - // generatePeersSynchronizedWithSpecifiedGroup(), - // - // generatePeersSynchronizedWithFirst(), - // - // generatePeersSynchronizedWithRandomGroup(), - generatePeers: function (configurations, syncMode, syncModeArgs) { syncModeArgs = syncModeArgs || SYNC_MODE_DEFAULT_ARGS[syncMode]; var peersList = []; From 09858cafd61b59ffd81a6047d84b2392e5dfefcb Mon Sep 17 00:00:00 2001 From: Maciej Baj Date: Thu, 23 Nov 2017 19:16:54 +0100 Subject: [PATCH 10/14] Remove pm2 automatically generated config and add it to .gitignore --- .gitignore | 1 + test/integration/pm2.integration.json | 59 --------------------------- 2 files changed, 1 insertion(+), 59 deletions(-) delete mode 100644 test/integration/pm2.integration.json diff --git a/.gitignore b/.gitignore index f86d71983e3..9c5fcfaa8d7 100644 --- a/.gitignore +++ b/.gitignore @@ -17,6 +17,7 @@ sftp-config.json *.swo tmux-client-*.log Session.vim +test/integration/pm2.integration.json test/integration/logs/ test/integration/configs/ !test/integration/configs/config.non-forge.json diff --git a/test/integration/pm2.integration.json b/test/integration/pm2.integration.json deleted file mode 100644 index e74c13718d8..00000000000 --- a/test/integration/pm2.integration.json +++ /dev/null @@ -1,59 +0,0 @@ -{ - "apps": [ - { - "exec_mode": "fork", - "script": "app.js", - "name": "node_0", - "args": " -c ./test/integration/configs/config.node-0.json", - "env": { - "NODE_ENV": "test" - }, - "error_file": "./test/integration/logs/lisk-test-node-0.err.log", - "out_file": "./test/integration/logs/lisk-test-node-0.out.log" - }, - { - "exec_mode": "fork", - "script": "app.js", - "name": "node_1", - "args": " -c ./test/integration/configs/config.node-1.json", - "env": { - "NODE_ENV": "test" - }, - "error_file": "./test/integration/logs/lisk-test-node-1.err.log", - "out_file": "./test/integration/logs/lisk-test-node-1.out.log" - }, - { - "exec_mode": "fork", - "script": "app.js", - "name": "node_2", - "args": " -c ./test/integration/configs/config.node-2.json", - "env": { - "NODE_ENV": "test" - }, - "error_file": "./test/integration/logs/lisk-test-node-2.err.log", - "out_file": "./test/integration/logs/lisk-test-node-2.out.log" - }, - { - "exec_mode": "fork", - "script": "app.js", - "name": "node_3", - "args": " -c ./test/integration/configs/config.node-3.json", - "env": { - "NODE_ENV": "test" - }, - "error_file": "./test/integration/logs/lisk-test-node-3.err.log", - "out_file": "./test/integration/logs/lisk-test-node-3.out.log" - }, - { - "exec_mode": "fork", - "script": "app.js", - "name": "node_4", - "args": " -c ./test/integration/configs/config.node-4.json", - "env": { - "NODE_ENV": "test" - }, - "error_file": "./test/integration/logs/lisk-test-node-4.err.log", - "out_file": "./test/integration/logs/lisk-test-node-4.out.log" - } - ] -} \ No newline at end of file From dfa9c0f4089e62ea5444e41e98b199e87de0a746 Mon Sep 17 00:00:00 2001 From: Oliver Beddows Date: Mon, 27 Nov 2017 00:00:49 +0100 Subject: [PATCH 11/14] Standardise comment --- test/integration/peers.integration.js | 0 1 file changed, 0 insertions(+), 0 deletions(-) create mode 100644 test/integration/peers.integration.js diff --git a/test/integration/peers.integration.js b/test/integration/peers.integration.js new file mode 100644 index 00000000000..e69de29bb2d From 3565a86028d672e0a93b68dede1b1d71e83bf81a Mon Sep 17 00:00:00 2001 From: Oliver Beddows Date: Mon, 27 Nov 2017 00:01:05 +0100 Subject: [PATCH 12/14] Standardise spacing --- test/integration/peers.integration.js | 497 +++++++++++++++++++++++++ test/integration/setup/sync.js | 3 + test/integration/transport.js | 1 + test/integration/utils/transactions.js | 1 + 4 files changed, 502 insertions(+) diff --git a/test/integration/peers.integration.js b/test/integration/peers.integration.js index e69de29bb2d..5ae0a5e5605 100644 --- a/test/integration/peers.integration.js +++ b/test/integration/peers.integration.js @@ -0,0 +1,497 @@ +'use strict'; + +var _ = require('lodash'); +var async = require('async'); +var child_process = require('child_process'); +var chai = require('chai'); +var expect = require('chai').expect; +var fs = require('fs'); +var popsicle = require('popsicle'); +var Promise = require('bluebird'); +var scClient = require('socketcluster-client'); +var waitUntilBlockchainReady = require('../common/globalBefore').waitUntilBlockchainReady; +var WAMPClient = require('wamp-socket-cluster/WAMPClient'); + +var baseConfig = require('../../test/config.json'); +var WSClient = require('../common/wsClient'); +var Logger = require('../../logger'); +var logger = new Logger({filename: 'integrationTestsLogger.logs', echo: 'log'}); + +var SYNC_MODE = { + RANDOM: 0, + ALL_TO_FIRST: 1, + ALL_TO_GROUP: 2 +}; + +var SYNC_MODE_DEFAULT_ARGS = { + RANDOM: { + PROBABILITY: 0.5 // Range 0 - 1 + }, + ALL_TO_GROUP: { + INDICES: [] + } +}; + +var WAIT_BEFORE_CONNECT_MS = 60000; + +SYNC_MODE_DEFAULT_ARGS.ALL_TO_GROUP.INDICES = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]; +var testNodeConfigs = generateNodesConfig(10, SYNC_MODE.ALL_TO_GROUP, [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]); + +var monitorWSClient = { + protocol: 'http', + hostname: '127.0.0.1', + port: 'toOverwrite', + autoReconnect: true, + query: WSClient.generatePeerHeaders() +}; + +monitorWSClient.query.port = 9999; + +function generateNodePeers (numOfPeers, syncMode, syncModeArgs) { + syncModeArgs = syncModeArgs || SYNC_MODE_DEFAULT_ARGS; + + var peersList = []; + + switch (syncMode) { + case SYNC_MODE.RANDOM: + if (typeof syncModeArgs.RANDOM.PROBABILITY !== 'number') { + throw new Error('Probability parameter not specified to random sync mode'); + } + + var isPickedWithProbability = function (n) { + return !!n && Math.random() <= n; + }; + + Array.apply(null, new Array(numOfPeers)).forEach(function (val, index) { + if (isPickedWithProbability(syncModeArgs.RANDOM.PROBABILITY)) { + peersList.push({ + ip: '127.0.0.1', + port: 5000 + index + }); + } + }); + break; + + case SYNC_MODE.ALL_TO_FIRST: + peersList = [{ + ip: '127.0.0.1', + port: 5001 + }]; + break; + + case SYNC_MODE.ALL_TO_GROUP: + if (!Array.isArray(syncModeArgs.ALL_TO_GROUP.INDICES)) { + throw new Error('Provide peers indices to sync with as an array'); + } + Array.apply(null, new Array(numOfPeers)).forEach(function (val, index) { + if (syncModeArgs.ALL_TO_GROUP.INDICES.indexOf(index) !== -1) { + peersList.push({ + ip: '127.0.0.1', + port: 5000 + index + }); + } + }); + } + + return peersList; +} + +function generateNodesConfig (numOfPeers, syncMode, forgingNodesIndices) { + var secretsInChunk = Math.ceil(baseConfig.forging.secret.length / forgingNodesIndices.length); + var secretsChunks = forgingNodesIndices.map(function (val, index) { + return baseConfig.forging.secret.slice(index * secretsInChunk, (index + 1) * secretsInChunk); + }); + + return Array.apply(null, new Array(numOfPeers)).map(function (val, index) { + var isForging = forgingNodesIndices.indexOf(index) !== -1; + + return { + ip: '127.0.0.1', + port: 5000 + index, + database: 'lisk_local_' + index, + peers: { + list: generateNodePeers(numOfPeers, syncMode) + }, + forging: isForging, + secrets: isForging ? secretsChunks[index] : [] + }; + }); +} + +function generatePM2NodesConfig (testNodeConfigs) { + var pm2Config = { + apps: [] + }; + + function insertNewNode (index, nodeConfig) { + function peersAsString (peersList) { + return peersList.reduce(function (acc, peer) { + acc += peer.ip + ':' + peer.port + ','; + return acc; + }, '').slice(0, -1); + } + + var nodePM2Config = { + 'exec_mode': 'fork', + 'script': 'app.js', + 'name': 'node_' + index, + 'args': ' -p ' + nodeConfig.port + + ' -h ' + (nodeConfig.port - 1000) + + ' -x ' + peersAsString(nodeConfig.peers.list) + + ' -d ' + nodeConfig.database, + 'env': { + 'NODE_ENV': 'test' + }, + 'error_file': './test/integration/logs/lisk-test-node-' + index + '.err.log', + 'out_file': './test/integration/logs/lisk-test-node-' + index + '.out.log' + }; + + if (!nodeConfig.forging) { + nodePM2Config.args += ' -c ./test/integration/configs/config.non-forge.json'; + } else { + var currentNodeConfig = _.clone(baseConfig); + currentNodeConfig.forging.force = false; + currentNodeConfig.forging.secret = nodeConfig.secrets; + fs.writeFileSync(__dirname + '/configs/config.node-' + index + '.json', JSON.stringify(currentNodeConfig, null, 4)); + nodePM2Config.args += ' -c ./test/integration/configs/config.node-' + index + '.json'; + } + pm2Config.apps.push(nodePM2Config); + } + + testNodeConfigs.forEach(function (testNodeConfig, index) { + insertNewNode(index, testNodeConfig); + }); + + fs.writeFileSync(__dirname + '/pm2.integration.json', JSON.stringify(pm2Config, null, 4)); +} + +function clearLogs (cb) { + child_process.exec('rm -rf test/integration/logs/*', function (err) { + return cb(err); + }); +} + +function launchTestNodes (cb) { + child_process.exec('node_modules/.bin/pm2 start test/integration/pm2.integration.json', function (err) { + return cb(err); + }); +} + +function killTestNodes (cb) { + child_process.exec('node_modules/.bin/pm2 delete all', function (err) { + return cb(err); + }); +} + +function runFunctionalTests (cb) { + var child = child_process.spawn('node_modules/.bin/_mocha', ['--timeout', (8 * 60 * 1000).toString(), '--exit', + 'test/functional/http/get/blocks.js', 'test/functional/http/get/transactions.js'], { + cwd: __dirname + '/../..' + }); + + child.stdout.pipe(process.stdout); + + child.on('close', function (code) { + if (code === 0) { + return cb(); + } else { + return cb('Functional tests failed'); + } + }); + + child.on('error', function (err) { + return cb(err); + }); +} + +function recreateDatabases (done) { + async.forEachOf(testNodeConfigs, function (nodeConfig, index, eachCb) { + child_process.exec('dropdb ' + nodeConfig.database + '; createdb ' + nodeConfig.database, eachCb); + }, done); +} + +function enableForgingOnDelegates (done) { + var enableForgingPromises = []; + + testNodeConfigs.forEach(function (testNodeConfig) { + testNodeConfig.secrets.forEach(function (keys) { + var enableForgingPromise = popsicle.put({ + url: 'http://' + testNodeConfig.ip + ':' + (testNodeConfig.port - 1000) + '/api/node/status/forging', + headers: { + 'Accept': 'application/json', + 'Content-Type': 'application/json' + }, + body: { + decryptionKey: 'elephant tree paris dragon chair galaxy', + publicKey: keys.publicKey + } + }); + enableForgingPromises.push(enableForgingPromise); + }); + }); + Promise.all(enableForgingPromises).then(function () { + done(); + }).catch(function () { + done('Failed to enable forging on delegates'); + }); +} + +function waitForAllNodesToBeReady (done) { + async.forEachOf(testNodeConfigs, function (nodeConfig, index, eachCb) { + waitUntilBlockchainReady(eachCb, 20, 2000, 'http://' + nodeConfig.ip + ':' + (nodeConfig.port - 1000)); + }, done); +} + +function establishWSConnectionsToNodes (sockets, done) { + var connectedTo = 0; + var wampClient = new WAMPClient(); + + setTimeout(function () { + testNodeConfigs.forEach(function (testNodeConfig) { + monitorWSClient.port = testNodeConfig.port; + var socket = scClient.connect(monitorWSClient); + wampClient.upgradeToWAMP(socket); + socket.on('connect', function () { + sockets.push(socket); + connectedTo += 1; + if (connectedTo === testNodeConfigs.length) { + done(); + } + }); + socket.on('error', function (err) {}); + socket.on('connectAbort', function (err) { + done('Unable to establish WS connection with ' + testNodeConfig.ip + ':' + testNodeConfig.port); + }); + }, WAIT_BEFORE_CONNECT_MS); + }); +} + +describe('integration', function () { + + var self = this; + var sockets = []; + generatePM2NodesConfig(testNodeConfigs); + + before(function (done) { + async.series([ + function (cbSeries) { + clearLogs(cbSeries); + }, + function (cbSeries) { + recreateDatabases(cbSeries); + }, + function (cbSeries) { + launchTestNodes(cbSeries); + }, + function (cbSeries) { + waitForAllNodesToBeReady(cbSeries); + }, + function (cbSeries) { + enableForgingOnDelegates(cbSeries); + }, + function (cbSeries) { + establishWSConnectionsToNodes(sockets, cbSeries); + self.timeout(WAIT_BEFORE_CONNECT_MS * 2); + } + ], done); + }); + + after(function (done) { + killTestNodes(done); + }); + + describe('Peers mutual connections', function () { + + it('should return a list of peer mutually interconnected', function () { + return Promise.all(sockets.map(function (socket) { + return socket.wampSend('list', {}); + })).then(function (results) { + results.forEach(function (result) { + expect(result).to.have.property('success').to.be.ok; + expect(result).to.have.property('peers').to.be.a('array'); + var peerPorts = result.peers.map(function (p) { + return p.port; + }); + var allPeerPorts = testNodeConfigs.map(function (testNodeConfig) { + return testNodeConfig.port; + }); + expect(_.intersection(allPeerPorts, peerPorts)).to.be.an('array').and.not.to.be.empty; + }); + }); + }); + }); + + describe('forging', function () { + + function getNetworkStatus (cb) { + Promise.all(sockets.map(function (socket) { + return socket.wampSend('status'); + })).then(function (results) { + var maxHeight = 1; + var heightSum = 0; + results.forEach(function (result) { + expect(result).to.have.property('success').to.be.ok; + expect(result).to.have.property('height').to.be.a('number'); + if (result.height > maxHeight) { + maxHeight = result.height; + } + heightSum += result.height; + }); + return cb(null, { + height: maxHeight, + averageHeight: heightSum / results.length + }); + + }).catch(function (err) { + cb(err); + }); + } + + before(function (done) { + // Expect some blocks to forge after 30 seconds + var timesToCheckNetworkStatus = 30; + var timesNetworkStatusChecked = 0; + var checkNetworkStatusInterval = 1000; + + var checkingInterval = setInterval(function () { + getNetworkStatus(function (err, res) { + timesNetworkStatusChecked += 1; + if (err) { + clearInterval(checkingInterval); + return done(err); + } + logger.log('network status: height - ' + res.height + ', average height - ' + res.averageHeight); + if (timesNetworkStatusChecked === timesToCheckNetworkStatus) { + clearInterval(checkingInterval); + return done(null, res); + } + }); + }, checkNetworkStatusInterval); + }); + + describe('network status after 30 seconds', function () { + + var getNetworkStatusError; + var networkHeight; + var networkAverageHeight; + + before(function (done) { + getNetworkStatus(function (err, res) { + getNetworkStatusError = err; + networkHeight = res.height; + networkAverageHeight = res.averageHeight; + done(); + }); + }); + + it('should have no error', function () { + expect(getNetworkStatusError).not.to.exist; + }); + + it('should have height > 1', function () { + expect(networkHeight).to.be.above(1); + }); + + it('should have average height above 1', function () { + expect(networkAverageHeight).to.be.above(1); + }); + + it('should have different peers heights propagated correctly on peers lists', function () { + return Promise.all(sockets.map(function (socket) { + return socket.wampSend('list'); + })).then(function (results) { + expect(results.some(function (peersList) { + return peersList.peers.some(function (peer) { + return peer.height > 1; + }); + })); + }); + }); + }); + }); + + describe('propagation', function () { + + before(function (done) { + runFunctionalTests(done); + }); + + describe('blocks', function () { + + var nodesBlocks; + + before(function () { + return Promise.all(testNodeConfigs.map(function (testNodeConfig) { + return popsicle.get({ + url: 'http://' + testNodeConfig.ip + ':' + (testNodeConfig.port - 1000) + '/api/blocks', + headers: { + 'Accept': 'application/json', + 'Content-Type': 'application/json' + } + }); + })).then(function (results) { + nodesBlocks = results.map(function (res) { + return JSON.parse(res.body).blocks; + }); + expect(nodesBlocks).to.have.lengthOf(testNodeConfigs.length); + }); + }); + + it('should contain non empty blocks after running functional tests', function () { + nodesBlocks.forEach(function (blocks) { + expect(blocks).to.be.an('array').and.not.empty; + }); + }); + + it('should have all peers at the same height', function () { + var uniquePeersHeights = _(nodesBlocks).map('length').uniq().value(); + expect(uniquePeersHeights).to.have.lengthOf.at.least(1); + }); + + it('should have all blocks the same at all peers', function () { + var patternBlocks = nodesBlocks[0]; + for (var i = 0; i < patternBlocks.length; i += 1) { + for (var j = 1; j < nodesBlocks.length; j += 1) { + expect(_.isEqual(nodesBlocks[j][i], patternBlocks[i])); + } + } + }); + }); + + describe('transactions', function () { + + var nodesTransactions = []; + + before(function () { + return Promise.all(sockets.map(function (socket) { + return socket.wampSend('blocks'); + })).then(function (results) { + nodesTransactions = results.map(function (res) { + return res.blocks; + }); + expect(nodesTransactions).to.have.lengthOf(testNodeConfigs.length); + }); + }); + + it('should contain non empty transactions after running functional tests', function () { + nodesTransactions.forEach(function (transactions) { + expect(transactions).to.be.an('array').and.not.empty; + }); + }); + + it('should have all peers having same amount of confirmed transactions', function () { + var uniquePeersTransactionsNumber = _(nodesTransactions).map('length').uniq().value(); + expect(uniquePeersTransactionsNumber).to.have.lengthOf.at.least(1); + }); + + it('should have all transactions the same at all peers', function () { + var patternTransactions = nodesTransactions[0]; + for (var i = 0; i < patternTransactions.length; i += 1) { + for (var j = 1; j < nodesTransactions.length; j += 1) { + expect(_.isEqual(nodesTransactions[j][i], patternTransactions[i])); + } + } + }); + }); + }); +}); diff --git a/test/integration/setup/sync.js b/test/integration/setup/sync.js index 252038c1ae6..ee4fe9228f5 100644 --- a/test/integration/setup/sync.js +++ b/test/integration/setup/sync.js @@ -21,7 +21,9 @@ module.exports = { generatePeers: function (configurations, syncMode, syncModeArgs) { syncModeArgs = syncModeArgs || SYNC_MODE_DEFAULT_ARGS[syncMode]; + var peersList = []; + switch (syncMode) { case SYNC_MODES.RANDOM: if (typeof syncModeArgs.probability !== 'number') { @@ -63,6 +65,7 @@ module.exports = { } }); } + return peersList; } }; diff --git a/test/integration/transport.js b/test/integration/transport.js index 91780dcbc77..7331e93f973 100644 --- a/test/integration/transport.js +++ b/test/integration/transport.js @@ -35,6 +35,7 @@ describe('given configurations for 10 nodes with address "127.0.0.1", WS ports 5 before(function () { var secretsMaxLength = Math.ceil(devConfig.forging.secret.length / configurations.length); var secrets = _.clone(devConfig.forging.secret); + configurations.forEach(function (configuration, index) { configuration.forging.force = false; configuration.forging.secret = secrets.slice(index * secretsMaxLength, (index + 1) * secretsMaxLength); diff --git a/test/integration/utils/transactions.js b/test/integration/utils/transactions.js index 690ab45d0af..74ba520a2bd 100644 --- a/test/integration/utils/transactions.js +++ b/test/integration/utils/transactions.js @@ -7,6 +7,7 @@ module.exports = { generateValidTransaction: function () { var gAccountPassphrase = 'wagon stock borrow episode laundry kitten salute link globe zero feed marble'; var randomAddress = lisk.crypto.getAddress(lisk.crypto.getKeys(Math.random().toString(36).substring(7)).publicKey); + return lisk.transaction.createTransaction(randomAddress, 1, gAccountPassphrase); } }; From a1e515298cdfc1546e3427e0dc28cd0e20d7dcdf Mon Sep 17 00:00:00 2001 From: Maciej Baj Date: Tue, 28 Nov 2017 12:56:30 +0100 Subject: [PATCH 13/14] Adapt enableForging integration util to new API endpoint --- test/integration/peers.integration.js | 497 -------------------------- test/integration/setup/network.js | 6 +- test/integration/utils/http.js | 12 +- 3 files changed, 8 insertions(+), 507 deletions(-) delete mode 100644 test/integration/peers.integration.js diff --git a/test/integration/peers.integration.js b/test/integration/peers.integration.js deleted file mode 100644 index 5ae0a5e5605..00000000000 --- a/test/integration/peers.integration.js +++ /dev/null @@ -1,497 +0,0 @@ -'use strict'; - -var _ = require('lodash'); -var async = require('async'); -var child_process = require('child_process'); -var chai = require('chai'); -var expect = require('chai').expect; -var fs = require('fs'); -var popsicle = require('popsicle'); -var Promise = require('bluebird'); -var scClient = require('socketcluster-client'); -var waitUntilBlockchainReady = require('../common/globalBefore').waitUntilBlockchainReady; -var WAMPClient = require('wamp-socket-cluster/WAMPClient'); - -var baseConfig = require('../../test/config.json'); -var WSClient = require('../common/wsClient'); -var Logger = require('../../logger'); -var logger = new Logger({filename: 'integrationTestsLogger.logs', echo: 'log'}); - -var SYNC_MODE = { - RANDOM: 0, - ALL_TO_FIRST: 1, - ALL_TO_GROUP: 2 -}; - -var SYNC_MODE_DEFAULT_ARGS = { - RANDOM: { - PROBABILITY: 0.5 // Range 0 - 1 - }, - ALL_TO_GROUP: { - INDICES: [] - } -}; - -var WAIT_BEFORE_CONNECT_MS = 60000; - -SYNC_MODE_DEFAULT_ARGS.ALL_TO_GROUP.INDICES = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]; -var testNodeConfigs = generateNodesConfig(10, SYNC_MODE.ALL_TO_GROUP, [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]); - -var monitorWSClient = { - protocol: 'http', - hostname: '127.0.0.1', - port: 'toOverwrite', - autoReconnect: true, - query: WSClient.generatePeerHeaders() -}; - -monitorWSClient.query.port = 9999; - -function generateNodePeers (numOfPeers, syncMode, syncModeArgs) { - syncModeArgs = syncModeArgs || SYNC_MODE_DEFAULT_ARGS; - - var peersList = []; - - switch (syncMode) { - case SYNC_MODE.RANDOM: - if (typeof syncModeArgs.RANDOM.PROBABILITY !== 'number') { - throw new Error('Probability parameter not specified to random sync mode'); - } - - var isPickedWithProbability = function (n) { - return !!n && Math.random() <= n; - }; - - Array.apply(null, new Array(numOfPeers)).forEach(function (val, index) { - if (isPickedWithProbability(syncModeArgs.RANDOM.PROBABILITY)) { - peersList.push({ - ip: '127.0.0.1', - port: 5000 + index - }); - } - }); - break; - - case SYNC_MODE.ALL_TO_FIRST: - peersList = [{ - ip: '127.0.0.1', - port: 5001 - }]; - break; - - case SYNC_MODE.ALL_TO_GROUP: - if (!Array.isArray(syncModeArgs.ALL_TO_GROUP.INDICES)) { - throw new Error('Provide peers indices to sync with as an array'); - } - Array.apply(null, new Array(numOfPeers)).forEach(function (val, index) { - if (syncModeArgs.ALL_TO_GROUP.INDICES.indexOf(index) !== -1) { - peersList.push({ - ip: '127.0.0.1', - port: 5000 + index - }); - } - }); - } - - return peersList; -} - -function generateNodesConfig (numOfPeers, syncMode, forgingNodesIndices) { - var secretsInChunk = Math.ceil(baseConfig.forging.secret.length / forgingNodesIndices.length); - var secretsChunks = forgingNodesIndices.map(function (val, index) { - return baseConfig.forging.secret.slice(index * secretsInChunk, (index + 1) * secretsInChunk); - }); - - return Array.apply(null, new Array(numOfPeers)).map(function (val, index) { - var isForging = forgingNodesIndices.indexOf(index) !== -1; - - return { - ip: '127.0.0.1', - port: 5000 + index, - database: 'lisk_local_' + index, - peers: { - list: generateNodePeers(numOfPeers, syncMode) - }, - forging: isForging, - secrets: isForging ? secretsChunks[index] : [] - }; - }); -} - -function generatePM2NodesConfig (testNodeConfigs) { - var pm2Config = { - apps: [] - }; - - function insertNewNode (index, nodeConfig) { - function peersAsString (peersList) { - return peersList.reduce(function (acc, peer) { - acc += peer.ip + ':' + peer.port + ','; - return acc; - }, '').slice(0, -1); - } - - var nodePM2Config = { - 'exec_mode': 'fork', - 'script': 'app.js', - 'name': 'node_' + index, - 'args': ' -p ' + nodeConfig.port + - ' -h ' + (nodeConfig.port - 1000) + - ' -x ' + peersAsString(nodeConfig.peers.list) + - ' -d ' + nodeConfig.database, - 'env': { - 'NODE_ENV': 'test' - }, - 'error_file': './test/integration/logs/lisk-test-node-' + index + '.err.log', - 'out_file': './test/integration/logs/lisk-test-node-' + index + '.out.log' - }; - - if (!nodeConfig.forging) { - nodePM2Config.args += ' -c ./test/integration/configs/config.non-forge.json'; - } else { - var currentNodeConfig = _.clone(baseConfig); - currentNodeConfig.forging.force = false; - currentNodeConfig.forging.secret = nodeConfig.secrets; - fs.writeFileSync(__dirname + '/configs/config.node-' + index + '.json', JSON.stringify(currentNodeConfig, null, 4)); - nodePM2Config.args += ' -c ./test/integration/configs/config.node-' + index + '.json'; - } - pm2Config.apps.push(nodePM2Config); - } - - testNodeConfigs.forEach(function (testNodeConfig, index) { - insertNewNode(index, testNodeConfig); - }); - - fs.writeFileSync(__dirname + '/pm2.integration.json', JSON.stringify(pm2Config, null, 4)); -} - -function clearLogs (cb) { - child_process.exec('rm -rf test/integration/logs/*', function (err) { - return cb(err); - }); -} - -function launchTestNodes (cb) { - child_process.exec('node_modules/.bin/pm2 start test/integration/pm2.integration.json', function (err) { - return cb(err); - }); -} - -function killTestNodes (cb) { - child_process.exec('node_modules/.bin/pm2 delete all', function (err) { - return cb(err); - }); -} - -function runFunctionalTests (cb) { - var child = child_process.spawn('node_modules/.bin/_mocha', ['--timeout', (8 * 60 * 1000).toString(), '--exit', - 'test/functional/http/get/blocks.js', 'test/functional/http/get/transactions.js'], { - cwd: __dirname + '/../..' - }); - - child.stdout.pipe(process.stdout); - - child.on('close', function (code) { - if (code === 0) { - return cb(); - } else { - return cb('Functional tests failed'); - } - }); - - child.on('error', function (err) { - return cb(err); - }); -} - -function recreateDatabases (done) { - async.forEachOf(testNodeConfigs, function (nodeConfig, index, eachCb) { - child_process.exec('dropdb ' + nodeConfig.database + '; createdb ' + nodeConfig.database, eachCb); - }, done); -} - -function enableForgingOnDelegates (done) { - var enableForgingPromises = []; - - testNodeConfigs.forEach(function (testNodeConfig) { - testNodeConfig.secrets.forEach(function (keys) { - var enableForgingPromise = popsicle.put({ - url: 'http://' + testNodeConfig.ip + ':' + (testNodeConfig.port - 1000) + '/api/node/status/forging', - headers: { - 'Accept': 'application/json', - 'Content-Type': 'application/json' - }, - body: { - decryptionKey: 'elephant tree paris dragon chair galaxy', - publicKey: keys.publicKey - } - }); - enableForgingPromises.push(enableForgingPromise); - }); - }); - Promise.all(enableForgingPromises).then(function () { - done(); - }).catch(function () { - done('Failed to enable forging on delegates'); - }); -} - -function waitForAllNodesToBeReady (done) { - async.forEachOf(testNodeConfigs, function (nodeConfig, index, eachCb) { - waitUntilBlockchainReady(eachCb, 20, 2000, 'http://' + nodeConfig.ip + ':' + (nodeConfig.port - 1000)); - }, done); -} - -function establishWSConnectionsToNodes (sockets, done) { - var connectedTo = 0; - var wampClient = new WAMPClient(); - - setTimeout(function () { - testNodeConfigs.forEach(function (testNodeConfig) { - monitorWSClient.port = testNodeConfig.port; - var socket = scClient.connect(monitorWSClient); - wampClient.upgradeToWAMP(socket); - socket.on('connect', function () { - sockets.push(socket); - connectedTo += 1; - if (connectedTo === testNodeConfigs.length) { - done(); - } - }); - socket.on('error', function (err) {}); - socket.on('connectAbort', function (err) { - done('Unable to establish WS connection with ' + testNodeConfig.ip + ':' + testNodeConfig.port); - }); - }, WAIT_BEFORE_CONNECT_MS); - }); -} - -describe('integration', function () { - - var self = this; - var sockets = []; - generatePM2NodesConfig(testNodeConfigs); - - before(function (done) { - async.series([ - function (cbSeries) { - clearLogs(cbSeries); - }, - function (cbSeries) { - recreateDatabases(cbSeries); - }, - function (cbSeries) { - launchTestNodes(cbSeries); - }, - function (cbSeries) { - waitForAllNodesToBeReady(cbSeries); - }, - function (cbSeries) { - enableForgingOnDelegates(cbSeries); - }, - function (cbSeries) { - establishWSConnectionsToNodes(sockets, cbSeries); - self.timeout(WAIT_BEFORE_CONNECT_MS * 2); - } - ], done); - }); - - after(function (done) { - killTestNodes(done); - }); - - describe('Peers mutual connections', function () { - - it('should return a list of peer mutually interconnected', function () { - return Promise.all(sockets.map(function (socket) { - return socket.wampSend('list', {}); - })).then(function (results) { - results.forEach(function (result) { - expect(result).to.have.property('success').to.be.ok; - expect(result).to.have.property('peers').to.be.a('array'); - var peerPorts = result.peers.map(function (p) { - return p.port; - }); - var allPeerPorts = testNodeConfigs.map(function (testNodeConfig) { - return testNodeConfig.port; - }); - expect(_.intersection(allPeerPorts, peerPorts)).to.be.an('array').and.not.to.be.empty; - }); - }); - }); - }); - - describe('forging', function () { - - function getNetworkStatus (cb) { - Promise.all(sockets.map(function (socket) { - return socket.wampSend('status'); - })).then(function (results) { - var maxHeight = 1; - var heightSum = 0; - results.forEach(function (result) { - expect(result).to.have.property('success').to.be.ok; - expect(result).to.have.property('height').to.be.a('number'); - if (result.height > maxHeight) { - maxHeight = result.height; - } - heightSum += result.height; - }); - return cb(null, { - height: maxHeight, - averageHeight: heightSum / results.length - }); - - }).catch(function (err) { - cb(err); - }); - } - - before(function (done) { - // Expect some blocks to forge after 30 seconds - var timesToCheckNetworkStatus = 30; - var timesNetworkStatusChecked = 0; - var checkNetworkStatusInterval = 1000; - - var checkingInterval = setInterval(function () { - getNetworkStatus(function (err, res) { - timesNetworkStatusChecked += 1; - if (err) { - clearInterval(checkingInterval); - return done(err); - } - logger.log('network status: height - ' + res.height + ', average height - ' + res.averageHeight); - if (timesNetworkStatusChecked === timesToCheckNetworkStatus) { - clearInterval(checkingInterval); - return done(null, res); - } - }); - }, checkNetworkStatusInterval); - }); - - describe('network status after 30 seconds', function () { - - var getNetworkStatusError; - var networkHeight; - var networkAverageHeight; - - before(function (done) { - getNetworkStatus(function (err, res) { - getNetworkStatusError = err; - networkHeight = res.height; - networkAverageHeight = res.averageHeight; - done(); - }); - }); - - it('should have no error', function () { - expect(getNetworkStatusError).not.to.exist; - }); - - it('should have height > 1', function () { - expect(networkHeight).to.be.above(1); - }); - - it('should have average height above 1', function () { - expect(networkAverageHeight).to.be.above(1); - }); - - it('should have different peers heights propagated correctly on peers lists', function () { - return Promise.all(sockets.map(function (socket) { - return socket.wampSend('list'); - })).then(function (results) { - expect(results.some(function (peersList) { - return peersList.peers.some(function (peer) { - return peer.height > 1; - }); - })); - }); - }); - }); - }); - - describe('propagation', function () { - - before(function (done) { - runFunctionalTests(done); - }); - - describe('blocks', function () { - - var nodesBlocks; - - before(function () { - return Promise.all(testNodeConfigs.map(function (testNodeConfig) { - return popsicle.get({ - url: 'http://' + testNodeConfig.ip + ':' + (testNodeConfig.port - 1000) + '/api/blocks', - headers: { - 'Accept': 'application/json', - 'Content-Type': 'application/json' - } - }); - })).then(function (results) { - nodesBlocks = results.map(function (res) { - return JSON.parse(res.body).blocks; - }); - expect(nodesBlocks).to.have.lengthOf(testNodeConfigs.length); - }); - }); - - it('should contain non empty blocks after running functional tests', function () { - nodesBlocks.forEach(function (blocks) { - expect(blocks).to.be.an('array').and.not.empty; - }); - }); - - it('should have all peers at the same height', function () { - var uniquePeersHeights = _(nodesBlocks).map('length').uniq().value(); - expect(uniquePeersHeights).to.have.lengthOf.at.least(1); - }); - - it('should have all blocks the same at all peers', function () { - var patternBlocks = nodesBlocks[0]; - for (var i = 0; i < patternBlocks.length; i += 1) { - for (var j = 1; j < nodesBlocks.length; j += 1) { - expect(_.isEqual(nodesBlocks[j][i], patternBlocks[i])); - } - } - }); - }); - - describe('transactions', function () { - - var nodesTransactions = []; - - before(function () { - return Promise.all(sockets.map(function (socket) { - return socket.wampSend('blocks'); - })).then(function (results) { - nodesTransactions = results.map(function (res) { - return res.blocks; - }); - expect(nodesTransactions).to.have.lengthOf(testNodeConfigs.length); - }); - }); - - it('should contain non empty transactions after running functional tests', function () { - nodesTransactions.forEach(function (transactions) { - expect(transactions).to.be.an('array').and.not.empty; - }); - }); - - it('should have all peers having same amount of confirmed transactions', function () { - var uniquePeersTransactionsNumber = _(nodesTransactions).map('length').uniq().value(); - expect(uniquePeersTransactionsNumber).to.have.lengthOf.at.least(1); - }); - - it('should have all transactions the same at all peers', function () { - var patternTransactions = nodesTransactions[0]; - for (var i = 0; i < patternTransactions.length; i += 1) { - for (var j = 1; j < nodesTransactions.length; j += 1) { - expect(_.isEqual(nodesTransactions[j][i], patternTransactions[i])); - } - } - }); - }); - }); -}); diff --git a/test/integration/setup/network.js b/test/integration/setup/network.js index d4f4dd06003..49cdeb7ca71 100644 --- a/test/integration/setup/network.js +++ b/test/integration/setup/network.js @@ -22,10 +22,8 @@ module.exports = { }); }); Promise.all(enableForgingPromises).then(function (forgingResults) { - return cb(forgingResults.map(function (result) { - return result.forging; - }).some(function (isForging) { - return !isForging; + return cb(forgingResults.some(function (forgingResult) { + return !forgingResult.forging; }) ? 'Enabling forging failed for some of delegates' : null); }).catch(function (error) { return cb(error); diff --git a/test/integration/utils/http.js b/test/integration/utils/http.js index 19b95f79ea1..2f3f85673e3 100644 --- a/test/integration/utils/http.js +++ b/test/integration/utils/http.js @@ -5,7 +5,7 @@ var apiCodes = require('../../../helpers/apiCodes'); var headers = { 'Accept': 'application/json', - 'Content-Type': 'application/x-www-form-urlencoded' + 'Content-Type': 'application/json' }; var endpoints = { @@ -38,7 +38,7 @@ var endpoints = { return 'http://' + ip + ':' + port + '/api/transactions'; }, enableForging: function (ip, port) { - return 'http://' + ip + ':' + port + '/api/delegates/forging'; + return 'http://' + ip + ':' + port + '/api/node/status/forging'; }, getTransactions: function (ip, port) { return 'http://' + ip + ':' + port + '/api/transactions'; @@ -106,16 +106,16 @@ module.exports = { url: endpoints.versions[currentVersion].enableForging(ip || '127.0.0.1', port || 4000), headers: headers, body: { - key: 'elephant tree paris dragon chair galaxy', + decryptionKey: 'elephant tree paris dragon chair galaxy', publicKey: keys.publicKey } }).then(function (res) { if (res.status !== apiCodes.OK) { throw new Error('Unable to enable forging for delegate with publicKey: ' + keys.publicKey); } - return JSON.parse(res.body); - }).catch(function () { - throw new Error('Unable to enable forging for delegate with publicKey: ' + keys.publicKey); + return JSON.parse(res.body).data[0]; + }).catch(function (err) { + throw new Error('Unable to enable forging for delegate with publicKey: ' + keys.publicKey + JSON.stringify(err)); }); } }; From 3092fcb12d0626802fed27a8ec8121e3ddc52ea1 Mon Sep 17 00:00:00 2001 From: Maciej Baj Date: Tue, 28 Nov 2017 14:37:17 +0100 Subject: [PATCH 14/14] Adapt getBlocks integration util to new API response --- test/integration/utils/http.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/integration/utils/http.js b/test/integration/utils/http.js index 2f3f85673e3..50d022ea659 100644 --- a/test/integration/utils/http.js +++ b/test/integration/utils/http.js @@ -67,7 +67,7 @@ module.exports = { if (res.status !== apiCodes.OK) { throw new Error('Unable to get blocks from peer: ' + res.body); } - return JSON.parse(res.body).blocks; + return JSON.parse(res.body).data; }); },