diff --git a/Jenkinsfile b/Jenkinsfile index 4f674dfbea3..3981d36fbaf 100644 --- a/Jenkinsfile +++ b/Jenkinsfile @@ -297,6 +297,15 @@ lock(resource: "Lisk-Core-Nodes", inversePrecedence: true) { ''' } }, + "Functional - Multisignature 3": { + node('node-03'){ + sh ''' + export TEST=test/functional/multisignature.js TEST_TYPE='FUNC' + cd "$(echo $WORKSPACE | cut -f 1 -d '@')" + npm run jenkins + ''' + } + }, "Functional Peer - Votes" : { node('node-02'){ sh ''' diff --git a/helpers/database.js b/helpers/database.js index f587641deef..9a3e4028b82 100644 --- a/helpers/database.js +++ b/helpers/database.js @@ -4,6 +4,7 @@ var async = require('async'); var bignum = require('./bignum'); var fs = require('fs'); var path = require('path'); +var monitor = require('pg-monitor'); // var isWin = /^win/.test(process.platform); // var isMac = /^darwin/.test(process.platform); @@ -185,7 +186,10 @@ module.exports.connect = function (config, logger, cb) { }; var pgp = require('pg-promise')(pgOptions); - var monitor = require('pg-monitor'); + + try { + monitor.detach(); + } catch (ex) {} monitor.attach(pgOptions, config.logEvents); monitor.setTheme('matrix'); @@ -211,3 +215,16 @@ module.exports.connect = function (config, logger, cb) { return cb(err, db); }); }; + +/** + * Detaches pg-monitor. Should be invoked after connect. + * @param {Object} logger + */ +module.exports.disconnect = function (logger) { + logger = logger || console; + try { + monitor.detach(); + } catch (ex) { + logger.log('database disconnect exception - ', ex); + } +}; diff --git a/logic/transaction.js b/logic/transaction.js index 20a06b23281..9c7327b4706 100644 --- a/logic/transaction.js +++ b/logic/transaction.js @@ -471,7 +471,7 @@ Transaction.prototype.verify = function (trs, sender, requester, cb) { } // Determine multisignatures from sender or transaction asset - var multisignatures = sender.multisignatures || sender.u_multisignatures || []; + var multisignatures = sender.multisignatures || []; if (multisignatures.length === 0) { if (trs.asset && trs.asset.multisignature && trs.asset.multisignature.keysgroup) { diff --git a/test/common/application.js b/test/common/application.js new file mode 100644 index 00000000000..a3b88573cf5 --- /dev/null +++ b/test/common/application.js @@ -0,0 +1,316 @@ +'use strict'; + +// Global imports +var child_process = require('child_process'); +var Promise = require('bluebird'); +var rewire = require('rewire'); +var sinon = require('sinon'); + +// Application-specific imports +var node = require('./../node.js'); +var database = require('../../helpers/database.js'); +var jobsQueue = require('../../helpers/jobsQueue.js'); +var Sequence = require('../../helpers/sequence.js'); + +var dbSandbox; +var currentAppScope; +var testDatabaseNames = []; + +function DBSandbox (dbConfig, testDatabaseName) { + this.dbConfig = dbConfig; + this.originalDatabaseName = dbConfig.database; + this.testDatabaseName = testDatabaseName || this.originalDatabaseName; + this.dbConfig.database = this.testDatabaseName; + testDatabaseNames.push(this.testDatabaseName); + + var dropCreatedDatabases = function () { + testDatabaseNames.forEach(function (testDatabaseName) { + child_process.exec('dropdb ' + testDatabaseName); + }); + }; + + process.on('exit', function () { + dropCreatedDatabases(); + }); +} + +DBSandbox.prototype.create = function (cb) { + child_process.exec('dropdb ' + this.dbConfig.database, function () { + child_process.exec('createdb ' + this.dbConfig.database, function () { + database.connect(this.dbConfig, console, cb); + }.bind(this)); + }.bind(this)); +}; + +DBSandbox.prototype.destroy = function (logger) { + database.disconnect(logger); + this.dbConfig.database = this.originalDatabaseName; +}; + +// Init whole application inside tests - public +function init (options, cb) { + options = options ? options : {}; + options.scope = options.scope ? options.scope : {}; + + if (options.sandbox) { + dbSandbox = new DBSandbox(options.sandbox.config || node.config.db, options.sandbox.name); + dbSandbox.create(function (err, __db) { + options.scope.db = __db; + __init(options.scope, cb); + }); + } else { + __init(options.scope, cb); + } +} + +// Init whole application inside tests - private +function __init (initScope, done) { + node.debug('initApplication: Application initialization inside test environment started...'); + + // Reset jobsQueue + jobsQueue.jobs = {}; + + var modules = [], rewiredModules = {}; + // Init dummy connection with database - valid, used for tests here + var options = { + promiseLib: Promise + }; + var db = initScope.db; + if (!db) { + var pgp = require('pg-promise')(options); + node.config.db.user = node.config.db.user || process.env.USER; + db = pgp(node.config.db); + } + + node.debug('initApplication: Target database - ' + node.config.db.database); + + // Clear tables + db.task(function (t) { + return t.batch([ + t.none('DELETE FROM blocks WHERE height > 1'), + t.none('DELETE FROM blocks'), + t.none('DELETE FROM mem_accounts') + ]); + }).then(function () { + var logger = initScope.logger || { + trace: sinon.spy(), + debug: sinon.spy(), + info: sinon.spy(), + log: sinon.spy(), + warn: sinon.spy(), + error: sinon.spy() + }; + + var modulesInit = { + accounts: '../../modules/accounts.js', + rounds: '../../modules/rounds.js', + transactions: '../../modules/transactions.js', + blocks: '../../modules/blocks.js', + signatures: '../../modules/signatures.js', + transport: '../../modules/transport.js', + loader: '../../modules/loader.js', + system: '../../modules/system.js', + peers: '../../modules/peers.js', + delegates: '../../modules/delegates.js', + multisignatures: '../../modules/multisignatures.js' + }; + + // Init limited application layer + node.async.auto({ + config: function (cb) { + cb(null, node.config); + }, + genesisblock: function (cb) { + var genesisblock = require('../../genesisBlock.json'); + cb(null, {block: genesisblock}); + }, + + schema: function (cb) { + var z_schema = require('../../helpers/z_schema.js'); + cb(null, new z_schema()); + }, + network: function (cb) { + // Init with empty function + cb(null, {io: {sockets: {emit: function () {}}}}); + }, + logger: function (cb) { + cb(null, logger); + }, + dbSequence: ['logger', function (scope, cb) { + var sequence = new Sequence({ + onWarning: function (current, limit) { + scope.logger.warn('DB queue', current); + } + }); + cb(null, sequence); + }], + sequence: ['logger', function (scope, cb) { + var sequence = new Sequence({ + onWarning: function (current, limit) { + scope.logger.warn('Main queue', current); + } + }); + cb(null, sequence); + }], + balancesSequence: ['logger', function (scope, cb) { + var sequence = new Sequence({ + onWarning: function (current, limit) { + scope.logger.warn('Balance queue', current); + } + }); + cb(null, sequence); + }], + ed: function (cb) { + cb(null, require('../../helpers/ed.js')); + }, + bus: ['ed', function (scope, cb) { + var changeCase = require('change-case'); + var bus = function () { + this.message = function () { + var args = []; + Array.prototype.push.apply(args, arguments); + var topic = args.shift(); + var eventName = 'on' + changeCase.pascalCase(topic); + + // Iterate over modules and execute event functions (on*) + modules.forEach(function (module) { + if (typeof(module[eventName]) === 'function') { + module[eventName].apply(module[eventName], args); + } + if (module.submodules) { + node.async.each(module.submodules, function (submodule) { + if (submodule && typeof(submodule[eventName]) === 'function') { + submodule[eventName].apply(submodule[eventName], args); + } + }); + } + }); + }; + }; + cb(null, new bus()); + }], + db: function (cb) { + cb(null, db); + }, + logic: ['db', 'bus', 'schema', 'genesisblock', function (scope, cb) { + var Transaction = require('../../logic/transaction.js'); + var Block = require('../../logic/block.js'); + var Account = require('../../logic/account.js'); + var Peers = require('../../logic/peers.js'); + + node.async.auto({ + bus: function (cb) { + cb(null, scope.bus); + }, + db: function (cb) { + cb(null, scope.db); + }, + ed: function (cb) { + cb(null, scope.ed); + }, + logger: function (cb) { + cb(null, scope.logger); + }, + schema: function (cb) { + cb(null, scope.schema); + }, + genesisblock: function (cb) { + cb(null, { + block: scope.genesisblock.block + }); + }, + account: ['db', 'bus', 'ed', 'schema', 'genesisblock', 'logger', function (scope, cb) { + new Account(scope.db, scope.schema, scope.logger, cb); + }], + transaction: ['db', 'bus', 'ed', 'schema', 'genesisblock', 'account', 'logger', function (scope, cb) { + new Transaction(scope.db, scope.ed, scope.schema, scope.genesisblock, scope.account, scope.logger, cb); + }], + block: ['db', 'bus', 'ed', 'schema', 'genesisblock', 'account', 'transaction', function (scope, cb) { + new Block(scope.ed, scope.schema, scope.transaction, cb); + }], + peers: ['logger', function (scope, cb) { + new Peers(scope.logger, cb); + }] + }, cb); + }], + modules: ['network', 'logger', 'bus', 'sequence', 'dbSequence', 'balancesSequence', 'db', 'logic', function (scope, cb) { + var tasks = {}; + scope.rewiredModules = {}; + + Object.keys(modulesInit).forEach(function (name) { + tasks[name] = function (cb) { + var Instance = rewire(modulesInit[name]); + + rewiredModules[name] = Instance; + var obj = new rewiredModules[name](cb, scope); + modules.push(obj); + node.debug('initApplication: Module ' + name + ' loaded'); + }; + }); + + node.async.parallel(tasks, function (err, results) { + cb(err, results); + }); + }], + ready: ['modules', 'bus', 'logic', function (scope, cb) { + // Fire onBind event in every module + scope.bus.message('bind', scope.modules); + scope.logic.transaction.bindModules(scope.modules); + scope.logic.peers.bindModules(scope.modules); + node.debug('initApplication: Modules binding done'); + cb(); + }] + }, function (err, scope) { + node.expect(err).to.be.null; + + scope.rewiredModules = rewiredModules; + currentAppScope = scope; + node.debug('initApplication: Rewired modules available'); + + // Overwrite onBlockchainReady function to prevent automatic forging + scope.modules.delegates.onBlockchainReady = function () { + node.debug('initApplication: Fake onBlockchainReady event called'); + node.debug('initApplication: Loading delegates...'); + + var loadDelegates = scope.rewiredModules.delegates.__get__('__private.loadDelegates'); + loadDelegates(function (err) { + node.expect(err).to.be.null; + + var keypairs = scope.rewiredModules.delegates.__get__('__private.keypairs'); + var delegates_cnt = Object.keys(keypairs).length; + node.expect(delegates_cnt).to.equal(node.config.forging.secret.length); + + node.debug('initApplication: Delegates loaded from config file - ' + delegates_cnt); + node.debug('initApplication: Done'); + return done(scope); + }); + }; + }); + }); +}; + +function cleanup (cb) { + node.async.eachSeries(currentAppScope.modules, function (module, seriesCb) { + if (typeof(module.cleanup) === 'function') { + module.cleanup(seriesCb); + } else { + seriesCb(); + } + }, function (err) { + if (err) { + currentAppScope.logger.error(err); + } else { + currentAppScope.logger.info('Cleaned up successfully'); + } + // Disconnect from database instance if sandbox was used + if (dbSandbox) { + dbSandbox.destroy(); + } + cb(); + }); +}; + +module.exports = { + init: init, + cleanup: cleanup +}; diff --git a/test/common/globalBefore.js b/test/common/globalBefore.js index a882e93dd60..7d7ee298a79 100644 --- a/test/common/globalBefore.js +++ b/test/common/globalBefore.js @@ -1,6 +1,13 @@ 'use strict'; var node = require('./../node.js'); +var child_process = require('child_process'); +var testDatabaseNames = []; + +var config = require('../../config.json'); +var database = require('../../helpers/database.js'); +var genesisblock = require('../genesisBlock.json'); +var ed = require('../../helpers/ed.js'); /** * @param {string} table @@ -46,7 +53,40 @@ function waitUntilBlockchainReady (cb, retries, timeout) { })(); } +function DBSandbox (dbConfig, testDatabaseName) { + this.dbConfig = dbConfig; + this.originalDatabaseName = dbConfig.database; + this.testDatabaseName = testDatabaseName || this.originalDatabaseName; + this.dbConfig.database = this.testDatabaseName; + testDatabaseNames.push(this.testDatabaseName); + + var dropCreatedDatabases = function () { + testDatabaseNames.forEach(function (testDatabaseName) { + child_process.exec('dropdb ' + testDatabaseName); + }); + }; + + process.on('exit', function () { + dropCreatedDatabases(); + }); +} + +DBSandbox.prototype.create = function (cb) { + child_process.exec('dropdb ' + this.dbConfig.database, function () { + child_process.exec('createdb ' + this.dbConfig.database, function () { + database.connect(this.dbConfig, console, cb); + }.bind(this)); + }.bind(this)); +}; + +DBSandbox.prototype.destroy = function (logger) { + database.disconnect(logger); + this.dbConfig.database = this.originalDatabaseName; +}; + + module.exports = { clearDatabaseTable: clearDatabaseTable, + DBSandbox: DBSandbox, waitUntilBlockchainReady: waitUntilBlockchainReady }; diff --git a/test/functional/multisignature.js b/test/functional/multisignature.js new file mode 100644 index 00000000000..638cbd48763 --- /dev/null +++ b/test/functional/multisignature.js @@ -0,0 +1,451 @@ +var node = require('../node.js'); +var async = require('async'); +var slots = require('../../helpers/slots.js'); +var sinon = require('sinon'); +var chai = require('chai'); +var expect = require('chai').expect; +var Promise = require('bluebird'); +var _ = require('lodash'); + +var application = require('./../common/application'); + +describe('multisignature', function () { + + var library; + + before('init sandboxed application', function (done) { + application.init({sandbox: {name: 'lisk_test_multisignatures'}}, function (scope) { + library = scope; + done(); + }); + }); + + after('cleanup sandboxed application', function (done) { + application.cleanup(done); + }); + + function forge (cb) { + function getNextForger (offset, cb) { + offset = !offset ? 1 : offset; + var last_block = library.modules.blocks.lastBlock.get(); + var slot = slots.getSlotNumber(last_block.timestamp); + library.modules.delegates.generateDelegateList(last_block.height, null, function (err, delegateList) { + if (err) { return cb (err); } + var nextForger = delegateList[(slot + offset) % slots.delegates]; + return cb(nextForger); + }); + } + + var transactionPool = library.rewiredModules.transactions.__get__('__private.transactionPool'); + var keypairs = library.rewiredModules.delegates.__get__('__private.keypairs'); + + node.async.waterfall([ + transactionPool.fillPool, + function (cb) { + getNextForger(null, function (delegatePublicKey) { + cb(null, delegatePublicKey); + }); + }, + function (delegate, seriesCb) { + var last_block = library.modules.blocks.lastBlock.get(); + var slot = slots.getSlotNumber(last_block.timestamp) + 1; + var keypair = keypairs[delegate]; + node.debug(' Last block height: ' + last_block.height + ' Last block ID: ' + last_block.id + ' Last block timestamp: ' + last_block.timestamp + ' Next slot: ' + slot + ' Next delegate PK: ' + delegate + ' Next block timestamp: ' + slots.getSlotTime(slot)); + library.modules.blocks.process.generateBlock(keypair, slots.getSlotTime(slot), function (err) { + if (err) { return seriesCb(err); } + last_block = library.modules.blocks.lastBlock.get(); + node.debug(' New last block height: ' + last_block.height + ' New last block ID: ' + last_block.id); + return seriesCb(err); + }); + } + ], function (err) { + cb(err); + }); + } + + function addTransaction (transaction, cb) { + // Add transaction to transactions pool - we use shortcut here to bypass transport module, but logic is the same + // See: modules.transport.__private.receiveTransaction + library.balancesSequence.add(function (sequenceCb) { + library.modules.transactions.processUnconfirmedTransaction(transaction, true, function (err) { + if (err) { + return setImmediate(sequenceCb, err.toString()); + } else { + return setImmediate(sequenceCb, null, transaction.id); + } + }); + }, cb); + } + + function addTransactionsAndForge (transactions, cb) { + node.async.waterfall([ + function addTransactions (waterCb) { + node.async.eachSeries(transactions, function (transaction, eachSeriesCb) { + addTransaction(transaction, eachSeriesCb); + }, waterCb); + }, + function (waterCb) { + setTimeout(function () { + forge(waterCb); + }, 800); + } + ], function (err) { + cb(err); + }); + } + + function getAccountFromDb (address) { + return Promise.all([ + library.db.query('SELECT * FROM mem_accounts where address = \'' + address + '\''), + library.db.query('SELECT * FROM mem_accounts2multisignatures where "accountId" = \'' + address + '\''), + library.db.query('SELECT * FROM mem_accounts2u_multisignatures where "accountId" = \'' + address + '\'') + ]).then(function (res) { + // Get the first row if resultant array is not empty + return { + mem_accounts: res[0].length > 0 ? res[0][0] : res[0], + mem_accounts2multisignatures: res[1], + mem_accounts2u_multisignatures: res[2] + }; + }); + } + + describe('with LISK sent to multisig account', function () { + + var multisigAccount; + + before('send funds to multisig account', function (done) { + multisigAccount = node.randomAccount(); + var sendTransaction = node.lisk.transaction.createTransaction(multisigAccount.address, 1000000000*100, node.gAccount.password); + addTransactionsAndForge([sendTransaction], done); + }); + + describe('from multisig account', function () { + + var multisigSender; + + before('get multisignature account', function (done) { + library.logic.account.get({address: multisigAccount.address}, function (err, res) { + multisigSender = res; + done(); + }); + }); + + describe('applyUnconfirm transaction', function () { + var multisigTransaction; + var signer1 = node.randomAccount(); + var signer2 = node.randomAccount(); + + before('applyUnconfirm multisignature transaction', function (done) { + var keysgroup = [ + '+' + signer1.publicKey, + '+' + signer2.publicKey + ]; + + multisigTransaction = node.lisk.multisignature.createMultisignature(multisigAccount.password, null, keysgroup, 4, 2); + var sign1 = node.lisk.multisignature.signTransaction(multisigTransaction, signer1.password); + var sign2 = node.lisk.multisignature.signTransaction(multisigTransaction, signer2.password); + + multisigTransaction.signatures = [sign1, sign2]; + multisigTransaction.ready = true; + library.logic.transaction.applyUnconfirmed(multisigTransaction, multisigSender, done); + }); + + describe('sender db rows', function () { + var accountRow; + + before('get mem_account, mem_account2multisignature and mem_account2u_multisignature rows', function () { + return getAccountFromDb(multisigAccount.address).then(function (res) { + accountRow = res; + }); + }); + + it('should have no rows in mem_accounts2multisignatures', function () { + expect(accountRow.mem_accounts2multisignatures).to.eql([]); + }); + + it('should have multimin field set to 0 on mem_accounts', function () { + expect(accountRow.mem_accounts.multimin).to.eql(0); + }); + + it('should have multilifetime field set to 0 on mem_accounts', function () { + expect(accountRow.mem_accounts.multilifetime).to.eql(0); + }); + + it('should include rows in mem_accounts2u_multisignatures', function () { + var signKeysInDb = _.map(accountRow.mem_accounts2u_multisignatures, function (row) { + return row.dependentId; + }); + expect(signKeysInDb).to.include(signer1.publicKey, signer2.publicKey); + }); + + it('should set u_multimin field set on mem_accounts', function () { + expect(accountRow.mem_accounts.u_multimin).to.eql(multisigTransaction.asset.multisignature.min); + }); + + it('should set u_multilifetime field set on mem_accounts', function () { + expect(accountRow.mem_accounts.u_multilifetime).to.eql(multisigTransaction.asset.multisignature.lifetime); + }); + }); + + describe('sender account', function () { + + var account; + + before('get multisignature account', function (done) { + library.logic.account.get({address: multisigAccount.address}, function (err, res) { + expect(err).to.not.exist; + account = res; + done(); + }); + }); + + it('should have u_multisignatures field set on account', function () { + expect(account.u_multisignatures).to.include(signer1.publicKey, signer2.publicKey); + }); + + it('should have multimin field set on account', function () { + expect(account.u_multimin).to.eql(multisigTransaction.asset.multisignature.min); + }); + + it('should have multilifetime field set on account', function () { + expect(account.u_multilifetime).to.eql(multisigTransaction.asset.multisignature.lifetime); + }); + }); + + describe('with another multisig transaction', function () { + + var multisigTransaction2; + var signer3 = node.randomAccount(); + var signer4 = node.randomAccount(); + + before('process multisignature transaction', function (done) { + var keysgroup = [ + '+' + signer3.publicKey, + '+' + signer4.publicKey + ]; + multisigTransaction2 = node.lisk.multisignature.createMultisignature(multisigAccount.password, null, keysgroup, 4, 2); + var sign3 = node.lisk.multisignature.signTransaction(multisigTransaction2, signer3.password); + var sign4 = node.lisk.multisignature.signTransaction(multisigTransaction2, signer4.password); + multisigTransaction2.signatures = [sign3, sign4]; + library.logic.transaction.process(multisigTransaction2, multisigSender, done); + }); + + describe('from the same account', function () { + + before('get multisignature account', function (done) { + library.logic.account.get({address: multisigAccount.address}, function (err, res) { + multisigSender = res; + done(); + }); + }); + + it('should verify transaction', function (done) { + library.logic.transaction.verify(multisigTransaction2, multisigSender, done); + }); + }); + }); + }); + }); + }); + + describe('with LISK sent to multisig account', function () { + + var multisigAccount; + + before('send funds to multisig account', function (done) { + multisigAccount = node.randomAccount(); + var sendTransaction = node.lisk.transaction.createTransaction(multisigAccount.address, 1000000000*100, node.gAccount.password); + addTransactionsAndForge([sendTransaction], done); + }); + + describe('from multisig account', function () { + + var multisigSender; + + before('get multisignature account', function (done) { + library.logic.account.get({address: multisigAccount.address}, function (err, res) { + multisigSender = res; + done(); + }); + }); + + describe('after forging Block with multisig transaction', function () { + + var multisigTransaction; + var signer1 = node.randomAccount(); + var signer2 = node.randomAccount(); + + before('forge block with multisignature transaction', function (done) { + var keysgroup = [ + '+' + signer1.publicKey, + '+' + signer2.publicKey + ]; + + multisigTransaction = node.lisk.multisignature.createMultisignature(multisigAccount.password, null, keysgroup, 4, 2); + var sign1 = node.lisk.multisignature.signTransaction(multisigTransaction, signer1.password); + var sign2 = node.lisk.multisignature.signTransaction(multisigTransaction, signer2.password); + + multisigTransaction.signatures = [sign1, sign2]; + multisigTransaction.ready = true; + addTransactionsAndForge([multisigTransaction], done); + }); + + describe('sender db rows', function () { + var accountRow; + + before('get mem_account, mem_account2multisignature and mem_account2u_multisignature rows', function () { + return getAccountFromDb(multisigAccount.address).then(function (res) { + accountRow = res; + }); + }); + + it('should include rows in mem_accounts2multisignatures', function () { + var signKeysInDb = _.map(accountRow.mem_accounts2multisignatures, function (row) { + return row.dependentId; + }); + expect(signKeysInDb).to.include(signer1.publicKey, signer2.publicKey); + }); + + it('should set multimin field set on mem_accounts', function () { + expect(accountRow.mem_accounts.multimin).to.eql(multisigTransaction.asset.multisignature.min); + }); + + it('should set multilifetime field set on mem_accounts', function () { + expect(accountRow.mem_accounts.multilifetime).to.eql(multisigTransaction.asset.multisignature.lifetime); + }); + + it('should include rows in mem_accounts2u_multisignatures', function () { + var signKeysInDb = _.map(accountRow.mem_accounts2u_multisignatures, function (row) { + return row.dependentId; + }); + expect(signKeysInDb).to.include(signer1.publicKey, signer2.publicKey); + }); + + it('should set u_multimin field set on mem_accounts', function () { + expect(accountRow.mem_accounts.u_multimin).to.eql(multisigTransaction.asset.multisignature.min); + }); + + it('should set u_multilifetime field set on mem_accounts', function () { + expect(accountRow.mem_accounts.u_multilifetime).to.eql(multisigTransaction.asset.multisignature.lifetime); + }); + }); + + describe('sender account', function () { + + var account; + + before('get multisignature account', function (done) { + library.logic.account.get({address: multisigAccount.address}, function (err, res) { + expect(err).to.not.exist; + account = res; + done(); + }); + }); + + it('should have multisignatures field set on account', function () { + expect(account.multisignatures).to.include(signer1.publicKey, signer2.publicKey); + }); + + it('should have multimin field set on account', function () { + expect(account.multimin).to.eql(multisigTransaction.asset.multisignature.min); + }); + + it('should have multilifetime field set on account', function () { + expect(account.multilifetime).to.eql(multisigTransaction.asset.multisignature.lifetime); + }); + + it('should have u_multisignatures field set on account', function () { + expect(account.u_multisignatures).to.include(signer1.publicKey, signer2.publicKey); + }); + + it('should have u_multimin field set on account', function () { + expect(account.u_multimin).to.eql(multisigTransaction.asset.multisignature.min); + }); + + it('should have u_multilifetime field set on account', function () { + expect(account.u_multilifetime).to.eql(multisigTransaction.asset.multisignature.lifetime); + }); + }); + + describe('after deleting block', function () { + + before('delete last block', function (done) { + var last_block = library.modules.blocks.lastBlock.get(); + library.modules.blocks.chain.deleteLastBlock(done); + }); + + describe('sender db rows', function () { + var accountRow; + + before('get mem_account, mem_account2multisignature and mem_account2u_multisignature rows', function () { + return getAccountFromDb(multisigAccount.address).then(function (res) { + accountRow = res; + }); + }); + + it('should have no rows in mem_accounts2multisignatures', function () { + expect(accountRow.mem_accounts2multisignatures).to.eql([]); + }); + + it('should have multimin field set to 0 on mem_accounts', function () { + expect(accountRow.mem_accounts.multimin).to.eql(0); + }); + + it('should have multilifetime field set to 0 on mem_accounts', function () { + expect(accountRow.mem_accounts.multilifetime).to.eql(0); + }); + + it('should have no rows in mem_accounts2u_multisignatures', function () { + expect(accountRow.mem_accounts2u_multisignatures).to.eql([]); + }); + + it('should have u_multimin field set to 0 on mem_accounts', function () { + expect(accountRow.mem_accounts.u_multimin).to.eql(0); + }); + + it('should have multilifetime field to 0 on mem_accounts', function () { + expect(accountRow.mem_accounts.u_multilifetime).to.eql(0); + }); + }); + + describe('sender account', function () { + + var account; + + before('get multisignature account', function (done) { + library.logic.account.get({address: multisigAccount.address}, function (err, res) { + expect(err).to.not.exist; + account = res; + done(); + }); + }); + + it('should set multisignatures field to null on account', function () { + expect(account.multisignatures).to.eql(null); + }); + + it('should set multimin field to 0 on account', function () { + expect(account.multimin).to.eql(0); + }); + + it('should set multilifetime field to 0 on account', function () { + expect(account.multilifetime).to.eql(0); + }); + + it('should set u_multisignatures field to null on account', function () { + expect(account.u_multisignatures).to.eql(null); + }); + + it('should set u_multimin field to null on account', function () { + expect(account.u_multimin).to.eql(0); + }); + + it('should set u_multilifetime field to null on account', function () { + expect(account.u_multilifetime).to.eql(0); + }); + }); + }); + }); + }); + }); +}); diff --git a/test/node.js b/test/node.js index 53c409198b3..9ac38cecd9e 100644 --- a/test/node.js +++ b/test/node.js @@ -2,8 +2,10 @@ // Root object var node = {}; -var Rounds = require('../modules/rounds.js'); + var slots = require('../helpers/slots.js'); +var async = require('async'); +var strftime = require('strftime').utc(); // Requires node.bignum = require('../helpers/bignum.js'); @@ -70,7 +72,7 @@ node.gAccount = { if (process.env.SILENT === 'true') { node.debug = function () {}; } else { - node.debug = console.log; + node.debug = console.log.bind(console, '[' + strftime('%F %T', new Date()) + ']'); } // Random LSK amount @@ -286,11 +288,12 @@ node.randomUsername = function () { // Returns a random capitialized username node.randomCapitalUsername = function () { - var size = node.randomNumber(1, 16); // Min. username size is 1, Max. username size is 16 + // Min. username size is 1, Max. username size is 16 (we use 15 as max because of hardcoded first letter) + var size = node.randomNumber(1, 15); var username = 'A'; var possible = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789!@$&_.'; - for (var i = 0; i < size - 1; i++) { + for (var i = 0; i < size; i++) { username += possible.charAt(Math.floor(Math.random() * possible.length)); } @@ -299,11 +302,11 @@ node.randomCapitalUsername = function () { // Returns a random application name node.randomApplicationName = function () { - var size = node.randomNumber(1, 32); // Min. username size is 1, Max. username size is 32 + var size = node.randomNumber(1, 31); // Min. length of Application name length is 1, Max. Application name length is 32 var name = 'A'; var possible = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789'; - for (var i = 0; i < size - 1; i++) { + for (var i = 0; i < size; i++) { name += possible.charAt(Math.floor(Math.random() * possible.length)); } @@ -388,7 +391,7 @@ node.put = function (path, params, done) { return abstractRequest({ verb: 'PUT', path: path, params: params }, done); }; -before(function (done) { +before('wait for node to be ready', function (done) { require('./common/globalBefore').waitUntilBlockchainReady(done); });