From 83538c20f3e9521770e85a729ef4a5927a10870b Mon Sep 17 00:00:00 2001 From: Oliver Beddows Date: Mon, 10 Oct 2016 18:59:25 +0200 Subject: [PATCH 001/272] Do not apply or undo transaction when syncing. Ignoring transaction before processing. --- modules/transactions.js | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/modules/transactions.js b/modules/transactions.js index 7ff6f5bee87..922a008f416 100644 --- a/modules/transactions.js +++ b/modules/transactions.js @@ -188,8 +188,6 @@ __private.addUnconfirmedTransaction = function (transaction, sender, cb) { if (err) { self.removeUnconfirmedTransaction(transaction.id); return setImmediate(cb, err); - } else if (modules.loader.syncing()) { - self.undoUnconfirmed(transaction, cb); } else { transaction.receivedAt = new Date(); __private.unconfirmedTransactions.push(transaction); @@ -237,6 +235,11 @@ Transactions.prototype.processUnconfirmedTransaction = function (transaction, br return setImmediate(cb, 'Missing transaction'); } + // Ignore transaction when syncing + if (modules.loader.syncing()) { + return setImmediate(cb); + } + // Check transaction indexes if (__private.unconfirmedTransactionsIdIndex[transaction.id] !== undefined) { library.logger.debug('Transaction is already processed: ' + transaction.id); From d9c9444e816ad48a0aa62bafa8c4274d09aa65dd Mon Sep 17 00:00:00 2001 From: Oliver Beddows Date: Thu, 13 Oct 2016 17:40:28 +0200 Subject: [PATCH 002/272] Closes #104. Implementing peer sweeper. Collecting peer changes and sweeping in batches of 100 per second. --- helpers/peerSweeper.js | 93 +++++++++++++++++++++++++++++++ modules/peers.js | 124 +++++++++-------------------------------- modules/transport.js | 28 ++++------ sql/peers.js | 4 +- 4 files changed, 131 insertions(+), 118 deletions(-) create mode 100644 helpers/peerSweeper.js diff --git a/helpers/peerSweeper.js b/helpers/peerSweeper.js new file mode 100644 index 00000000000..8bc7b2890bd --- /dev/null +++ b/helpers/peerSweeper.js @@ -0,0 +1,93 @@ +'use strict'; + +var pgp = require('pg-promise'); + +// Constructor +function PeerSweeper (scope) { + this.peers = []; + this.limit = 100; + this.scope = scope; + + setInterval(function () { + if (this.peers.length) { + this.sweep(this.peers.splice(0, this.limit)); + } + }.bind(this), 1000); +} + +// Public methods +PeerSweeper.prototype.push = function (action, peer) { + if (peer.state === undefined) { + peer.state = 1; + } + if (action) { + peer.action = action; + } else { + throw 'Missing push action'; + } + this.peers.push(peer); +}; + +PeerSweeper.prototype.sweep = function (peers) { + var self = this; + + if (!peers.length) { return; } + + self.scope.library.db.tx(function (t) { + var queries = peers.map(function (peer) { + return pgp.as.format(self.scope.sql[peer.action], peer); + }); + + return t.query(queries.join(';')); + }).then(function () { + self.addDapps(peers); + + self.scope.library.logger.debug(['Swept', peers.length, 'peer changes'].join(' ')); + }).catch(function (err) { + self.scope.library.logger.error('Failed to sweep peers', err.stack); + }); +}; + +PeerSweeper.prototype.addDapps = function (peers) { + var self = this; + + peers = peers.filter(function (peer) { + return peer.action === 'upsert' && peer.dappid; + }); + + if (!peers.length) { return; } + + self.scope.library.db.tx(function (t) { + var peerPromises = peers.map(function (peer) { + if (peer.action === 'upsert') { + return t.query(self.scope.sql.getByIdPort, { ip: peer.ip, port: peer.port }); + } + }); + + return t.batch(peerPromises).then(function (res) { + for (var i = 0; i < peers.length; i++) { + var peer = peers[i]; + var row = res[i][0]; + + if (row && row.id) { + peer.id = row.id; + } + } + + var queries = peers.map(function (peer) { + return pgp.as.format(self.scope.sql.addDapp, { + dappId: peer.dappid, + peerId: peer.id + }); + }); + + return t.query(queries.join(';')); + }); + }).then(function () { + self.scope.library.logger.debug(['Added', peers.length, 'dapp peers'].join(' ')); + }).catch(function (err) { + self.scope.library.logger.error('Failed to add dapp peers', err.stack); + }); +}; + +module.exports = PeerSweeper; diff --git a/modules/peers.js b/modules/peers.js index 2cc0f376d3f..eaf481fd6fb 100644 --- a/modules/peers.js +++ b/modules/peers.js @@ -7,6 +7,7 @@ var fs = require('fs'); var ip = require('ip'); var OrderBy = require('../helpers/orderBy.js'); var path = require('path'); +var PeerSweeper = require('../helpers/peerSweeper.js'); var Router = require('../helpers/router.js'); var sandboxHelper = require('../helpers/sandbox.js'); var schema = require('../schema/peers.js'); @@ -26,6 +27,7 @@ function Peers (cb, scope) { self = this; __private.attachApi(); + __private.sweeper = new PeerSweeper({ library: library, sql: sql }); setImmediate(cb, null, self); } @@ -106,15 +108,11 @@ __private.updatePeersList = function (cb) { err.forEach(function (e) { library.logger.error(['Rejecting invalid peer:', peer.ip, e.path, e.message].join(' ')); }); - - return setImmediate(cb); } else { - library.dbSequence.add(function (cb) { - self.update(peer, cb); - }); - - return setImmediate(cb); + self.update(peer); } + + return setImmediate(cb); }); }, cb); }); @@ -240,109 +238,39 @@ Peers.prototype.list = function (options, cb) { }); }; -Peers.prototype.state = function (pip, port, state, timeoutSeconds, cb) { +Peers.prototype.state = function (pip, port, state, timeoutSeconds) { var isFrozenList = _.find(library.config.peers, function (peer) { return peer.ip === pip && peer.port === port; }); - if (isFrozenList !== undefined && cb) { - return setImmediate(cb, 'Peer in white list'); - } - var clock; - if (state === 0) { - clock = (timeoutSeconds || 1) * 1000; - clock = Date.now() + clock; - } else { - clock = null; + if (!isFrozenList) { + var clock; + if (state === 0) { + clock = (timeoutSeconds || 1) * 1000; + clock = Date.now() + clock; + } else { + clock = null; + } + return __private.sweeper.push('state', { + state: state, + clock: clock, + ip: pip, + port: port + }); } - var params = { - state: state, - clock: clock, - ip: pip, - port: port - }; - library.db.query(sql.state, params).then(function (res) { - library.logger.debug('Updated peer state', params); - return cb && setImmediate(cb, null, res); - }).catch(function (err) { - library.logger.error(err.stack); - return cb && setImmediate(cb); - }); }; -Peers.prototype.remove = function (pip, port, cb) { +Peers.prototype.remove = function (pip, port) { var isFrozenList = _.find(library.config.peers.list, function (peer) { return peer.ip === pip && peer.port === port; }); - if (isFrozenList !== undefined && cb) { - return setImmediate(cb, 'Peer in white list'); + if (!isFrozenList) { + removed.push(pip); + return __private.sweeper.push('remove', { ip: pip, port: port }); } - removed.push(pip); - var params = { - ip: pip, - port: port - }; - library.db.query(sql.remove, params).then(function (res) { - library.logger.debug('Removed peer', params); - return cb && setImmediate(cb, null, res); - }).catch(function (err) { - library.logger.error(err.stack); - return cb && setImmediate(cb); - }); }; -Peers.prototype.addDapp = function (config, cb) { - library.db.task(function (t) { - return t.query(sql.getByIdPort, { ip: config.ip, port: config.port }).then(function (rows) { - if (rows.length) { - var params = { - dappId: config.dappid, - peerId: rows[0].id - }; - - return t.query(sql.addDapp, params).then(function (res) { - library.logger.debug('Added dapp peer', params); - }); - } else { - return t; - } - }); - }).then(function (res) { - return setImmediate(cb, null, res); - }).catch(function (err) { - library.logger.error(err.stack); - return setImmediate(cb, 'Peers#addDapp error'); - }); -}; - -Peers.prototype.update = function (peer, cb) { - var params = { - ip: peer.ip, - port: peer.port, - os: peer.os || null, - version: peer.version || null, - state: 1 - }; - - var query; - if (peer.state !== undefined) { - params.state = peer.state; - query = sql.upsertWithState; - } else { - query = sql.upsertWithoutState; - } - - library.db.query(query, params).then(function () { - library.logger.debug('Upserted peer', params); - - if (peer.dappid) { - return self.addDapp({dappid: peer.dappid, ip: peer.ip, port: peer.port}, cb); - } else { - return setImmediate(cb); - } - }).catch(function (err) { - library.logger.error(err.stack); - return setImmediate(cb, 'Peers#update error'); - }); +Peers.prototype.update = function (peer) { + return __private.sweeper.push('upsert', peer); }; Peers.prototype.sandboxApi = function (call, args, cb) { diff --git a/modules/transport.js b/modules/transport.js index adaeaa91235..208378c1ace 100644 --- a/modules/transport.js +++ b/modules/transport.js @@ -88,9 +88,7 @@ __private.attachApi = function () { modules.delegates.enableForging(); } - library.dbSequence.add(function (cb) { - modules.peers.update(req.peer, cb); - }); + modules.peers.update(req.peer); } return next(); @@ -373,15 +371,13 @@ __private.hashsum = function (obj) { }; __private.banPeer = function (options) { - modules.peers.state(options.peer.ip, options.peer.port, 0, options.clock, function (err) { - library.logger.warn([options.code, ['Ban', options.peer.string, (options.clock / 60), 'minutes'].join(' '), options.req.method, options.req.url].join(' ')); - }); + library.logger.warn([options.code, ['Ban', options.peer.string, (options.clock / 60), 'minutes'].join(' '), options.req.method, options.req.url].join(' ')); + modules.peers.state(options.peer.ip, options.peer.port, 0, options.clock); }; __private.removePeer = function (options) { - modules.peers.remove(options.peer.ip, options.peer.port, function (err) { - library.logger.warn([options.code, 'Removing peer', options.peer.string, options.req.method, options.req.url].join(' ')); - }); + library.logger.warn([options.code, 'Removing peer', options.peer.string, options.req.method, options.req.url].join(' ')); + modules.peers.remove(options.peer.ip, options.peer.port); }; // Public methods @@ -477,14 +473,12 @@ Transport.prototype.getFromPeer = function (peer, options, cb) { } if (headers.version === library.config.version) { - library.dbSequence.add(function (cb) { - modules.peers.update({ - ip: peer.ip, - port: headers.port, - state: 2, - os: headers.os, - version: headers.version - }, cb); + modules.peers.update({ + ip: peer.ip, + port: headers.port, + state: 2, + os: headers.os, + version: headers.version }); } diff --git a/sql/peers.js b/sql/peers.js index a74870fc39e..1d9ec0b7288 100644 --- a/sql/peers.js +++ b/sql/peers.js @@ -32,9 +32,7 @@ var PeersSql = { addDapp: 'INSERT INTO peers_dapp ("peerId", "dappid") VALUES (${peerId}, ${dappId}) ON CONFLICT DO NOTHING;', - upsertWithState: 'INSERT INTO peers ("ip", "port", "state", "os", "version") VALUES (${ip}, ${port}, ${state}, ${os}, ${version}) ON CONFLICT ("ip", "port") DO UPDATE SET ("ip", "port", "state", "os", "version") = (${ip}, ${port}, (CASE WHEN EXCLUDED."state" = 0 THEN EXCLUDED."state" ELSE ${state} END), ${os}, ${version})', - - upsertWithoutState: 'INSERT INTO peers ("ip", "port", "os", "version") VALUES (${ip}, ${port}, ${os}, ${version}) ON CONFLICT ("ip", "port") DO UPDATE SET ("ip", "port", "os", "version") = (${ip}, ${port}, ${os}, ${version})', + upsert: 'INSERT INTO peers ("ip", "port", "state", "os", "version") VALUES (${ip}, ${port}, ${state}, ${os}, ${version}) ON CONFLICT ("ip", "port") DO UPDATE SET ("ip", "port", "state", "os", "version") = (${ip}, ${port}, (CASE WHEN EXCLUDED."state" = 0 THEN EXCLUDED."state" ELSE ${state} END), ${os}, ${version})', insertSeed: 'INSERT INTO peers("ip", "port", "state") VALUES(${ip}, ${port}, ${state}) ON CONFLICT DO NOTHING;', }; From cba381c5904b6df05417ef33360a6834e1fcbf01 Mon Sep 17 00:00:00 2001 From: Oliver Beddows Date: Thu, 13 Oct 2016 16:52:47 +0200 Subject: [PATCH 003/272] Closes #294. Loading unconfirmed transactions from "good" peer. Using async.waterfall to control flow. --- modules/loader.js | 135 ++++++++++++++++++++++++++++------------------ 1 file changed, 84 insertions(+), 51 deletions(-) diff --git a/modules/loader.js b/modules/loader.js index 511172740bb..706168faf28 100644 --- a/modules/loader.js +++ b/modules/loader.js @@ -78,73 +78,106 @@ __private.syncTrigger = function (turnOn) { }; __private.loadSignatures = function (cb) { - modules.transport.getFromRandomPeer({ - api: '/signatures', - method: 'GET' - }, function (err, res) { - if (err) { - return setImmediate(cb); - } - - library.schema.validate(res.body, schema.loadSignatures, function (err) { - if (err) { - return setImmediate(cb); - } - + async.waterfall([ + function (waterCb) { + self.getNetwork(function (err, network) { + if (err) { + return setImmediate(waterCb, err); + } else { + var peer = network.peers[Math.floor(Math.random() * network.peers.length)]; + return setImmediate(waterCb, null, peer); + } + }); + }, + function (peer, waterCb) { + modules.transport.getFromPeer(peer, { + api: '/signatures', + method: 'GET' + }, function (err, res) { + if (err) { + return setImmediate(waterCb, err); + } else { + library.schema.validate(res.body, schema.loadSignatures, function (err) { + return setImmediate(waterCb, err, res.body.signatures); + }); + } + }); + }, + function (signatures, waterCb) { library.sequence.add(function (cb) { - async.eachSeries(res.body.signatures, function (signature, cb) { - async.eachSeries(signature.signatures, function (s, cb) { + async.eachSeries(signatures, function (signature, eachSeriesCb) { + async.eachSeries(signature.signatures, function (s, eachSeriesCb) { modules.multisignatures.processSignature({ signature: s, transaction: signature.transaction }, function (err) { - return setImmediate(cb); + return setImmediate(eachSeriesCb); }); - }, cb); + }, eachSeriesCb); }, cb); - }, cb); - }); + }, waterCb); + } + ], function (err) { + return setImmediate(cb, err); }); }; __private.loadUnconfirmedTransactions = function (cb) { - modules.transport.getFromRandomPeer({ - api: '/transactions', - method: 'GET' - }, function (err, res) { - if (err) { - return setImmediate(cb); - } - - var report = library.schema.validate(res.body, schema.loadUnconfirmedTransactions); - - if (!report) { - return setImmediate(cb); - } - - var peer = modules.peers.inspect(res.peer); - var transactions = res.body.transactions; + async.waterfall([ + function (waterCb) { + self.getNetwork(function (err, network) { + if (err) { + return setImmediate(waterCb, err); + } else { + var peer = network.peers[Math.floor(Math.random() * network.peers.length)]; + return setImmediate(waterCb, null, peer); + } + }); + }, + function (peer, waterCb) { + modules.transport.getFromPeer(peer, { + api: '/transactions', + method: 'GET' + }, function (err, res) { + if (err) { + return setImmediate(waterCb, err); + } - for (var i = 0; i < transactions.length; i++) { - var transaction = transactions[i]; - var id = (transaction ? transactions.id : 'null'); + library.schema.validate(res.body, schema.loadUnconfirmedTransactions, function (err) { + if (err) { + return setImmediate(waterCb, err[0].message); + } else { + return setImmediate(waterCb, null, peer, res.body.transactions); + } + }); + }); + }, + function (peer, transactions, waterCb) { + async.eachSeries(transactions, function (transaction, eachSeriesCb) { + var id = (transaction ? transactions.id : 'null'); - try { - transaction = library.logic.transaction.objectNormalize(transaction); - } catch (e) { - library.logger.error(['Transaction', id].join(' '), e.toString()); - if (transaction) { library.logger.error('Transaction', transaction); } + try { + transaction = library.logic.transaction.objectNormalize(transaction); + } catch (e) { + library.logger.error(['Transaction', id].join(' '), e.toString()); + if (transaction) { library.logger.error('Transaction', transaction); } - library.logger.warn(['Transaction', id, 'is not valid, ban 60 min'].join(' '), peer.string); - modules.peers.state(peer.ip, peer.port, 0, 3600); + library.logger.warn(['Transaction', id, 'is not valid, ban 60 min'].join(' '), peer.string); + modules.peers.state(peer.ip, peer.port, 0, 3600); - return setImmediate(cb); - } + return setImmediate(waterCb, e); + } + }, function (err) { + return setImmediate(waterCb, err, transactions); + }); + }, + function (transactions, waterCb) { + library.balancesSequence.add(function (cb) { + modules.transactions.receiveTransactions(transactions, cb); + }, waterCb); } - - library.balancesSequence.add(function (cb) { - modules.transactions.receiveTransactions(transactions, cb); - }, cb); + ], function (err) { + return setImmediate(cb, err); }); }; From 9d4aa2aaf070a5c75f0eb7339b21e0c6e55a5683 Mon Sep 17 00:00:00 2001 From: Oliver Beddows Date: Thu, 13 Oct 2016 17:41:28 +0200 Subject: [PATCH 004/272] Removing command terminators. --- sql/peers.js | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/sql/peers.js b/sql/peers.js index 1d9ec0b7288..7499a636a72 100644 --- a/sql/peers.js +++ b/sql/peers.js @@ -5,7 +5,7 @@ var PeersSql = { count: 'SELECT COUNT(*)::int FROM peers', - banManager: 'UPDATE peers SET "state" = 1, "clock" = null WHERE ("state" = 0 AND "clock" - ${now} < 0);', + banManager: 'UPDATE peers SET "state" = 1, "clock" = null WHERE ("state" = 0 AND "clock" - ${now} < 0)', getByFilter: function (params) { return [ @@ -24,17 +24,17 @@ var PeersSql = { ].filter(Boolean).join(' '); }, - state: 'UPDATE peers SET "state" = ${state}, "clock" = ${clock} WHERE "ip" = ${ip} AND "port" = ${port};', + state: 'UPDATE peers SET "state" = ${state}, "clock" = ${clock} WHERE "ip" = ${ip} AND "port" = ${port}', - remove: 'DELETE FROM peers WHERE "ip" = ${ip} AND "port" = ${port};', + remove: 'DELETE FROM peers WHERE "ip" = ${ip} AND "port" = ${port}', getByIdPort: 'SELECT "id" FROM peers WHERE "ip" = ${ip} AND "port" = ${port}', - addDapp: 'INSERT INTO peers_dapp ("peerId", "dappid") VALUES (${peerId}, ${dappId}) ON CONFLICT DO NOTHING;', + addDapp: 'INSERT INTO peers_dapp ("peerId", "dappid") VALUES (${peerId}, ${dappId}) ON CONFLICT DO NOTHING', upsert: 'INSERT INTO peers ("ip", "port", "state", "os", "version") VALUES (${ip}, ${port}, ${state}, ${os}, ${version}) ON CONFLICT ("ip", "port") DO UPDATE SET ("ip", "port", "state", "os", "version") = (${ip}, ${port}, (CASE WHEN EXCLUDED."state" = 0 THEN EXCLUDED."state" ELSE ${state} END), ${os}, ${version})', - insertSeed: 'INSERT INTO peers("ip", "port", "state") VALUES(${ip}, ${port}, ${state}) ON CONFLICT DO NOTHING;', + insertSeed: 'INSERT INTO peers("ip", "port", "state") VALUES(${ip}, ${port}, ${state}) ON CONFLICT DO NOTHING', }; module.exports = PeersSql; From d00a58ae0fef0735eb037c5287e5bce83cd971a6 Mon Sep 17 00:00:00 2001 From: Oliver Beddows Date: Thu, 13 Oct 2016 21:09:49 +0200 Subject: [PATCH 005/272] Closed #284. Emitting delegates/fork event after forking. --- modules/delegates.js | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/modules/delegates.js b/modules/delegates.js index f363c97962d..1522484bd23 100644 --- a/modules/delegates.js +++ b/modules/delegates.js @@ -488,13 +488,17 @@ Delegates.prototype.fork = function (block, cause) { self.disableForging('fork'); - library.db.none(sql.insertFork, { + var fork = { delegatePublicKey: block.generatorPublicKey, blockTimestamp: block.timestamp, blockId: block.id, blockHeight: block.height, previousBlock: block.previousBlock, cause: cause + }; + + library.db.none(sql.insertFork, fork).then(function () { + library.network.io.sockets.emit('delegates/fork', fork); }); }; From c34a06e1e4e4d44341ce0949ee86d59ecd9e6ccd Mon Sep 17 00:00:00 2001 From: Oliver Beddows Date: Thu, 13 Oct 2016 21:12:58 +0200 Subject: [PATCH 006/272] Emitting transaction / data for each event. --- logic/dapp.js | 2 +- logic/multisignature.js | 2 +- modules/dapps.js | 10 +++++----- modules/multisignatures.js | 8 ++++---- modules/transport.js | 6 +++--- 5 files changed, 14 insertions(+), 14 deletions(-) diff --git a/logic/dapp.js b/logic/dapp.js index 482cef18e86..ab569a7677d 100644 --- a/logic/dapp.js +++ b/logic/dapp.js @@ -336,7 +336,7 @@ DApp.prototype.dbSave = function (trs) { }; DApp.prototype.afterSave = function (trs, cb) { - library.network.io.sockets.emit('dapps/change', {}); + library.network.io.sockets.emit('dapps/change', trs); return setImmediate(cb); }; diff --git a/logic/multisignature.js b/logic/multisignature.js index e1edceb6b50..9faf7d3a82f 100644 --- a/logic/multisignature.js +++ b/logic/multisignature.js @@ -301,7 +301,7 @@ Multisignature.prototype.dbSave = function (trs) { }; Multisignature.prototype.afterSave = function (trs, cb) { - library.network.io.sockets.emit('multisignatures/change', {}); + library.network.io.sockets.emit('multisignatures/change', trs); return setImmediate(cb); }; diff --git a/modules/dapps.js b/modules/dapps.js index e5f8fff92b1..af92db250c0 100644 --- a/modules/dapps.js +++ b/modules/dapps.js @@ -319,14 +319,14 @@ __private.attachApi = function () { }); }); } else { - library.network.io.sockets.emit('dapps/change', {}); + library.network.io.sockets.emit('dapps/change', dapp); __private.loading[body.id] = false; return res.json({success: true, path: dappPath}); } }); } else { - library.network.io.sockets.emit('dapps/change', {}); + library.network.io.sockets.emit('dapps/change', dapp); __private.loading[body.id] = false; return res.json({success: true, path: dappPath}); @@ -409,7 +409,7 @@ __private.attachApi = function () { if (err) { return res.json({success: false, error: err}); } else { - library.network.io.sockets.emit('dapps/change', {}); + library.network.io.sockets.emit('dapps/change', dapp); return res.json({success: true}); } @@ -423,7 +423,7 @@ __private.attachApi = function () { if (err) { return res.json({success: false, error: err}); } else { - library.network.io.sockets.emit('dapps/change', {}); + library.network.io.sockets.emit('dapps/change', dapp); return res.json({success: true}); } @@ -508,7 +508,7 @@ __private.attachApi = function () { library.logger.error(err); return res.json({success: false, error: 'Failed to stop application'}); } else { - library.network.io.sockets.emit('dapps/change', {}); + library.network.io.sockets.emit('dapps/change', dapp); __private.launched[body.id] = false; return res.json({success: true}); } diff --git a/modules/multisignatures.js b/modules/multisignatures.js index 91207feeea0..bd978f4ad13 100644 --- a/modules/multisignatures.js +++ b/modules/multisignatures.js @@ -296,7 +296,7 @@ Multisignatures.prototype.processSignature = function (tx, cb) { return setImmediate(cb, 'Failed to verify signature'); } - library.network.io.sockets.emit('multisignatures/signature/change', {}); + library.network.io.sockets.emit('multisignatures/signature/change', transaction); return done(cb); }); } @@ -356,7 +356,7 @@ shared.sign = function (req, cb) { return setImmediate(cb, 'Permission to sign transaction denied'); } - library.network.io.sockets.emit('multisignatures/signature/change', {}); + library.network.io.sockets.emit('multisignatures/signature/change', transaction); return done(cb); } else { modules.accounts.getAccount({ @@ -384,7 +384,7 @@ shared.sign = function (req, cb) { return setImmediate(cb, 'Permission to sign transaction denied'); } - library.network.io.sockets.emit('multisignatures/signature/change', {}); + library.network.io.sockets.emit('multisignatures/signature/change', transaction); return done(cb); }); } @@ -450,7 +450,7 @@ shared.addMultisignature = function (req, cb) { return setImmediate(cb, err); } - library.network.io.sockets.emit('multisignatures/change', {}); + library.network.io.sockets.emit('multisignatures/change', transaction); return setImmediate(cb, null, {transactionId: transaction[0].id}); }); }); diff --git a/modules/transport.js b/modules/transport.js index 208378c1ace..75bf1f155a9 100644 --- a/modules/transport.js +++ b/modules/transport.js @@ -524,21 +524,21 @@ Transport.prototype.onBlockchainReady = function () { Transport.prototype.onSignature = function (signature, broadcast) { if (broadcast) { self.broadcast({limit: 100}, {api: '/signatures', data: {signature: signature}, method: 'POST'}); - library.network.io.sockets.emit('signature/change', {}); + library.network.io.sockets.emit('signature/change', signature); } }; Transport.prototype.onUnconfirmedTransaction = function (transaction, broadcast) { if (broadcast) { self.broadcast({limit: 100}, {api: '/transactions', data: {transaction: transaction}, method: 'POST'}); - library.network.io.sockets.emit('transactions/change', {}); + library.network.io.sockets.emit('transactions/change', transaction); } }; Transport.prototype.onNewBlock = function (block, broadcast) { if (broadcast) { self.broadcast({limit: 100}, {api: '/blocks', data: {block: block}, method: 'POST'}); - library.network.io.sockets.emit('blocks/change', {}); + library.network.io.sockets.emit('blocks/change', block); } }; From 6375f58a2cc9d854a9a177ccded997aeadf5e746 Mon Sep 17 00:00:00 2001 From: Oliver Beddows Date: Thu, 13 Oct 2016 22:20:08 +0200 Subject: [PATCH 007/272] Refactoring code. Moving logic into verifySnapshot closure. --- modules/loader.js | 24 +++++++++++++++--------- 1 file changed, 15 insertions(+), 9 deletions(-) diff --git a/modules/loader.js b/modules/loader.js index 706168faf28..34ba4d8ed08 100644 --- a/modules/loader.js +++ b/modules/loader.js @@ -254,17 +254,9 @@ __private.loadBlockChain = function () { return t.batch(promises); } - library.db.task(checkMemTables).then(function (results) { - var count = results[0].count; - var missed = !(results[1].count); - - library.logger.info('Blocks ' + count); - - var round = modules.rounds.calc(count); - + function verifySnapshot (count, round) { if (library.config.loading.snapshot !== undefined || library.config.loading.snapshot > 0) { library.logger.info('Snapshot mode enabled'); - verify = true; if (isNaN(library.config.loading.snapshot) || library.config.loading.snapshot >= round) { library.config.loading.snapshot = round; @@ -275,12 +267,26 @@ __private.loadBlockChain = function () { } library.logger.info('Snapshotting to end of round: ' + library.config.loading.snapshot); + return true; + } else { + return false; } + } + + library.db.task(checkMemTables).then(function (results) { + var count = results[0].count; + var missed = !(results[1].count); + + library.logger.info('Blocks ' + count); + + var round = modules.rounds.calc(count); if (count === 1) { return reload(count); } + verify = verifySnapshot(count, round); + if (verify) { return reload(count, 'Blocks verification enabled'); } From 939d6a16da87240a002cfab893a5f56f9f09e7fc Mon Sep 17 00:00:00 2001 From: Oliver Beddows Date: Thu, 13 Oct 2016 22:27:54 +0200 Subject: [PATCH 008/272] Refactoring code. Using async.series to control flow. --- modules/loader.js | 86 +++++++++++++++++++++++++++-------------------- 1 file changed, 49 insertions(+), 37 deletions(-) diff --git a/modules/loader.js b/modules/loader.js index 34ba4d8ed08..fa1ce3746a7 100644 --- a/modules/loader.js +++ b/modules/loader.js @@ -189,49 +189,61 @@ __private.loadBlockChain = function () { verify = true; __private.total = count; - library.logic.account.removeTables(function (err) { - if (err) { - throw err; - } else { + async.series({ + removeTables: function (seriesCb) { + library.logic.account.removeTables(function (err) { + if (err) { + throw err; + } else { + return setImmediate(seriesCb); + } + }); + }, + createTables: function (seriesCb) { library.logic.account.createTables(function (err) { if (err) { throw err; } else { - async.until( - function () { - return count < offset; - }, function (cb) { - if (count > 1) { - library.logger.info('Rebuilding blockchain, current block height: ' + (offset + 1)); - } - modules.blocks.loadBlocksOffset(limit, offset, verify, function (err, lastBlock) { - if (err) { - return setImmediate(cb, err); - } - - offset = offset + limit; - __private.lastBlock = lastBlock; - - return setImmediate(cb); - }); - }, function (err) { - if (err) { - library.logger.error(err); - if (err.block) { - library.logger.error('Blockchain failed at: ' + err.block.height); - modules.blocks.simpleDeleteAfterBlock(err.block.id, function (err, res) { - library.logger.error('Blockchain clipped'); - library.bus.message('blockchainReady'); - }); - } - } else { - library.logger.info('Blockchain ready'); - library.bus.message('blockchainReady'); - } - } - ); + return setImmediate(seriesCb); } }); + }, + loadBlocksOffset: function (seriesCb) { + async.until( + function () { + return count < offset; + }, function (cb) { + if (count > 1) { + library.logger.info('Rebuilding blockchain, current block height: ' + (offset + 1)); + } + modules.blocks.loadBlocksOffset(limit, offset, verify, function (err, lastBlock) { + if (err) { + return setImmediate(cb, err); + } + + offset = offset + limit; + __private.lastBlock = lastBlock; + + return setImmediate(cb); + }); + }, function (err) { + return setImmediate(seriesCb, err); + } + ); + } + }, function (err) { + if (err) { + library.logger.error(err); + if (err.block) { + library.logger.error('Blockchain failed at: ' + err.block.height); + modules.blocks.simpleDeleteAfterBlock(err.block.id, function (err, res) { + library.logger.error('Blockchain clipped'); + library.bus.message('blockchainReady'); + }); + } + } else { + library.logger.info('Blockchain ready'); + library.bus.message('blockchainReady'); } }); } From 090e49a187cfd4760528b989d660f99e6196e856 Mon Sep 17 00:00:00 2001 From: Oliver Beddows Date: Thu, 13 Oct 2016 22:28:10 +0200 Subject: [PATCH 009/272] Returning load. --- modules/loader.js | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/modules/loader.js b/modules/loader.js index fa1ce3746a7..2d14c440b4b 100644 --- a/modules/loader.js +++ b/modules/loader.js @@ -253,7 +253,8 @@ __private.loadBlockChain = function () { library.logger.warn(message); library.logger.warn('Recreating memory tables'); } - load(count); + + return load(count); } function checkMemTables (t) { From 16793c85c3b57a5c2f3a278fead1e2a9d5c6a8b0 Mon Sep 17 00:00:00 2001 From: Oliver Beddows Date: Thu, 13 Oct 2016 22:31:54 +0200 Subject: [PATCH 010/272] Moving var declaration. --- modules/loader.js | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/modules/loader.js b/modules/loader.js index 2d14c440b4b..077ae8b5e0a 100644 --- a/modules/loader.js +++ b/modules/loader.js @@ -288,7 +288,6 @@ __private.loadBlockChain = function () { library.db.task(checkMemTables).then(function (results) { var count = results[0].count; - var missed = !(results[1].count); library.logger.info('Blocks ' + count); @@ -304,6 +303,8 @@ __private.loadBlockChain = function () { return reload(count, 'Blocks verification enabled'); } + var missed = !(results[1].count); + if (missed) { return reload(count, 'Detected missed blocks in mem_accounts'); } From f59d3df429069a6c89ca75bf59ae97648d5a9113 Mon Sep 17 00:00:00 2001 From: Oliver Beddows Date: Thu, 13 Oct 2016 22:35:25 +0200 Subject: [PATCH 011/272] Closes #255. Matching genesis block with database. --- modules/loader.js | 26 ++++++++++++++++++++++---- sql/loader.js | 2 ++ 2 files changed, 24 insertions(+), 4 deletions(-) diff --git a/modules/loader.js b/modules/loader.js index 077ae8b5e0a..3fcfbb65e70 100644 --- a/modules/loader.js +++ b/modules/loader.js @@ -260,6 +260,7 @@ __private.loadBlockChain = function () { function checkMemTables (t) { var promises = [ t.one(sql.countBlocks), + t.query(sql.getGenesisBlock), t.one(sql.countMemAccounts), t.query(sql.getMemRounds) ]; @@ -267,6 +268,21 @@ __private.loadBlockChain = function () { return t.batch(promises); } + function matchGenesisBlock (row) { + if (row) { + var matched = ( + row.id === __private.genesisBlock.block.id && + row.payloadHash.toString('hex') === __private.genesisBlock.block.payloadHash && + row.blockSignature.toString('hex') === __private.genesisBlock.block.blockSignature + ); + if (matched) { + library.logger.info('Genesis block matched with database'); + } else { + throw 'Failed to match genesis block with database'; + } + } + } + function verifySnapshot (count, round) { if (library.config.loading.snapshot !== undefined || library.config.loading.snapshot > 0) { library.logger.info('Snapshot mode enabled'); @@ -297,19 +313,21 @@ __private.loadBlockChain = function () { return reload(count); } + matchGenesisBlock(results[1][0]); + verify = verifySnapshot(count, round); if (verify) { return reload(count, 'Blocks verification enabled'); } - var missed = !(results[1].count); + var missed = !(results[2].count); if (missed) { return reload(count, 'Detected missed blocks in mem_accounts'); } - var unapplied = results[2].filter(function (row) { + var unapplied = results[3].filter(function (row) { return (row.round !== String(round)); }); @@ -347,8 +365,8 @@ __private.loadBlockChain = function () { }); }); }).catch(function (err) { - library.logger.error(err.stack); - return process.exit(0); + library.logger.error(err.stack || err); + return process.emit('exit'); }); }; diff --git a/sql/loader.js b/sql/loader.js index a32b5ef0ffe..39085051a12 100644 --- a/sql/loader.js +++ b/sql/loader.js @@ -3,6 +3,8 @@ var LoaderSql = { countBlocks: 'SELECT COUNT("rowId")::int FROM blocks', + getGenesisBlock: 'SELECT "id", "payloadHash", "blockSignature" FROM blocks WHERE "height" = 1', + countMemAccounts: 'SELECT COUNT(*)::int FROM mem_accounts WHERE "blockId" = (SELECT "id" FROM "blocks" ORDER BY "height" DESC LIMIT 1)', getMemRounds: 'SELECT "round" FROM mem_round GROUP BY "round"', From 78e29bb91cafb30c1b16ae03ac4073910ef06bd9 Mon Sep 17 00:00:00 2001 From: Oliver Beddows Date: Fri, 14 Oct 2016 11:15:21 +0200 Subject: [PATCH 012/272] Logging sync start and finish at debug level. --- modules/loader.js | 3 +++ 1 file changed, 3 insertions(+) diff --git a/modules/loader.js b/modules/loader.js index 3fcfbb65e70..6a1311852ac 100644 --- a/modules/loader.js +++ b/modules/loader.js @@ -438,6 +438,8 @@ __private.loadBlocksFromNetwork = function (cb) { __private.sync = function (cb) { var transactions = modules.transactions.getUnconfirmedTransactionList(true); + library.logger.debug('Starting sync'); + __private.isActive = true; __private.syncTrigger(true); @@ -458,6 +460,7 @@ __private.sync = function (cb) { __private.syncTrigger(false); __private.blocksToSync = 0; + library.logger.debug('Finished sync'); return setImmediate(cb, err); }); }; From 7e408cf020c31ddddbf09eed7a5c10bffa2d5d5a Mon Sep 17 00:00:00 2001 From: Oliver Beddows Date: Fri, 14 Oct 2016 11:20:25 +0200 Subject: [PATCH 013/272] Improving logs, moving to info level. --- modules/loader.js | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/modules/loader.js b/modules/loader.js index 6a1311852ac..0af5f119811 100644 --- a/modules/loader.js +++ b/modules/loader.js @@ -90,6 +90,8 @@ __private.loadSignatures = function (cb) { }); }, function (peer, waterCb) { + library.logger.info('Loading signatures from: ' + peer.string); + modules.transport.getFromPeer(peer, { api: '/signatures', method: 'GET' @@ -135,6 +137,8 @@ __private.loadUnconfirmedTransactions = function (cb) { }); }, function (peer, waterCb) { + library.logger.info('Loading unconfirmed transactions from: ' + peer.string); + modules.transport.getFromPeer(peer, { api: '/transactions', method: 'GET' @@ -617,7 +621,6 @@ Loader.prototype.onPeersReady = function () { setImmediate(function nextLoadUnconfirmedTransactions () { if (__private.loaded && !self.syncing()) { - library.logger.debug('Loading unconfirmed transactions'); __private.loadUnconfirmedTransactions(function (err) { if (err) { library.logger.warn('Unconfirmed transactions timer', err); @@ -632,7 +635,6 @@ Loader.prototype.onPeersReady = function () { setImmediate(function nextLoadSignatures () { if (__private.loaded && !self.syncing()) { - library.logger.debug('Loading signatures'); __private.loadSignatures(function (err) { if (err) { library.logger.warn('Signatures timer', err); From fdcfda16ce99e8cd469d828e25eb116f26df04c5 Mon Sep 17 00:00:00 2001 From: Oliver Beddows Date: Fri, 14 Oct 2016 11:20:35 +0200 Subject: [PATCH 014/272] Removing log. --- modules/loader.js | 1 - 1 file changed, 1 deletion(-) diff --git a/modules/loader.js b/modules/loader.js index 0af5f119811..11e04b7260b 100644 --- a/modules/loader.js +++ b/modules/loader.js @@ -604,7 +604,6 @@ Loader.prototype.onPeersReady = function () { var lastReceipt = modules.blocks.lastReceipt(); if (__private.loaded && !self.syncing() && (!lastReceipt || lastReceipt.stale)) { - library.logger.debug('Loading blocks from network'); library.sequence.add(function (cb) { __private.sync(cb); }, function (err) { From 9329a98046b4ebe5ecc1127719a1848956d5547d Mon Sep 17 00:00:00 2001 From: Oliver Beddows Date: Fri, 14 Oct 2016 11:24:46 +0200 Subject: [PATCH 015/272] Renaming variables. --- modules/peers.js | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/modules/peers.js b/modules/peers.js index eaf481fd6fb..974318a4718 100644 --- a/modules/peers.js +++ b/modules/peers.js @@ -239,10 +239,10 @@ Peers.prototype.list = function (options, cb) { }; Peers.prototype.state = function (pip, port, state, timeoutSeconds) { - var isFrozenList = _.find(library.config.peers, function (peer) { + var frozenPeer = _.find(library.config.peers, function (peer) { return peer.ip === pip && peer.port === port; }); - if (!isFrozenList) { + if (!frozenPeer) { var clock; if (state === 0) { clock = (timeoutSeconds || 1) * 1000; @@ -260,10 +260,10 @@ Peers.prototype.state = function (pip, port, state, timeoutSeconds) { }; Peers.prototype.remove = function (pip, port) { - var isFrozenList = _.find(library.config.peers.list, function (peer) { + var frozenPeer = _.find(library.config.peers.list, function (peer) { return peer.ip === pip && peer.port === port; }); - if (!isFrozenList) { + if (!frozenPeer) { removed.push(pip); return __private.sweeper.push('remove', { ip: pip, port: port }); } From fc50d84714fb6556452b1d0c50bf2c69814e9507 Mon Sep 17 00:00:00 2001 From: Oliver Beddows Date: Fri, 14 Oct 2016 11:39:13 +0200 Subject: [PATCH 016/272] Renaming function. --- modules/delegates.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/modules/delegates.js b/modules/delegates.js index 1522484bd23..ead5d5facc0 100644 --- a/modules/delegates.js +++ b/modules/delegates.js @@ -362,7 +362,7 @@ __private.checkDelegates = function (publicKey, votes, state, cb) { }); }; -__private.loadMyDelegates = function (cb) { +__private.loadDelegates = function (cb) { var secrets = null; if (library.config.forging.secret) { secrets = Array.isArray(library.config.forging.secret) ? library.config.forging.secret : [library.config.forging.secret]; @@ -538,7 +538,7 @@ Delegates.prototype.onBind = function (scope) { Delegates.prototype.onBlockchainReady = function () { __private.loaded = true; - __private.loadMyDelegates(function nextForge (err) { + __private.loadDelegates(function nextForge (err) { if (err) { library.logger.error('Failed to load delegates', err); } From 1cca72cf992de85e8343d2debcebdbc60adebed6 Mon Sep 17 00:00:00 2001 From: Oliver Beddows Date: Fri, 14 Oct 2016 11:44:38 +0200 Subject: [PATCH 017/272] Improving error / log messages. --- modules/delegates.js | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/modules/delegates.js b/modules/delegates.js index ead5d5facc0..9ca59c4b775 100644 --- a/modules/delegates.js +++ b/modules/delegates.js @@ -379,14 +379,14 @@ __private.loadDelegates = function (cb) { } if (!account) { - return setImmediate(cb, 'Account ' + keypair.publicKey.toString('hex') + ' not found'); + return setImmediate(cb, ['Account with public key:', keypair.publicKey.toString('hex'), 'not found'].join(' ')); } if (account.isDelegate) { __private.keypairs[keypair.publicKey.toString('hex')] = keypair; - library.logger.info('Forging enabled on account: ' + account.address); + library.logger.info(['Forging enabled on account:', account.address].join(' ')); } else { - library.logger.warn('Delegate with this public key not found: ' + keypair.publicKey.toString('hex')); + library.logger.warn(['Account with public key:', keypair.publicKey.toString('hex'), 'is not a delegate'].join(' ')); } return setImmediate(cb); }); From 552a40157ccb679260193ca84623175669cbfabd Mon Sep 17 00:00:00 2001 From: Oliver Beddows Date: Fri, 14 Oct 2016 11:51:29 +0200 Subject: [PATCH 018/272] Improving readability. --- modules/delegates.js | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/modules/delegates.js b/modules/delegates.js index 9ca59c4b775..e3233eee28c 100644 --- a/modules/delegates.js +++ b/modules/delegates.js @@ -363,9 +363,14 @@ __private.checkDelegates = function (publicKey, votes, state, cb) { }; __private.loadDelegates = function (cb) { - var secrets = null; + var secrets; + if (library.config.forging.secret) { - secrets = Array.isArray(library.config.forging.secret) ? library.config.forging.secret : [library.config.forging.secret]; + if (Array.isArray(library.config.forging.secret)) { + secrets = library.config.forging.secret + } else { + secrets = [library.config.forging.secret]; + } } async.eachSeries(secrets, function (secret, cb) { @@ -388,6 +393,7 @@ __private.loadDelegates = function (cb) { } else { library.logger.warn(['Account with public key:', keypair.publicKey.toString('hex'), 'is not a delegate'].join(' ')); } + return setImmediate(cb); }); }, cb); From 48d72b50ca1d6a717b807d1e7176608d490f4a94 Mon Sep 17 00:00:00 2001 From: Oliver Beddows Date: Fri, 14 Oct 2016 11:52:03 +0200 Subject: [PATCH 019/272] Closes #141. Attempting to load delegates on each cycle. --- modules/delegates.js | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/modules/delegates.js b/modules/delegates.js index e3233eee28c..87575f2287e 100644 --- a/modules/delegates.js +++ b/modules/delegates.js @@ -227,7 +227,7 @@ __private.getBlockSlotData = function (slot, height, cb) { __private.forge = function (cb) { if (!Object.keys(__private.keypairs).length) { library.logger.debug('No delegates enabled'); - return setImmediate(cb); + return __private.loadDelegates(cb); } if (!__private.forging) { @@ -367,12 +367,18 @@ __private.loadDelegates = function (cb) { if (library.config.forging.secret) { if (Array.isArray(library.config.forging.secret)) { - secrets = library.config.forging.secret + secrets = library.config.forging.secret; } else { secrets = [library.config.forging.secret]; } } + if (!secrets) { + return setImmediate(cb); + } else { + library.logger.info(['Loading', secrets.length, 'delegates from config'].join(' ')); + } + async.eachSeries(secrets, function (secret, cb) { var keypair = library.ed.makeKeypair(crypto.createHash('sha256').update(secret, 'utf8').digest()); From e22188b037b01e0fa18d94b1623c7246f8a9b05f Mon Sep 17 00:00:00 2001 From: Oliver Beddows Date: Sat, 15 Oct 2016 13:18:09 +0200 Subject: [PATCH 020/272] Checking for secrets length before loading. --- modules/delegates.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/modules/delegates.js b/modules/delegates.js index 87575f2287e..35b76ca5597 100644 --- a/modules/delegates.js +++ b/modules/delegates.js @@ -373,7 +373,7 @@ __private.loadDelegates = function (cb) { } } - if (!secrets) { + if (!secrets || !secrets.length) { return setImmediate(cb); } else { library.logger.info(['Loading', secrets.length, 'delegates from config'].join(' ')); From 4d6ba6c3da269d98316619c75eeee2f7eedf45b2 Mon Sep 17 00:00:00 2001 From: Oliver Beddows Date: Sat, 15 Oct 2016 18:24:53 +0200 Subject: [PATCH 021/272] Removing maxUpdatePeers option. --- config.json | 1 - modules/peers.js | 8 -------- 2 files changed, 9 deletions(-) diff --git a/config.json b/config.json index 9d7ead35c9d..644615c59a6 100644 --- a/config.json +++ b/config.json @@ -63,7 +63,6 @@ "delayAfter": 0, "windowMs": 60000 }, - "maxUpdatePeers": 20, "timeout": 5000 } }, diff --git a/modules/peers.js b/modules/peers.js index 974318a4718..b7aaa273e10 100644 --- a/modules/peers.js +++ b/modules/peers.js @@ -79,14 +79,6 @@ __private.updatePeersList = function (cb) { return removed.indexOf(peer.ip); }); - // Update only a subset of the peers to decrease the noise on the network. - // Default is 20 peers. To be fined tuned. Node gets checked by a peer every 3s on average. - // Maybe increasing schedule (every 60s right now). - var maxUpdatePeers = Math.floor(library.config.peers.options.maxUpdatePeers) || 20; - if (peers.length > maxUpdatePeers) { - peers = peers.slice(0, maxUpdatePeers); - } - // Drop one random peer from removed array to give them a chance. // This mitigates the issue that a node could be removed forever if it was offline for long. // This is not harmful for the node, but prevents network from shrinking, increasing noise. From 00eff87668e1ed1e082be2ba704f12335244e10f Mon Sep 17 00:00:00 2001 From: Oliver Beddows Date: Sat, 15 Oct 2016 18:25:41 +0200 Subject: [PATCH 022/272] Setting maximum number of peers. --- schema/peers.js | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/schema/peers.js b/schema/peers.js index 0ac2287e414..8d0d21ad600 100644 --- a/schema/peers.js +++ b/schema/peers.js @@ -8,7 +8,8 @@ module.exports = { properties: { peers: { type: 'array', - uniqueItems: true + uniqueItems: true, + maxItems: 100 } }, required: ['peers'] From 3d6c3282a3a0d057d2d0a10580095c67acae21eb Mon Sep 17 00:00:00 2001 From: Oliver Beddows Date: Sat, 15 Oct 2016 18:40:13 +0200 Subject: [PATCH 023/272] Adding blockReceiptTimeOut constant. Increasing timeout from 120 seconds to 500 seconds. --- helpers/constants.js | 1 + modules/blocks.js | 4 ++-- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/helpers/constants.js b/helpers/constants.js index 751f216538a..1c66aa7f319 100644 --- a/helpers/constants.js +++ b/helpers/constants.js @@ -4,6 +4,7 @@ module.exports = { activeDelegates: 101, addressLength: 208, blockHeaderLength: 248, + blockReceiptTimeOut: 500, // 50 blocks confirmationLength: 77, epochTime: new Date(Date.UTC(2016, 4, 24, 17, 0, 0, 0)), fees:{ diff --git a/modules/blocks.js b/modules/blocks.js index 65ac8a00f65..64cbe7ac9dd 100644 --- a/modules/blocks.js +++ b/modules/blocks.js @@ -473,7 +473,7 @@ Blocks.prototype.lastReceipt = function (lastReceipt) { if (__private.lastReceipt) { var timeNow = new Date(); __private.lastReceipt.secondsAgo = Math.floor((timeNow.getTime() - __private.lastReceipt.getTime()) / 1000); - __private.lastReceipt.stale = (__private.lastReceipt.secondsAgo > 120); + __private.lastReceipt.stale = (__private.lastReceipt.secondsAgo > constants.blockReceiptTimeOut); } return __private.lastReceipt; @@ -661,7 +661,7 @@ Blocks.prototype.getLastBlock = function () { var currentTime = new Date().getTime() / 1000; __private.lastBlock.secondsAgo = currentTime - lastBlockTime; - __private.lastBlock.fresh = (__private.lastBlock.secondsAgo < 120); + __private.lastBlock.fresh = (__private.lastBlock.secondsAgo < constants.blockReceiptTimeOut); } return __private.lastBlock; From 44f9e5527ec3fbea7ad91a37d78c2a40df3615f4 Mon Sep 17 00:00:00 2001 From: Oliver Beddows Date: Sat, 15 Oct 2016 20:49:00 +0200 Subject: [PATCH 024/272] =?UTF-8?q?Revising=20=5F=5Fprivate.goodPeers?= =?UTF-8?q?=C2=A0#297.?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Removing heights below last block height. --- modules/loader.js | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/modules/loader.js b/modules/loader.js index 11e04b7260b..0ba6a8e8623 100644 --- a/modules/loader.js +++ b/modules/loader.js @@ -471,9 +471,14 @@ __private.sync = function (cb) { // Given a list of peers with associated blockchain height (heights = {peer: peer, height: height}), we find a list of good peers (likely to sync with), then perform a histogram cut, removing peers far from the most common observed height. This is not as easy as it sounds, since the histogram has likely been made accross several blocks, therefore need to aggregate). __private.findGoodPeers = function (heights) { - // Removing unreachable peers + var lastBlockHeight = modules.blocks.getLastBlock().height; + heights = heights.filter(function (item) { + // Removing unreachable peers. return item != null; + }).filter(function (item) { + // Remove heights below last block height. + return item.height >= lastBlockHeight; }); // Assuming that the node reached at least 10% of the network From 699e244611d23c1f249ebe0c33a824578ac9cf25 Mon Sep 17 00:00:00 2001 From: Oliver Beddows Date: Sat, 15 Oct 2016 21:41:45 +0200 Subject: [PATCH 025/272] Logging good peers results at debug level #297. --- modules/loader.js | 3 +++ 1 file changed, 3 insertions(+) diff --git a/modules/loader.js b/modules/loader.js index 0ba6a8e8623..16c996ab988 100644 --- a/modules/loader.js +++ b/modules/loader.js @@ -516,6 +516,9 @@ __private.findGoodPeers = function (heights) { item.peer.height = item.height; return item.peer; }); + + library.logger.debug('Good peers', peers); + return {height: height, peers: peers}; } }; From dbced385400a1a117a22ee3062257ba678c11a99 Mon Sep 17 00:00:00 2001 From: Oliver Beddows Date: Sun, 16 Oct 2016 00:28:22 +0200 Subject: [PATCH 026/272] Serialising Loader.prototype.onPeersReady timers #297. --- modules/loader.js | 84 +++++++++++++++++++++++++---------------------- 1 file changed, 45 insertions(+), 39 deletions(-) diff --git a/modules/loader.js b/modules/loader.js index 16c996ab988..66b3f665613 100644 --- a/modules/loader.js +++ b/modules/loader.js @@ -169,8 +169,10 @@ __private.loadUnconfirmedTransactions = function (cb) { library.logger.warn(['Transaction', id, 'is not valid, ban 60 min'].join(' '), peer.string); modules.peers.state(peer.ip, peer.port, 0, 3600); - return setImmediate(waterCb, e); + return setImmediate(eachSeriesCb, e); } + + return setImmediate(eachSeriesCb); }, function (err) { return setImmediate(waterCb, err, transactions); }); @@ -608,50 +610,54 @@ Loader.prototype.sandboxApi = function (call, args, cb) { // Events Loader.prototype.onPeersReady = function () { - setImmediate(function nextLoadBlock () { - var lastReceipt = modules.blocks.lastReceipt(); - - if (__private.loaded && !self.syncing() && (!lastReceipt || lastReceipt.stale)) { - library.sequence.add(function (cb) { - __private.sync(cb); - }, function (err) { - if (err) { - library.logger.warn('Blocks timer', err); - } + setImmediate(function nextSeries () { + async.series({ + sync: function (seriesCb) { + var lastReceipt = modules.blocks.lastReceipt(); - setTimeout(nextLoadBlock, 10000); - }); - } else { - setTimeout(nextLoadBlock, 10000); - } - }); + if (__private.loaded && !self.syncing() && (!lastReceipt || lastReceipt.stale)) { + library.sequence.add(function (cb) { + __private.sync(cb); + }, function (err) { + if (err) { + library.logger.warn('Sync timer', err); + } - setImmediate(function nextLoadUnconfirmedTransactions () { - if (__private.loaded && !self.syncing()) { - __private.loadUnconfirmedTransactions(function (err) { - if (err) { - library.logger.warn('Unconfirmed transactions timer', err); + return setImmediate(seriesCb); + }); + } else { + return setImmediate(seriesCb); } + }, + loadUnconfirmedTransactions: function (seriesCb) { + if (__private.loaded) { + __private.loadUnconfirmedTransactions(function (err) { + if (err) { + library.logger.warn('Unconfirmed transactions timer', err); + } - setTimeout(nextLoadUnconfirmedTransactions, 14000); - }); - } else { - setTimeout(nextLoadUnconfirmedTransactions, 14000); - } - }); - - setImmediate(function nextLoadSignatures () { - if (__private.loaded && !self.syncing()) { - __private.loadSignatures(function (err) { - if (err) { - library.logger.warn('Signatures timer', err); + return setImmediate(seriesCb); + }); + } else { + return setImmediate(seriesCb); } + }, + loadSignatures: function (seriesCb) { + if (__private.loaded) { + __private.loadSignatures(function (err) { + if (err) { + library.logger.warn('Signatures timer', err); + } - setTimeout(nextLoadSignatures, 14000); - }); - } else { - setTimeout(nextLoadSignatures, 14000); - } + return setImmediate(seriesCb); + }); + } else { + return setImmediate(seriesCb); + } + } + }, function (err) { + return setTimeout(nextSeries, 10000); + }); }); }; From 46b2228e3bab3f79af0befac42847a18def2e011 Mon Sep 17 00:00:00 2001 From: Oliver Beddows Date: Sun, 16 Oct 2016 00:28:28 +0200 Subject: [PATCH 027/272] Increasing timeout of nextUnconfirmedExpiry. --- modules/transactions.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/modules/transactions.js b/modules/transactions.js index 922a008f416..4bdc2767522 100644 --- a/modules/transactions.js +++ b/modules/transactions.js @@ -440,7 +440,7 @@ Transactions.prototype.onPeersReady = function () { library.logger.error('Unconfirmed transactions timer:', err); } - setTimeout(nextUnconfirmedExpiry, 14 * 1000); + setTimeout(nextUnconfirmedExpiry, 30000); }); }); }; From e813075db0090e794c969c2938aa22e2459f60d6 Mon Sep 17 00:00:00 2001 From: Oliver Beddows Date: Sun, 16 Oct 2016 10:16:00 +0200 Subject: [PATCH 028/272] Removing try / catch. Headers are validated after initial peer inspection. --- modules/transport.js | 20 ++++++-------------- 1 file changed, 6 insertions(+), 14 deletions(-) diff --git a/modules/transport.js b/modules/transport.js index 75bf1f155a9..f1206c18a7d 100644 --- a/modules/transport.js +++ b/modules/transport.js @@ -40,20 +40,12 @@ __private.attachApi = function () { }); router.use(function (req, res, next) { - try { - req.peer = modules.peers.inspect( - { - ip: req.headers['x-forwarded-for'] || req.connection.remoteAddress, - port: req.headers.port - } - ); - } catch (e) { - // Remove peer - __private.removePeer({peer: req.peer, code: 'EHEADERS', req: req}); - - library.logger.debug(e.toString()); - return res.status(406).send({success: false, error: 'Invalid request headers'}); - } + req.peer = modules.peers.inspect( + { + ip: req.headers['x-forwarded-for'] || req.connection.remoteAddress, + port: req.headers.port + } + ); var headers = req.headers; headers.ip = req.peer.ip; From 4a5d9ab7c8d6ad77c83e7464bfdb3c914bf7162f Mon Sep 17 00:00:00 2001 From: Oliver Beddows Date: Sun, 16 Oct 2016 10:17:54 +0200 Subject: [PATCH 029/272] Removing redundant check on nethash. Already checked beforehand. --- modules/transport.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/modules/transport.js b/modules/transport.js index f1206c18a7d..00facffade7 100644 --- a/modules/transport.js +++ b/modules/transport.js @@ -75,7 +75,7 @@ __private.attachApi = function () { req.peer.dappid = req.body.dappid; } - if ((req.peer.version === library.config.version) && (headers.nethash === library.config.nethash)) { + if (req.peer.version === library.config.version) { if (!modules.blocks.lastReceipt()) { modules.delegates.enableForging(); } From 4449dcd6d3e9a837ef76131f3dec68a5ab1c9bff Mon Sep 17 00:00:00 2001 From: Oliver Beddows Date: Sun, 16 Oct 2016 13:24:07 +0200 Subject: [PATCH 030/272] Adding broadhash and height system headers #297. Updated after sync operation, after block broadcast. --- modules/loader.js | 3 +++ modules/system.js | 59 ++++++++++++++++++++++++++++++++++++++++++-- modules/transport.js | 27 ++++++++++++++------ sql/system.js | 7 ++++++ 4 files changed, 86 insertions(+), 10 deletions(-) create mode 100644 sql/system.js diff --git a/modules/loader.js b/modules/loader.js index 66b3f665613..5c423b40d87 100644 --- a/modules/loader.js +++ b/modules/loader.js @@ -457,6 +457,9 @@ __private.sync = function (cb) { loadBlocksFromNetwork: function (cb) { return __private.loadBlocksFromNetwork(cb); }, + updateSystem: function (cb) { + return modules.system.update(cb); + }, receiveTransactions: function (cb) { library.logger.debug('Receiving unconfirmed transactions after sync'); return modules.transactions.receiveTransactions(transactions, cb); diff --git a/modules/system.js b/modules/system.js index 0c22adca2c9..8e4e6dcba18 100644 --- a/modules/system.js +++ b/modules/system.js @@ -1,7 +1,10 @@ 'use strict'; +var async = require('async'); +var crypto = require('crypto'); var os = require('os'); var sandboxHelper = require('../helpers/sandbox.js'); +var sql = require('../sql/system.js'); // Private fields var modules, library, self, __private = {}, shared = {}; @@ -11,10 +14,12 @@ function System (cb, scope) { library = scope; self = this; + __private.os = os.platform() + os.release(); __private.version = library.config.version; __private.port = library.config.port; + __private.height = 1; __private.nethash = library.config.nethash; - __private.osName = os.platform() + os.release(); + __private.broadhash = library.config.nethash; setImmediate(cb, null, self); } @@ -22,8 +27,12 @@ function System (cb, scope) { // Private methods // Public methods +System.prototype.headers = function () { + return __private; +}; + System.prototype.getOS = function () { - return __private.osName; + return __private.os; }; System.prototype.getVersion = function () { @@ -34,10 +43,56 @@ System.prototype.getPort = function () { return __private.port; }; +System.prototype.getHeight = function () { + return __private.height; +}; + System.prototype.getNethash = function () { return __private.nethash; }; +System.prototype.getBroadhash = function (cb) { + if (typeof cb !== 'function') { + return __private.broadhash; + } + + library.db.query(sql.getBroadhash, { limit: 5 }).then(function (rows) { + if (rows.length <= 1) { + return setImmediate(cb, null, __private.nethash); + } else { + var seed = rows.map(function (row) { return row.id; }).join(''); + var hash = crypto.createHash('sha256').update(seed, 'utf8').digest(); + + return setImmediate(cb, null, hash.toString('hex')); + } + }).catch(function (err) { + library.logger.error(err.stack); + return setImmediate(cb, err); + }); +}; + +System.prototype.update = function (cb) { + async.series({ + getBroadhash: function (seriesCb) { + self.getBroadhash(function (err, hash) { + if (!err) { + __private.broadhash = hash; + } + + return setImmediate(seriesCb); + }); + }, + getHeight: function (seriesCb) { + __private.height = modules.blocks.getLastBlock().height; + return setImmediate(seriesCb); + } + }, function (err) { + library.logger.debug('System headers', __private); + modules.transport.headers(__private); + return setImmediate(cb, err); + }); +}; + System.prototype.sandboxApi = function (call, args, cb) { sandboxHelper.callMethod(shared, call, args, cb); }; diff --git a/modules/transport.js b/modules/transport.js index 00facffade7..02b45831270 100644 --- a/modules/transport.js +++ b/modules/transport.js @@ -373,10 +373,21 @@ __private.removePeer = function (options) { }; // Public methods +Transport.prototype.headers = function (headers) { + if (headers) { + __private.headers = headers; + } + + return __private.headers; +}; + Transport.prototype.broadcast = function (config, options, cb) { library.logger.debug('Broadcast', options); config.limit = config.limit || 1; + config.broadhash = config.broadhash || null; + config.height = config.height || null; + modules.peers.list(config, function (err, peers) { if (!err) { async.eachLimit(peers, 3, function (peer, cb) { @@ -501,12 +512,7 @@ Transport.prototype.sandboxApi = function (call, args, cb) { Transport.prototype.onBind = function (scope) { modules = scope; - __private.headers = { - os: modules.system.getOS(), - version: modules.system.getVersion(), - port: modules.system.getPort(), - nethash: modules.system.getNethash() - }; + __private.headers = modules.system.headers(); }; Transport.prototype.onBlockchainReady = function () { @@ -529,8 +535,13 @@ Transport.prototype.onUnconfirmedTransaction = function (transaction, broadcast) Transport.prototype.onNewBlock = function (block, broadcast) { if (broadcast) { - self.broadcast({limit: 100}, {api: '/blocks', data: {block: block}, method: 'POST'}); - library.network.io.sockets.emit('blocks/change', block); + var broadhash = modules.system.getBroadhash(); + var height = modules.system.getHeight(); + + modules.system.update(function () { + self.broadcast({limit: 100, broadhash: broadhash, height: height}, {api: '/blocks', data: {block: block}, method: 'POST'}); + library.network.io.sockets.emit('blocks/change', block); + }); } }; diff --git a/sql/system.js b/sql/system.js new file mode 100644 index 00000000000..eef397194cf --- /dev/null +++ b/sql/system.js @@ -0,0 +1,7 @@ +'use strict'; + +var SystemSql = { + getBroadhash: 'SELECT "id" FROM blocks ORDER BY "height" DESC LIMIT ${limit}' +}; + +module.exports = SystemSql; From 0dde367cc408946ff440ba27ebfd3e6105ea2ed9 Mon Sep 17 00:00:00 2001 From: Oliver Beddows Date: Sun, 16 Oct 2016 13:49:11 +0200 Subject: [PATCH 031/272] Adding broadhash and height columns to peers #297. --- .../20161016133824_addBroadhashColumnToPeers.sql | 11 +++++++++++ .../20161016133824_addHeightColumnToPeers.sql | 11 +++++++++++ 2 files changed, 22 insertions(+) create mode 100644 sql/migrations/20161016133824_addBroadhashColumnToPeers.sql create mode 100644 sql/migrations/20161016133824_addHeightColumnToPeers.sql diff --git a/sql/migrations/20161016133824_addBroadhashColumnToPeers.sql b/sql/migrations/20161016133824_addBroadhashColumnToPeers.sql new file mode 100644 index 00000000000..3e292fab38d --- /dev/null +++ b/sql/migrations/20161016133824_addBroadhashColumnToPeers.sql @@ -0,0 +1,11 @@ +/* Add Broadhash Column to Peers + * + */ + +BEGIN; + +ALTER TABLE "peers" ADD COLUMN "broadhash" bytea; + +CREATE INDEX IF NOT EXISTS "peers_broadhash" ON "peers"("broadhash"); + +COMMIT; diff --git a/sql/migrations/20161016133824_addHeightColumnToPeers.sql b/sql/migrations/20161016133824_addHeightColumnToPeers.sql new file mode 100644 index 00000000000..eaca3d25cf0 --- /dev/null +++ b/sql/migrations/20161016133824_addHeightColumnToPeers.sql @@ -0,0 +1,11 @@ +/* Add Height Column to Peers + * + */ + +BEGIN; + +ALTER TABLE "peers" ADD COLUMN "height" INT; + +CREATE INDEX IF NOT EXISTS "peers_height" ON "peers"("height"); + +COMMIT; From 31591ee9c9d97809b13227e9156606afbbe811b3 Mon Sep 17 00:00:00 2001 From: Oliver Beddows Date: Sun, 16 Oct 2016 14:18:33 +0200 Subject: [PATCH 032/272] Changing order. --- schema/transport.js | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/schema/transport.js b/schema/transport.js index 269199e9de9..7ecce93ef4d 100644 --- a/schema/transport.js +++ b/schema/transport.js @@ -18,16 +18,16 @@ module.exports = { type: 'string', maxLength: 64 }, - nethash: { - type: 'string', - maxLength: 64 - }, version: { type: 'string', maxLength: 11 + }, + nethash: { + type: 'string', + maxLength: 64 } }, - required: ['ip', 'port', 'nethash', 'version'] + required: ['ip', 'port', 'version', 'nethash'] }, commonBlock: { id: 'transport.commonBlock', From 5b623be5f5e4795a90626fcd148c13966388fe61 Mon Sep 17 00:00:00 2001 From: Oliver Beddows Date: Sun, 16 Oct 2016 14:19:22 +0200 Subject: [PATCH 033/272] Adding broadhash and height to schema #297. --- schema/loader.js | 7 +++++++ schema/peers.js | 7 +++++++ schema/transport.js | 7 +++++++ 3 files changed, 21 insertions(+) diff --git a/schema/loader.js b/schema/loader.js index 2e476b3cd70..38ba5f3dfcd 100644 --- a/schema/loader.js +++ b/schema/loader.js @@ -58,6 +58,13 @@ module.exports = { }, version: { type: 'string' + }, + broadhash: { + type: 'string', + format: 'hex' + }, + height: { + type: 'string' } }, required: ['ip', 'port', 'state'] diff --git a/schema/peers.js b/schema/peers.js index 8d0d21ad600..9911af0512e 100644 --- a/schema/peers.js +++ b/schema/peers.js @@ -39,6 +39,13 @@ module.exports = { version: { type: 'string', maxLength: 11 + }, + broadhash: { + type: 'string', + format: 'hex' + }, + height: { + type: 'string' } }, required: ['ip', 'port', 'state'] diff --git a/schema/transport.js b/schema/transport.js index 7ecce93ef4d..4c84c343bad 100644 --- a/schema/transport.js +++ b/schema/transport.js @@ -25,6 +25,13 @@ module.exports = { nethash: { type: 'string', maxLength: 64 + }, + broadhash: { + type: 'string', + format: 'hex' + }, + height: { + type: 'string' } }, required: ['ip', 'port', 'version', 'nethash'] From 3a18a99a4411587c187e62c3960f3f10eb5c0b8b Mon Sep 17 00:00:00 2001 From: Oliver Beddows Date: Sun, 16 Oct 2016 14:22:48 +0200 Subject: [PATCH 034/272] Adding broadhash and height to SQL queries #297. --- sql/peers.js | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/sql/peers.js b/sql/peers.js index 7499a636a72..9c5b44b5ddb 100644 --- a/sql/peers.js +++ b/sql/peers.js @@ -1,7 +1,7 @@ 'use strict'; var PeersSql = { - sortFields: ['ip', 'port', 'state', 'os', 'version'], + sortFields: ['ip', 'port', 'state', 'os', 'version', 'broadhash', 'height'], count: 'SELECT COUNT(*)::int FROM peers', @@ -9,7 +9,7 @@ var PeersSql = { getByFilter: function (params) { return [ - 'SELECT "ip", "port", "state", "os", "version" FROM peers', + 'SELECT "ip", "port", "state", "os", "version", ENCODE("broadhash", \'hex\') AS "broadhash", "height" FROM peers', (params.where.length ? 'WHERE ' + params.where.join(' AND ') : ''), (params.sortField ? 'ORDER BY ' + [params.sortField, params.sortMethod].join(' ') : 'ORDER BY random()'), 'LIMIT ${limit} OFFSET ${offset}' @@ -18,7 +18,7 @@ var PeersSql = { randomList: function (params) { return [ - 'SELECT p."ip", p."port", p."state", p."os", p."version" FROM peers p', + 'SELECT p."ip", p."port", p."state", p."os", p."version", ENCODE(p."broadhash", \'hex\') AS "broadhash", p."height" FROM peers p', (params.dappid ? 'INNER JOIN peers_dapp AS pd ON p."id" = pd."peerId" AND pd."dappid" = ${dappid}' : ''), 'WHERE p."state" > 0 ORDER BY RANDOM() LIMIT ${limit}' ].filter(Boolean).join(' '); @@ -32,7 +32,7 @@ var PeersSql = { addDapp: 'INSERT INTO peers_dapp ("peerId", "dappid") VALUES (${peerId}, ${dappId}) ON CONFLICT DO NOTHING', - upsert: 'INSERT INTO peers ("ip", "port", "state", "os", "version") VALUES (${ip}, ${port}, ${state}, ${os}, ${version}) ON CONFLICT ("ip", "port") DO UPDATE SET ("ip", "port", "state", "os", "version") = (${ip}, ${port}, (CASE WHEN EXCLUDED."state" = 0 THEN EXCLUDED."state" ELSE ${state} END), ${os}, ${version})', + upsert: 'INSERT INTO peers ("ip", "port", "state", "os", "version", "broadhash", "height") VALUES (${ip}, ${port}, ${state}, ${os}, ${version}, ${broadhash}, ${height}) ON CONFLICT ("ip", "port") DO UPDATE SET ("ip", "port", "state", "os", "version", "broadhash", "height") = (${ip}, ${port}, (CASE WHEN EXCLUDED."state" = 0 THEN EXCLUDED."state" ELSE ${state} END), ${os}, ${version}, ${broadhash}, ${height})', insertSeed: 'INSERT INTO peers("ip", "port", "state") VALUES(${ip}, ${port}, ${state}) ON CONFLICT DO NOTHING', }; From 04a6a969fd556caa088130a0fbf356083695cc30 Mon Sep 17 00:00:00 2001 From: Oliver Beddows Date: Sun, 16 Oct 2016 14:45:48 +0200 Subject: [PATCH 035/272] Adding broadhash and height to peer inspection #297. --- modules/peers.js | 2 ++ 1 file changed, 2 insertions(+) diff --git a/modules/peers.js b/modules/peers.js index b7aaa273e10..14a16bbb51e 100644 --- a/modules/peers.js +++ b/modules/peers.js @@ -215,6 +215,8 @@ Peers.prototype.inspect = function (peer) { peer.os = peer.os || 'unknown'; peer.version = peer.version || '0.0.0'; + peer.broadhash = peer.broadhash || ''; + peer.height = peer.height || ''; return peer; }; From b58e4a548ddfb06db85b20751f9c4077903b8972 Mon Sep 17 00:00:00 2001 From: Oliver Beddows Date: Sun, 16 Oct 2016 14:46:10 +0200 Subject: [PATCH 036/272] Inspecting peer just before sweeper push #297. --- modules/peers.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/modules/peers.js b/modules/peers.js index 14a16bbb51e..88f716f3f65 100644 --- a/modules/peers.js +++ b/modules/peers.js @@ -264,7 +264,7 @@ Peers.prototype.remove = function (pip, port) { }; Peers.prototype.update = function (peer) { - return __private.sweeper.push('upsert', peer); + return __private.sweeper.push('upsert', self.inspect(peer)); }; Peers.prototype.sandboxApi = function (call, args, cb) { From 26d3ad44fb7f74de412dcbca2875d81faed4040e Mon Sep 17 00:00:00 2001 From: Oliver Beddows Date: Sun, 16 Oct 2016 15:39:00 +0200 Subject: [PATCH 037/272] Rewriting Loader.prototype.getNetwork #297. - Using peer height from peers table when usable. - Moving peer logic to __private.getPeer. - Controlling flow using async.series. --- modules/loader.js | 139 ++++++++++++++++++++++++++++------------------ 1 file changed, 84 insertions(+), 55 deletions(-) diff --git a/modules/loader.js b/modules/loader.js index 5c423b40d87..cc14d5d8213 100644 --- a/modules/loader.js +++ b/modules/loader.js @@ -528,6 +528,55 @@ __private.findGoodPeers = function (heights) { } }; +__private.getPeer = function (peer, cb) { + async.series({ + validatePeer: function (seriesCb) { + peer = modules.peers.inspect(peer); + + library.schema.validate(peer, schema.getNetwork.peer, function (err) { + if (err) { + return setImmediate(seriesCb, ['Failed to validate peer:', err[0].path, err[0].message].join(' ')); + } else { + return setImmediate(seriesCb, null); + } + }); + }, + getHeight: function (seriesCb) { + if (peer.height) { + return setImmediate(seriesCb); + } else { + modules.transport.getFromPeer(peer, { + api: '/height', + method: 'GET' + }, function (err, res) { + if (err) { + return setImmediate(seriesCb, 'Failed to get height from peer: ' + peer.string); + } else { + peer.height = res.body.height; + return setImmediate(seriesCb); + } + }); + } + }, + validateHeight: function (seriesCb) { + var heightIsValid = library.schema.validate(peer, schema.getNetwork.height); + + if (heightIsValid) { + library.logger.info(['Received height:', peer.height, 'from peer:', peer.string].join(' ')); + return setImmediate(seriesCb); + } else { + return setImmediate(seriesCb, 'Received invalid height from peer: ' + peer.string); + } + } + }, function (err) { + if (err) { + peer.height = null; + library.logger.error(err); + } + return setImmediate(cb, null, {peer: peer, height: peer.height}); + }); +}; + // Public methods // Rationale: @@ -535,71 +584,51 @@ __private.findGoodPeers = function (heights) { // - Then for each of them we grab the height of their blockchain. // - With this list we try to get a peer with sensibly good blockchain height (see __private.findGoodPeers for actual strategy). Loader.prototype.getNetwork = function (cb) { - // If __private.network.height is not so far (i.e. 1 round) from current node height, just return cached __private.network. if (__private.network.height > 0 && Math.abs(__private.network.height - modules.blocks.getLastBlock().height) < 101) { return setImmediate(cb, null, __private.network); } - - // Fetch a list of 100 random peers - modules.transport.getFromRandomPeer({ - api: '/list', - method: 'GET' - }, function (err, res) { - if (err) { - library.logger.info('Failed to connect properly with network', err); - return setImmediate(cb, err); - } - - library.schema.validate(res.body, schema.getNetwork.peers, function (err) { - if (err) { - return setImmediate(cb, err); - } - - var peers = res.body.peers; - - library.logger.debug(['Received', res.body.peers.length, 'peers from'].join(' '), res.peer.string); - - // Validate each peer and then attempt to get its height - async.map(peers, function (peer, cb) { - var peerIsValid = library.schema.validate(modules.peers.inspect(peer), schema.getNetwork.peer); - - if (peerIsValid) { - modules.transport.getFromPeer(peer, { - api: '/height', - method: 'GET' - }, function (err, res) { - if (err) { - library.logger.error(err); - library.logger.warn('Failed to get height from peer', peer.string); - return setImmediate(cb); - } - - var heightIsValid = library.schema.validate(res.body, schema.getNetwork.height); - - if (heightIsValid) { - library.logger.info(['Received height:', res.body.height, 'from peer'].join(' '), peer.string); - return setImmediate(cb, null, {peer: peer, height: res.body.height}); - } else { - library.logger.warn('Received invalid height from peer', peer.string); - return setImmediate(cb); - } - }); + async.waterfall([ + function (waterCb) { + modules.transport.getFromRandomPeer({ + api: '/list', + method: 'GET' + }, function (err, res) { + if (err) { + return setImmediate(waterCb, err); } else { - library.logger.warn('Failed to validate peer', peer); - return setImmediate(cb); + return setImmediate(waterCb, null, res); } - }, function (err, heights) { - __private.network = __private.findGoodPeers(heights); + }); + }, + function (res, waterCb) { + library.schema.validate(res.body, schema.getNetwork.peers, function (err) { + var peers = res.body.peers; if (err) { - return setImmediate(cb, err); - } else if (!__private.network.peers.length) { - return setImmediate(cb, 'Failed to find enough good peers to sync with'); + return setImmediate(waterCb, err); } else { - return setImmediate(cb, null, __private.network); + library.logger.debug(['Received', peers.length, 'peers from'].join(' '), res.peer.string); + return setImmediate(waterCb, null, peers); } }); - }); + }, + function (peers, waterCb) { + async.map(peers, __private.getPeer, function (err, peers) { + return setImmediate(waterCb, err, peers); + }); + } + ], function (err, heights) { + if (err) { + return setImmediate(cb, err); + } + + __private.network = __private.findGoodPeers(heights); + + if (!__private.network.peers.length) { + return setImmediate(cb, 'Failed to find enough good peers to sync with'); + } else { + return setImmediate(cb, null, __private.network); + } }); }; From 3f8bb8aab0000d9905295bcb088f0c2bccfd9a40 Mon Sep 17 00:00:00 2001 From: Oliver Beddows Date: Sun, 16 Oct 2016 18:18:12 +0200 Subject: [PATCH 038/272] Changing log. --- modules/loader.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/modules/loader.js b/modules/loader.js index cc14d5d8213..7219f3e8da0 100644 --- a/modules/loader.js +++ b/modules/loader.js @@ -625,7 +625,7 @@ Loader.prototype.getNetwork = function (cb) { __private.network = __private.findGoodPeers(heights); if (!__private.network.peers.length) { - return setImmediate(cb, 'Failed to find enough good peers to sync with'); + return setImmediate(cb, 'Failed to find enough good peers'); } else { return setImmediate(cb, null, __private.network); } From 2d05057b95baf12cf071f09de6830d72382cf762 Mon Sep 17 00:00:00 2001 From: Oliver Beddows Date: Mon, 17 Oct 2016 15:23:22 +0200 Subject: [PATCH 039/272] Implementing broadhash targeted peers listing #297. Attempting to list peers which share the same broadhash, height. - Attempt 1: Matched broadhash peers - Attempt 2: Unmatched broadhash peers - Attempt 3: Legacy peers --- modules/peers.js | 66 ++++++++++++++++++++++++++++++++++++++++++++---- sql/peers.js | 7 +++-- 2 files changed, 66 insertions(+), 7 deletions(-) diff --git a/modules/peers.js b/modules/peers.js index 88f716f3f65..e82c99773a4 100644 --- a/modules/peers.js +++ b/modules/peers.js @@ -223,12 +223,68 @@ Peers.prototype.inspect = function (peer) { Peers.prototype.list = function (options, cb) { options.limit = options.limit || 100; + options.broadhash = options.broadhash || modules.system.getBroadhash(); + options.height = options.height || modules.system.getHeight(); + options.attempts = ['matched broadhash', 'unmatched broadhash', 'legacy']; + options.attempt = 0; - library.db.query(sql.randomList(options), options).then(function (rows) { - return setImmediate(cb, null, rows); - }).catch(function (err) { - library.logger.error(err.stack); - return setImmediate(cb, 'Peers#list error'); + if (!options.broadhash) { + delete options.broadhash; + } + + if (!options.height) { + delete options.height; + } + + function randomList (options, peers, cb) { + library.db.query(sql.randomList(options), options).then(function (rows) { + library.logger.debug(['Listing', rows.length, options.attempts[options.attempt], 'peers'].join(' ')); + return setImmediate(cb, null, peers.concat(rows)); + }).catch(function (err) { + library.logger.error(err.stack); + return setImmediate(cb, 'Peers#list error'); + }); + } + + function nextAttempt (peers) { + options.limit = 100; + options.limit = (options.limit - peers.length); + options.attempt += 1; + + if (options.attempt === 2) { + delete options.broadhash; + delete options.height; + } + } + + async.waterfall([ + // Broadhash + function (waterCb) { + return randomList(options, [], waterCb); + }, + // Unmatched + function (peers, waterCb) { + if (peers.length < options.limit && (options.broadhash || options.height)) { + nextAttempt(peers); + + return randomList(options, peers, waterCb); + } else { + return setImmediate(waterCb, null, peers); + } + }, + // Fallback + function (peers, waterCb) { + if (peers.length < options.limit) { + nextAttempt(peers); + + return randomList(options, peers, waterCb); + } else { + return setImmediate(waterCb, null, peers); + } + } + ], function (err, peers) { + library.logger.debug(['Listing', peers.length, 'total peers'].join(' ')); + return setImmediate(cb, err, peers); }); }; diff --git a/sql/peers.js b/sql/peers.js index 9c5b44b5ddb..0b0de0ee87a 100644 --- a/sql/peers.js +++ b/sql/peers.js @@ -20,7 +20,10 @@ var PeersSql = { return [ 'SELECT p."ip", p."port", p."state", p."os", p."version", ENCODE(p."broadhash", \'hex\') AS "broadhash", p."height" FROM peers p', (params.dappid ? 'INNER JOIN peers_dapp AS pd ON p."id" = pd."peerId" AND pd."dappid" = ${dappid}' : ''), - 'WHERE p."state" > 0 ORDER BY RANDOM() LIMIT ${limit}' + 'WHERE p."state" > 0', + (params.broadhash ? 'AND "broadhash" ' + (params.attempt === 0 ? '=' : '!=') + ' DECODE(${broadhash}, \'hex\')' : 'AND "broadhash" IS NULL'), + (params.height ? params.attempt === 0 ? 'AND "height" = ${height}' : 'OR "height" > ${height}' : 'OR "height" IS NULL'), + 'ORDER BY RANDOM() LIMIT ${limit}' ].filter(Boolean).join(' '); }, @@ -32,7 +35,7 @@ var PeersSql = { addDapp: 'INSERT INTO peers_dapp ("peerId", "dappid") VALUES (${peerId}, ${dappId}) ON CONFLICT DO NOTHING', - upsert: 'INSERT INTO peers ("ip", "port", "state", "os", "version", "broadhash", "height") VALUES (${ip}, ${port}, ${state}, ${os}, ${version}, ${broadhash}, ${height}) ON CONFLICT ("ip", "port") DO UPDATE SET ("ip", "port", "state", "os", "version", "broadhash", "height") = (${ip}, ${port}, (CASE WHEN EXCLUDED."state" = 0 THEN EXCLUDED."state" ELSE ${state} END), ${os}, ${version}, ${broadhash}, ${height})', + upsert: 'INSERT INTO peers AS p ("ip", "port", "state", "os", "version", "broadhash", "height") VALUES (${ip}, ${port}, ${state}, ${os}, ${version}, ${broadhash}, ${height}) ON CONFLICT ("ip", "port") DO UPDATE SET ("ip", "port", "state", "os", "version", "broadhash", "height") = (${ip}, ${port}, (CASE WHEN p."state" = 0 THEN p."state" ELSE ${state} END), ${os}, ${version}, (CASE WHEN ${broadhash} IS NULL THEN p."broadhash" ELSE ${broadhash} END), (CASE WHEN ${height} IS NULL THEN p."height" ELSE ${height} END))', insertSeed: 'INSERT INTO peers("ip", "port", "state") VALUES(${ip}, ${port}, ${state}) ON CONFLICT DO NOTHING', }; From 552bedac33052bf55fc58281dbeda26e1736ec67 Mon Sep 17 00:00:00 2001 From: Oliver Beddows Date: Sun, 16 Oct 2016 21:32:33 +0200 Subject: [PATCH 040/272] Adding test coverage #297. --- test/api/peer.js | 2 ++ 1 file changed, 2 insertions(+) diff --git a/test/api/peer.js b/test/api/peer.js index a419562816d..5536fb42f37 100644 --- a/test/api/peer.js +++ b/test/api/peer.js @@ -31,6 +31,8 @@ describe('GET /peer/list', function () { node.expect(peer).to.have.property('state').that.is.a('number'); node.expect(peer).to.have.property('os'); node.expect(peer).to.have.property('version'); + node.expect(peer).to.have.property('broadhash'); + node.expect(peer).to.have.property('height'); }); done(); }); From 3d0b1b9d0dfb1021efd9a89cc36a7d4929a702db Mon Sep 17 00:00:00 2001 From: Oliver Beddows Date: Sun, 16 Oct 2016 21:35:57 +0200 Subject: [PATCH 041/272] Changing order. --- modules/peers.js | 34 +++++++++++++++++----------------- 1 file changed, 17 insertions(+), 17 deletions(-) diff --git a/modules/peers.js b/modules/peers.js index e82c99773a4..6f80bb665d1 100644 --- a/modules/peers.js +++ b/modules/peers.js @@ -134,6 +134,16 @@ __private.getByFilter = function (filter, cb) { var where = []; var params = {}; + if (filter.ip) { + where.push('"ip" = ${ip}'); + params.ip = filter.ip; + } + + if (filter.port) { + where.push('"port" = ${port}'); + params.port = filter.port; + } + if (filter.state) { where.push('"state" = ${state}'); params.state = filter.state; @@ -149,14 +159,14 @@ __private.getByFilter = function (filter, cb) { params.version = filter.version; } - if (filter.ip) { - where.push('"ip" = ${ip}'); - params.ip = filter.ip; - } + var orderBy = OrderBy( + filter.orderBy, { + sortFields: sql.sortFields + } + ); - if (filter.port) { - where.push('"port" = ${port}'); - params.port = filter.port; + if (orderBy.error) { + return setImmediate(cb, orderBy.error); } if (!filter.limit) { @@ -175,16 +185,6 @@ __private.getByFilter = function (filter, cb) { return setImmediate(cb, 'Invalid limit. Maximum is 100'); } - var orderBy = OrderBy( - filter.orderBy, { - sortFields: sql.sortFields - } - ); - - if (orderBy.error) { - return setImmediate(cb, orderBy.error); - } - library.db.query(sql.getByFilter({ where: where, sortField: orderBy.sortField, From 9575459d82422e2a85bde1af39121e729d78e210 Mon Sep 17 00:00:00 2001 From: Oliver Beddows Date: Sun, 16 Oct 2016 21:36:40 +0200 Subject: [PATCH 042/272] Adding ip to peer.getPeers schema. --- schema/peers.js | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/schema/peers.js b/schema/peers.js index 9911af0512e..196a09e8d6f 100644 --- a/schema/peers.js +++ b/schema/peers.js @@ -55,6 +55,10 @@ module.exports = { id: 'peer.getPeers', type: 'object', properties: { + ip: { + type: 'string', + format: 'ip' + }, port: { type: 'integer', minimum: 1, From d364f9e9d631c2f834830eb8684e35ed6fb5fa48 Mon Sep 17 00:00:00 2001 From: Oliver Beddows Date: Sun, 16 Oct 2016 22:28:39 +0200 Subject: [PATCH 043/272] Removing naff examples. --- test/api/peers.js | 58 +++++++++-------------------------------------- 1 file changed, 11 insertions(+), 47 deletions(-) diff --git a/test/api/peers.js b/test/api/peers.js index a47c0a22f43..2b91ff40601 100644 --- a/test/api/peers.js +++ b/test/api/peers.js @@ -16,24 +16,6 @@ describe('GET /api/peers/version', function () { describe('GET /api/peers', function () { - it('using empty parameters should fail', function (done) { - var params = [ - 'state=', - 'os=', - 'shared=', - 'version=', - 'limit=', - 'offset=', - 'orderBy=' - ]; - - node.get('/api/peers?' + params.join('&'), function (err, res) { - node.expect(res.body).to.have.property('success').to.be.not.ok; - node.expect(res.body).to.have.property('error'); - done(); - }); - }); - it('using state should be ok', function (done) { var state = 1; var params = 'state=' + state; @@ -62,6 +44,17 @@ describe('GET /api/peers', function () { }); }); + it('using limit > 100 should fail', function (done) { + var limit = 101; + var params = 'limit=' + limit; + + node.get('/api/peers?' + params, function (err, res) { + node.expect(res.body).to.have.property('success').to.be.not.ok; + node.expect(res.body).to.have.property('error'); + done(); + }); + }); + it('using orderBy should be ok', function (done) { var orderBy = 'state:desc'; var params = 'orderBy=' + orderBy; @@ -81,35 +74,6 @@ describe('GET /api/peers', function () { done(); }); }); - - it('using limit > 100 should fail', function (done) { - var limit = 101; - var params = 'limit=' + limit; - - node.get('/api/peers?' + params, function (err, res) { - node.expect(res.body).to.have.property('success').to.be.not.ok; - node.expect(res.body).to.have.property('error'); - done(); - }); - }); - - it('using invalid parameters should fail', function (done) { - var params = [ - 'state=invalid', - 'os=invalid', - 'shared=invalid', - 'version=invalid', - 'limit=invalid', - 'offset=invalid', - 'orderBy=invalid' - ]; - - node.get('/api/peers?' + params.join('&'), function (err, res) { - node.expect(res.body).to.have.property('success').to.be.not.ok; - node.expect(res.body).to.have.property('error'); - done(); - }); - }); }); describe('GET /api/peers/get', function () { From 178ee1461d9c546561b84dc36d760fc2698a7f77 Mon Sep 17 00:00:00 2001 From: Oliver Beddows Date: Sun, 16 Oct 2016 22:29:19 +0200 Subject: [PATCH 044/272] Adding broadhash and height filters #297. --- modules/peers.js | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/modules/peers.js b/modules/peers.js index 6f80bb665d1..02a9e5920da 100644 --- a/modules/peers.js +++ b/modules/peers.js @@ -159,6 +159,16 @@ __private.getByFilter = function (filter, cb) { params.version = filter.version; } + if (filter.broadhash) { + where.push('"broadhash" = ${broadhash}'); + params.broadhash = filter.broadhash; + } + + if (filter.height) { + where.push('"height" = ${height}'); + params.height = filter.height; + } + var orderBy = OrderBy( filter.orderBy, { sortFields: sql.sortFields From f6ae9af7c5cad9230ab31640059ad1adfc786b4c Mon Sep 17 00:00:00 2001 From: Oliver Beddows Date: Sun, 16 Oct 2016 22:41:32 +0200 Subject: [PATCH 045/272] Adding broadhash and height peer.getPeers schema #297. --- schema/peers.js | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/schema/peers.js b/schema/peers.js index 196a09e8d6f..0f99f5c27c7 100644 --- a/schema/peers.js +++ b/schema/peers.js @@ -77,6 +77,13 @@ module.exports = { type: 'string', maxLength: 11 }, + broadhash: { + type: 'string', + format: 'hex' + }, + height: { + type: 'integer' + }, orderBy: { type: 'string' }, From 086a66c41842b03103b395c6f5f9997dee471dee Mon Sep 17 00:00:00 2001 From: Oliver Beddows Date: Sun, 16 Oct 2016 23:47:16 +0200 Subject: [PATCH 046/272] Changing order. --- test/api/delegates.js | 204 +++++++++++++++++++++--------------------- 1 file changed, 102 insertions(+), 102 deletions(-) diff --git a/test/api/delegates.js b/test/api/delegates.js index f9bcea81cc8..41eb1241f97 100644 --- a/test/api/delegates.js +++ b/test/api/delegates.js @@ -391,108 +391,6 @@ describe('GET /api/delegates', function () { }); }); - it('using string limit should fail', function (done) { - var limit = 'one'; - var params = 'limit=' + limit; - - node.get('/api/delegates?' + params, function (err, res) { - node.expect(res.body).to.have.property('success').to.be.not.ok; - node.expect(res.body).to.have.property('error').to.equal('Expected type integer but found type string'); - done(); - }); - }); - - it('using limit == -1 should fail', function (done) { - var limit = -1; - var params = 'limit=' + limit; - - node.get('/api/delegates?' + params, function (err, res) { - node.expect(res.body).to.have.property('success').to.be.not.ok; - node.expect(res.body).to.have.property('error').to.equal('Value -1 is less than minimum 1'); - done(); - }); - }); - - it('using limit == 0 should fail', function (done) { - var limit = 0; - var params = 'limit=' + limit; - - node.get('/api/delegates?' + params, function (err, res) { - node.expect(res.body).to.have.property('success').to.be.not.ok; - node.expect(res.body).to.have.property('error').to.equal('Value 0 is less than minimum 1'); - done(); - }); - }); - - it('using limit == 1 should be ok', function (done) { - var limit = 1; - var params = 'limit=' + limit; - - node.get('/api/delegates?' + params, function (err, res) { - node.expect(res.body).to.have.property('success').to.be.ok; - node.expect(res.body).to.have.property('delegates').that.is.an('array'); - node.expect(res.body.delegates).to.have.lengthOf(1); - done(); - }); - }); - - it('using limit == 101 should be ok', function (done) { - var limit = 101; - var params = 'limit=' + limit; - - node.get('/api/delegates?' + params, function (err, res) { - node.expect(res.body).to.have.property('success').to.be.ok; - node.expect(res.body).to.have.property('delegates').that.is.an('array'); - node.expect(res.body.delegates).to.have.lengthOf(101); - done(); - }); - }); - - it('using limit > 101 should fail', function (done) { - var limit = 102; - var params = 'limit=' + limit; - - node.get('/api/delegates?' + params, function (err, res) { - node.expect(res.body).to.have.property('success').to.be.not.ok; - node.expect(res.body).to.have.property('error').to.equal('Value 102 is greater than maximum 101'); - done(); - }); - }); - - it('using string offset should fail', function (done) { - var limit = 'one'; - var params = 'offset=' + limit; - - node.get('/api/delegates?' + params, function (err, res) { - node.expect(res.body).to.have.property('success').to.be.not.ok; - node.expect(res.body).to.have.property('error').to.equal('Expected type integer but found type string'); - done(); - }); - }); - - it('using offset == 1 should be ok', function (done) { - var offset = 1; - var params = 'offset=' + offset; - - node.get('/api/delegates?' + params, function (err, res) { - node.expect(res.body).to.have.property('success').to.be.ok; - node.expect(res.body).to.have.property('delegates').that.is.an('array'); - node.expect(res.body.delegates).to.have.lengthOf(101); - done(); - }); - }); - - it('using offset == -1 should fail', function (done) { - var offset = -1; - var params = 'offset=' + offset; - - node.get('/api/delegates?' + params, function (err, res) { - node.expect(res.body).to.have.property('success').to.be.not.ok; - node.expect(res.body).to.have.property('error').to.equal('Value -1 is less than minimum 0'); - done(); - }); - }); - it('using orderBy == "unknown:asc" should fail', function (done) { var orderBy = 'unknown:asc'; var params = 'orderBy=' + orderBy; @@ -631,6 +529,108 @@ describe('GET /api/delegates', function () { done(); }); }); + + it('using string limit should fail', function (done) { + var limit = 'one'; + var params = 'limit=' + limit; + + node.get('/api/delegates?' + params, function (err, res) { + node.expect(res.body).to.have.property('success').to.be.not.ok; + node.expect(res.body).to.have.property('error').to.equal('Expected type integer but found type string'); + done(); + }); + }); + + it('using limit == -1 should fail', function (done) { + var limit = -1; + var params = 'limit=' + limit; + + node.get('/api/delegates?' + params, function (err, res) { + node.expect(res.body).to.have.property('success').to.be.not.ok; + node.expect(res.body).to.have.property('error').to.equal('Value -1 is less than minimum 1'); + done(); + }); + }); + + it('using limit == 0 should fail', function (done) { + var limit = 0; + var params = 'limit=' + limit; + + node.get('/api/delegates?' + params, function (err, res) { + node.expect(res.body).to.have.property('success').to.be.not.ok; + node.expect(res.body).to.have.property('error').to.equal('Value 0 is less than minimum 1'); + done(); + }); + }); + + it('using limit == 1 should be ok', function (done) { + var limit = 1; + var params = 'limit=' + limit; + + node.get('/api/delegates?' + params, function (err, res) { + node.expect(res.body).to.have.property('success').to.be.ok; + node.expect(res.body).to.have.property('delegates').that.is.an('array'); + node.expect(res.body.delegates).to.have.lengthOf(1); + done(); + }); + }); + + it('using limit == 101 should be ok', function (done) { + var limit = 101; + var params = 'limit=' + limit; + + node.get('/api/delegates?' + params, function (err, res) { + node.expect(res.body).to.have.property('success').to.be.ok; + node.expect(res.body).to.have.property('delegates').that.is.an('array'); + node.expect(res.body.delegates).to.have.lengthOf(101); + done(); + }); + }); + + it('using limit > 101 should fail', function (done) { + var limit = 102; + var params = 'limit=' + limit; + + node.get('/api/delegates?' + params, function (err, res) { + node.expect(res.body).to.have.property('success').to.be.not.ok; + node.expect(res.body).to.have.property('error').to.equal('Value 102 is greater than maximum 101'); + done(); + }); + }); + + it('using string offset should fail', function (done) { + var limit = 'one'; + var params = 'offset=' + limit; + + node.get('/api/delegates?' + params, function (err, res) { + node.expect(res.body).to.have.property('success').to.be.not.ok; + node.expect(res.body).to.have.property('error').to.equal('Expected type integer but found type string'); + done(); + }); + }); + + it('using offset == 1 should be ok', function (done) { + var offset = 1; + var params = 'offset=' + offset; + + node.get('/api/delegates?' + params, function (err, res) { + node.expect(res.body).to.have.property('success').to.be.ok; + node.expect(res.body).to.have.property('delegates').that.is.an('array'); + node.expect(res.body.delegates).to.have.lengthOf(101); + done(); + }); + }); + + it('using offset == -1 should fail', function (done) { + var offset = -1; + var params = 'offset=' + offset; + + node.get('/api/delegates?' + params, function (err, res) { + node.expect(res.body).to.have.property('success').to.be.not.ok; + node.expect(res.body).to.have.property('error').to.equal('Value -1 is less than minimum 0'); + done(); + }); + }); }); describe('GET /api/delegates/count', function () { From fb26e3bb3d465acb95a463b9ffe16de78c51b2b5 Mon Sep 17 00:00:00 2001 From: Oliver Beddows Date: Sun, 16 Oct 2016 23:47:44 +0200 Subject: [PATCH 047/272] Standardising code. --- sql/peers.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sql/peers.js b/sql/peers.js index 0b0de0ee87a..56dced25bce 100644 --- a/sql/peers.js +++ b/sql/peers.js @@ -11,7 +11,7 @@ var PeersSql = { return [ 'SELECT "ip", "port", "state", "os", "version", ENCODE("broadhash", \'hex\') AS "broadhash", "height" FROM peers', (params.where.length ? 'WHERE ' + params.where.join(' AND ') : ''), - (params.sortField ? 'ORDER BY ' + [params.sortField, params.sortMethod].join(' ') : 'ORDER BY random()'), + (params.sortField ? 'ORDER BY ' + [params.sortField, params.sortMethod].join(' ') : 'ORDER BY RANDOM()'), 'LIMIT ${limit} OFFSET ${offset}' ].filter(Boolean).join(' '); }, From 9b2cac799ea9d8b839124ee571f2efc98ea56968 Mon Sep 17 00:00:00 2001 From: Oliver Beddows Date: Sun, 16 Oct 2016 23:51:24 +0200 Subject: [PATCH 048/272] Normalising schema. --- schema/blocks.js | 2 +- schema/dapps.js | 10 +++++----- schema/peers.js | 6 +++--- schema/transactions.js | 26 +++++++++++++------------- 4 files changed, 22 insertions(+), 22 deletions(-) diff --git a/schema/blocks.js b/schema/blocks.js index 87ace052ea0..b20bd49ed7b 100644 --- a/schema/blocks.js +++ b/schema/blocks.js @@ -24,7 +24,7 @@ module.exports = { properties: { limit: { type: 'integer', - minimum: 0, + minimum: 1, maximum: 100 }, orderBy: { diff --git a/schema/dapps.js b/schema/dapps.js index 9841058999e..83af776ba26 100644 --- a/schema/dapps.js +++ b/schema/dapps.js @@ -101,18 +101,18 @@ module.exports = { minLength: 1, maxLength: 2000 }, + orderBy: { + type: 'string', + minLength: 1 + }, limit: { type: 'integer', - minimum: 0, + minimum: 1, maximum: 100 }, offset: { type: 'integer', minimum: 0 - }, - orderBy: { - type: 'string', - minLength: 1 } } }, diff --git a/schema/peers.js b/schema/peers.js index 0f99f5c27c7..a12c405339d 100644 --- a/schema/peers.js +++ b/schema/peers.js @@ -30,7 +30,7 @@ module.exports = { state: { type: 'integer', minimum: 0, - maximum: 3 + maximum: 2 }, os: { type: 'string', @@ -67,7 +67,7 @@ module.exports = { state: { type: 'integer', minimum: 0, - maximum: 3 + maximum: 2 }, os: { type: 'string', @@ -89,7 +89,7 @@ module.exports = { }, limit: { type: 'integer', - minimum: 0, + minimum: 1, maximum: 100 }, offset: { diff --git a/schema/transactions.js b/schema/transactions.js index 2b05e34d9a4..a826c60190d 100644 --- a/schema/transactions.js +++ b/schema/transactions.js @@ -10,23 +10,14 @@ module.exports = { blockId: { type: 'string' }, - limit: { - type: 'integer', - minimum: 0, - maximum: 100 - }, type: { type: 'integer', minimum: 0, maximum: 10 }, - orderBy: { + senderId: { type: 'string' }, - offset: { - type: 'integer', - minimum: 0 - }, senderPublicKey: { type: 'string', format: 'publicKey' @@ -38,9 +29,6 @@ module.exports = { ownerAddress: { type: 'string' }, - senderId: { - type: 'string' - }, recipientId: { type: 'string' }, @@ -53,6 +41,18 @@ module.exports = { type: 'integer', minimum: 0, maximum: constants.fixedPoint + }, + orderBy: { + type: 'string' + }, + limit: { + type: 'integer', + minimum: 1, + maximum: 100 + }, + offset: { + type: 'integer', + minimum: 0 } } }, From 52ffa30130c6da3c03d2349a70bc5190603a6c94 Mon Sep 17 00:00:00 2001 From: Oliver Beddows Date: Sun, 16 Oct 2016 23:51:51 +0200 Subject: [PATCH 049/272] Fixing condition on filter.state. --- modules/peers.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/modules/peers.js b/modules/peers.js index 02a9e5920da..3518077c213 100644 --- a/modules/peers.js +++ b/modules/peers.js @@ -144,7 +144,7 @@ __private.getByFilter = function (filter, cb) { params.port = filter.port; } - if (filter.state) { + if (filter.state >= 0) { where.push('"state" = ${state}'); params.state = filter.state; } From 2b5aa09a7a52ae61d0748d4f5582e9fc0a30ad18 Mon Sep 17 00:00:00 2001 From: Oliver Beddows Date: Mon, 17 Oct 2016 00:01:38 +0200 Subject: [PATCH 050/272] Expanding test coverage #297. Describing GET /api/peers. --- test/api/peers.js | 270 +++++++++++++++++++++++++++++++++++++++++++--- 1 file changed, 258 insertions(+), 12 deletions(-) diff --git a/test/api/peers.js b/test/api/peers.js index 2b91ff40601..a0a6198f308 100644 --- a/test/api/peers.js +++ b/test/api/peers.js @@ -16,7 +16,76 @@ describe('GET /api/peers/version', function () { describe('GET /api/peers', function () { - it('using state should be ok', function (done) { + it('using invalid ip should fail', function (done) { + var ip = 'invalid'; + var params = 'ip=' + ip; + + node.get('/api/peers?' + params, function (err, res) { + node.expect(res.body).to.have.property('success').to.be.not.ok; + node.expect(res.body).to.have.property('error').to.equal('Object didn\'t pass validation for format ip: invalid'); + done(); + }); + }); + + it('using valid ip should be ok', function (done) { + var ip = '0.0.0.0'; + var params = 'ip=' + ip; + + node.get('/api/peers?' + params, function (err, res) { + node.expect(res.body).to.have.property('success').to.be.ok; + done(); + }); + }); + + it('using port < 1 should fail', function (done) { + var port = 0; + var params = 'port=' + port; + + node.get('/api/peers?' + params, function (err, res) { + node.expect(res.body).to.have.property('success').to.be.not.ok; + node.expect(res.body).to.have.property('error').to.equal('Value 0 is less than minimum 1'); + done(); + }); + }); + + it('using port == 65535 be ok', function (done) { + var port = 65535; + var params = 'port=' + port; + + node.get('/api/peers?' + params, function (err, res) { + node.expect(res.body).to.have.property('success').to.be.ok; + done(); + }); + }); + + it('using port > 65535 should fail', function (done) { + var port = 65536; + var params = 'port=' + port; + + node.get('/api/peers?' + params, function (err, res) { + node.expect(res.body).to.have.property('success').to.be.not.ok; + node.expect(res.body).to.have.property('error').to.equal('Value 65536 is greater than maximum 65535'); + done(); + }); + }); + + it('using state == 0 should be ok', function (done) { + var state = 0; + var params = 'state=' + state; + + node.get('/api/peers?' + params, function (err, res) { + node.expect(res.body).to.have.property('success').to.be.ok; + node.expect(res.body).to.have.property('peers').that.is.an('array'); + if (res.body.peers.length > 0) { + for (var i = 0; i < res.body.peers.length; i++) { + node.expect(res.body.peers[i].state).to.equal(parseInt(state)); + } + } + done(); + }); + }); + + it('using state == 1 should be ok', function (done) { var state = 1; var params = 'state=' + state; @@ -32,30 +101,107 @@ describe('GET /api/peers', function () { }); }); - it('using limit should be ok', function (done) { - var limit = 3; - var params = 'limit=' + limit; + it('using state == 2 should be ok', function (done) { + var state = 2; + var params = 'state=' + state; node.get('/api/peers?' + params, function (err, res) { node.expect(res.body).to.have.property('success').to.be.ok; node.expect(res.body).to.have.property('peers').that.is.an('array'); - node.expect(res.body.peers.length).to.be.at.most(limit); + if (res.body.peers.length > 0) { + for (var i = 0; i < res.body.peers.length; i++) { + node.expect(res.body.peers[i].state).to.equal(parseInt(state)); + } + } done(); }); }); - it('using limit > 100 should fail', function (done) { - var limit = 101; - var params = 'limit=' + limit; + it('using state > 2 should fail', function (done) { + var state = 3; + var params = 'state=' + state; + + node.get('/api/peers?' + params, function (err, res) { + node.expect(res.body).to.have.property('success').to.be.not.ok; + node.expect(res.body).to.have.property('error').to.equal('Value 3 is greater than maximum 2'); + done(); + }); + }); + + it('using os with length == 1 should be ok', function (done) { + var os = 'b'; + var params = 'os=' + os; + + node.get('/api/peers?' + params, function (err, res) { + node.expect(res.body).to.have.property('success').to.be.ok; + done(); + }); + }); + + it('using os with length == 64 should be ok', function (done) { + var os = 'battle-claw-lunch-confirm-correct-limb-siege-erode-child-libert'; + var params = 'os=' + os; + + node.get('/api/peers?' + params, function (err, res) { + node.expect(res.body).to.have.property('success').to.be.ok; + done(); + }); + }); + + it('using os with length > 64 should be ok', function (done) { + var os = 'battle-claw-lunch-confirm-correct-limb-siege-erode-child-liberty-'; + var params = 'os=' + os; node.get('/api/peers?' + params, function (err, res) { node.expect(res.body).to.have.property('success').to.be.not.ok; - node.expect(res.body).to.have.property('error'); + node.expect(res.body).to.have.property('error').to.equal('String is too long (65 chars), maximum 64'); done(); }); }); - it('using orderBy should be ok', function (done) { + it('using version with length == 11 characters should be ok', function (done) { + var version = '999.999.999'; + var params = 'version=' + version; + + node.get('/api/peers?' + params, function (err, res) { + node.expect(res.body).to.have.property('success').to.be.ok; + done(); + }); + }); + + it('using version with length > 11 characters should fail', function (done) { + var version = '9999.999.999'; + var params = 'version=' + version; + + node.get('/api/peers?' + params, function (err, res) { + node.expect(res.body).to.have.property('success').to.be.not.ok; + node.expect(res.body).to.have.property('error').to.equal('String is too long (12 chars), maximum 11'); + done(); + }); + }); + + it('using invalid broadhash should fail', function (done) { + var broadhash = 'invalid'; + var params = 'broadhash=' + broadhash; + + node.get('/api/peers?' + params, function (err, res) { + node.expect(res.body).to.have.property('success').to.be.not.ok; + node.expect(res.body).to.have.property('error').to.equal('Object didn\'t pass validation for format hex: invalid'); + done(); + }); + }); + + it('using valid broadhash should be ok', function (done) { + var broadhash = node.config.nethash; + var params = 'broadhash=' + broadhash; + + node.get('/api/peers?' + params, function (err, res) { + node.expect(res.body).to.have.property('success').to.be.ok; + done(); + }); + }); + + it('using orderBy == "state:desc" should be ok', function (done) { var orderBy = 'state:desc'; var params = 'orderBy=' + orderBy; @@ -65,8 +211,8 @@ describe('GET /api/peers', function () { if (res.body.peers.length > 0) { for (var i = 0; i < res.body.peers.length; i++) { - if (res.body.peers[i+1] != null) { - node.expect(res.body.peers[i+1].state).to.at.most(res.body.peers[i].state); + if (res.body.peers[i + 1] != null) { + node.expect(res.body.peers[i + 1].state).to.be.at.most(res.body.peers[i].state); } } } @@ -74,6 +220,106 @@ describe('GET /api/peers', function () { done(); }); }); + + it('using string limit should fail', function (done) { + var limit = 'one'; + var params = 'limit=' + limit; + + node.get('/api/peers?' + params, function (err, res) { + node.expect(res.body).to.have.property('success').to.be.not.ok; + node.expect(res.body).to.have.property('error').to.equal('Expected type integer but found type string'); + done(); + }); + }); + + it('using limit == -1 should fail', function (done) { + var limit = -1; + var params = 'limit=' + limit; + + node.get('/api/peers?' + params, function (err, res) { + node.expect(res.body).to.have.property('success').to.be.not.ok; + node.expect(res.body).to.have.property('error').to.equal('Value -1 is less than minimum 1'); + done(); + }); + }); + + it('using limit == 0 should fail', function (done) { + var limit = 0; + var params = 'limit=' + limit; + + node.get('/api/peers?' + params, function (err, res) { + node.expect(res.body).to.have.property('success').to.be.not.ok; + node.expect(res.body).to.have.property('error').to.equal('Value 0 is less than minimum 1'); + done(); + }); + }); + + it('using limit == 1 should be ok', function (done) { + var limit = 1; + var params = 'limit=' + limit; + + node.get('/api/peers?' + params, function (err, res) { + node.expect(res.body).to.have.property('success').to.be.ok; + node.expect(res.body).to.have.property('peers').that.is.an('array'); + node.expect(res.body.peers.length).to.be.at.most(limit); + done(); + }); + }); + + it('using limit == 100 should be ok', function (done) { + var limit = 100; + var params = 'limit=' + limit; + + node.get('/api/peers?' + params, function (err, res) { + node.expect(res.body).to.have.property('success').to.be.ok; + node.expect(res.body).to.have.property('peers').that.is.an('array'); + node.expect(res.body.peers.length).to.be.at.most(limit); + done(); + }); + }); + + it('using limit > 100 should fail', function (done) { + var limit = 101; + var params = 'limit=' + limit; + + node.get('/api/peers?' + params, function (err, res) { + node.expect(res.body).to.have.property('success').to.be.not.ok; + node.expect(res.body).to.have.property('error').to.equal('Value 101 is greater than maximum 100'); + done(); + }); + }); + + it('using string offset should fail', function (done) { + var offset = 'one'; + var params = 'offset=' + offset; + + node.get('/api/peers?' + params, function (err, res) { + node.expect(res.body).to.have.property('success').to.be.not.ok; + node.expect(res.body).to.have.property('error').to.equal('Expected type integer but found type string'); + done(); + }); + }); + + it('using offset == -1 should fail', function (done) { + var offset = -1; + var params = 'offset=' + offset; + + node.get('/api/peers?' + params, function (err, res) { + node.expect(res.body).to.have.property('success').to.be.not.ok; + node.expect(res.body).to.have.property('error').to.equal('Value -1 is less than minimum 0'); + done(); + }); + }); + + it('using offset == 1 should be ok', function (done) { + var offset = 1; + var params = 'offset=' + offset; + + node.get('/api/peers?' + params, function (err, res) { + node.expect(res.body).to.have.property('success').to.be.ok; + done(); + }); + }); }); describe('GET /api/peers/get', function () { From 1696737d09307277ce96f2e1d3f0485514f6b0d0 Mon Sep 17 00:00:00 2001 From: Oliver Beddows Date: Mon, 17 Oct 2016 01:15:28 +0200 Subject: [PATCH 051/272] Serialising Peers.prototype.onPeersReady timers #297. --- modules/peers.js | 33 +++++++++++++++++++-------------- 1 file changed, 19 insertions(+), 14 deletions(-) diff --git a/modules/peers.js b/modules/peers.js index 3518077c213..877bea22c45 100644 --- a/modules/peers.js +++ b/modules/peers.js @@ -379,21 +379,26 @@ Peers.prototype.onBlockchainReady = function () { }; Peers.prototype.onPeersReady = function () { - setImmediate(function nextUpdatePeersList () { - __private.updatePeersList(function (err) { - if (err) { - library.logger.error('Peers timer:', err); - } - setTimeout(nextUpdatePeersList, 60 * 1000); - }); - }); - - setImmediate(function nextBanManager () { - __private.banManager(function (err) { - if (err) { - library.logger.error('Ban manager timer:', err); + setImmediate(function nextSeries () { + async.series({ + updatePeersList: function (seriesCb) { + __private.updatePeersList(function (err) { + if (err) { + library.logger.error('Peers timer:', err); + } + return setImmediate(seriesCb); + }); + }, + nextBanManager: function (seriesCb) { + __private.banManager(function (err) { + if (err) { + library.logger.error('Ban manager timer:', err); + } + return setImmediate(seriesCb); + }); } - setTimeout(nextBanManager, 65 * 1000); + }, function (err) { + return setTimeout(nextSeries, 65000); }); }); }; From df733b774f9d086e78efb004be10f3b4c988ed02 Mon Sep 17 00:00:00 2001 From: Oliver Beddows Date: Mon, 17 Oct 2016 01:20:43 +0200 Subject: [PATCH 052/272] Standardising code. --- modules/peers.js | 1 - 1 file changed, 1 deletion(-) diff --git a/modules/peers.js b/modules/peers.js index 877bea22c45..26d8eaa85df 100644 --- a/modules/peers.js +++ b/modules/peers.js @@ -404,7 +404,6 @@ Peers.prototype.onPeersReady = function () { }; // Shared - shared.getPeers = function (req, cb) { library.schema.validate(req.body, schema.getPeers, function (err) { if (err) { From 9b657af3e777393da7ea1139e6563a9831befab3 Mon Sep 17 00:00:00 2001 From: Oliver Beddows Date: Mon, 17 Oct 2016 14:27:55 +0200 Subject: [PATCH 053/272] Changing log. --- modules/delegates.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/modules/delegates.js b/modules/delegates.js index 35b76ca5597..0c85e9779e7 100644 --- a/modules/delegates.js +++ b/modules/delegates.js @@ -246,7 +246,7 @@ __private.forge = function (cb) { var lastBlock = modules.blocks.getLastBlock(); if (currentSlot === slots.getSlotNumber(lastBlock.timestamp)) { - library.logger.debug('Last block within same delegate slot'); + library.logger.debug('Waiting for next delegate slot'); return setImmediate(cb); } From e70ba8a2f14a36e11ebd98354edd235e1841ca89 Mon Sep 17 00:00:00 2001 From: Oliver Beddows Date: Mon, 17 Oct 2016 14:33:11 +0200 Subject: [PATCH 054/272] Removing multiplication. --- modules/blocks.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/modules/blocks.js b/modules/blocks.js index 64cbe7ac9dd..e2783a55aa7 100644 --- a/modules/blocks.js +++ b/modules/blocks.js @@ -1237,7 +1237,7 @@ Blocks.prototype.cleanup = function (cb) { setImmediate(function nextWatch () { if (__private.isActive) { library.logger.info('Waiting for block processing to finish...'); - setTimeout(nextWatch, 1 * 1000); + setTimeout(nextWatch, 10000); } else { return setImmediate(cb); } From 690b10a53ee971f2455b05e0e44528baffeccfe0 Mon Sep 17 00:00:00 2001 From: Oliver Beddows Date: Mon, 17 Oct 2016 16:14:14 +0200 Subject: [PATCH 055/272] Adding GET /api/blocks/getBroadhash endpoint #297. Including broadhash in GET /api/blocks/getStatus. --- modules/blocks.js | 10 ++++++++++ test/api/blocks.js | 11 +++++++++++ 2 files changed, 21 insertions(+) diff --git a/modules/blocks.js b/modules/blocks.js index e2783a55aa7..f56e0434166 100644 --- a/modules/blocks.js +++ b/modules/blocks.js @@ -99,6 +99,7 @@ __private.attachApi = function () { router.map(shared, { 'get /get': 'getBlock', 'get /': 'getBlocks', + 'get /getBroadhash': 'getBroadhash', 'get /getEpoch': 'getEpoch', 'get /getHeight': 'getHeight', 'get /getNethash': 'getNethash', @@ -1288,6 +1289,14 @@ shared.getBlocks = function (req, cb) { }); }; +shared.getBroadhash = function (req, cb) { + if (!__private.loaded) { + return setImmediate(cb, 'Blockchain is loading'); + } + + return setImmediate(cb, null, {broadhash: modules.system.getBroadhash()}); +}; + shared.getEpoch = function (req, cb) { if (!__private.loaded) { return setImmediate(cb, 'Blockchain is loading'); @@ -1358,6 +1367,7 @@ shared.getStatus = function (req, cb) { } return setImmediate(cb, null, { + broadhash: modules.system.getBroadhash(), epoch: constants.epochTime, height: __private.lastBlock.height, fee: library.logic.block.calculateFee(), diff --git a/test/api/blocks.js b/test/api/blocks.js index eaba0a8f2b8..881c47b9f26 100644 --- a/test/api/blocks.js +++ b/test/api/blocks.js @@ -12,6 +12,16 @@ var block = { var testBlocksUnder101 = false; +describe('GET /api/blocks/getBroadhash', function () { + + it('should be ok', function (done) { + node.get('/api/blocks/getBroadhash', function (err, res) { + node.expect(res.body).to.have.property('broadhash').to.be.a('string'); + done(); + }); + }); +}); + describe('GET /api/blocks/getEpoch', function () { it('should be ok', function (done) { @@ -116,6 +126,7 @@ describe('GET /api/blocks/getStatus', function () { it('should be ok', function (done) { node.get('/api/blocks/getStatus', function (err, res) { node.expect(res.body).to.have.property('success').to.be.ok; + node.expect(res.body).to.have.property('broadhash').to.be.a('string'); node.expect(res.body).to.have.property('epoch').to.be.a('string'); node.expect(res.body).to.have.property('height').to.be.a('number'); node.expect(res.body).to.have.property('fee').to.be.a('number'); From 402a782574a78aa5ae8d9ebbc1a0bf873e86c8bd Mon Sep 17 00:00:00 2001 From: Oliver Beddows Date: Mon, 17 Oct 2016 16:23:19 +0200 Subject: [PATCH 056/272] Renaming callbacks. --- modules/loader.js | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/modules/loader.js b/modules/loader.js index 7219f3e8da0..197b04ec7d8 100644 --- a/modules/loader.js +++ b/modules/loader.js @@ -450,19 +450,19 @@ __private.sync = function (cb) { __private.syncTrigger(true); async.series({ - undoUnconfirmedList: function (cb) { + undoUnconfirmedList: function (seriesCb) { library.logger.debug('Undoing unconfirmed transactions before sync'); - return modules.transactions.undoUnconfirmedList(cb); + return modules.transactions.undoUnconfirmedList(seriesCb); }, - loadBlocksFromNetwork: function (cb) { - return __private.loadBlocksFromNetwork(cb); + loadBlocksFromNetwork: function (seriesCb) { + return __private.loadBlocksFromNetwork(seriesCb); }, - updateSystem: function (cb) { - return modules.system.update(cb); + updateSystem: function (seriesCb) { + return modules.system.update(seriesCb); }, - receiveTransactions: function (cb) { + receiveTransactions: function (seriesCb) { library.logger.debug('Receiving unconfirmed transactions after sync'); - return modules.transactions.receiveTransactions(transactions, cb); + return modules.transactions.receiveTransactions(transactions, seriesCb); } }, function (err) { __private.isActive = false; From b9ddea078d3da5db3a51f5ba34e7d6d9f1486a36 Mon Sep 17 00:00:00 2001 From: Oliver Beddows Date: Mon, 17 Oct 2016 16:35:14 +0200 Subject: [PATCH 057/272] Changing log level from debug to info. --- modules/loader.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/modules/loader.js b/modules/loader.js index 197b04ec7d8..254a6c078ef 100644 --- a/modules/loader.js +++ b/modules/loader.js @@ -444,7 +444,7 @@ __private.loadBlocksFromNetwork = function (cb) { __private.sync = function (cb) { var transactions = modules.transactions.getUnconfirmedTransactionList(true); - library.logger.debug('Starting sync'); + library.logger.info('Starting sync'); __private.isActive = true; __private.syncTrigger(true); @@ -469,7 +469,7 @@ __private.sync = function (cb) { __private.syncTrigger(false); __private.blocksToSync = 0; - library.logger.debug('Finished sync'); + library.logger.info('Finished sync'); return setImmediate(cb, err); }); }; From 253059b06aed8ac9a777f83651fac509111c055e Mon Sep 17 00:00:00 2001 From: Oliver Beddows Date: Mon, 17 Oct 2016 19:07:53 +0200 Subject: [PATCH 058/272] =?UTF-8?q?Adding=20properties=20to=20incoming=20p?= =?UTF-8?q?eers=20from=20headers=C2=A0#297.?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- modules/transport.js | 2 ++ 1 file changed, 2 insertions(+) diff --git a/modules/transport.js b/modules/transport.js index 02b45831270..45a9ab79c71 100644 --- a/modules/transport.js +++ b/modules/transport.js @@ -70,6 +70,8 @@ __private.attachApi = function () { req.peer.state = 2; req.peer.os = headers.os; req.peer.version = headers.version; + req.peer.broadhash = headers.broadhash; + req.peer.height = headers.height; if (req.body && req.body.dappid) { req.peer.dappid = req.body.dappid; From 6b425751cba612053f9d463008f677f264a8eb16 Mon Sep 17 00:00:00 2001 From: Oliver Beddows Date: Mon, 17 Oct 2016 19:08:21 +0200 Subject: [PATCH 059/272] Handling NaN port. --- modules/peers.js | 1 + 1 file changed, 1 insertion(+) diff --git a/modules/peers.js b/modules/peers.js index 26d8eaa85df..2f3ca18e67d 100644 --- a/modules/peers.js +++ b/modules/peers.js @@ -216,6 +216,7 @@ Peers.prototype.inspect = function (peer) { } peer.port = parseInt(peer.port); + peer.port = isNaN(peer.port) ? 0 : peer.port; if (peer.ip) { peer.string = (peer.ip + ':' + peer.port || 'unknown'); From bad3dd1b1ef984fba8b2b2b202af8980fad1d547 Mon Sep 17 00:00:00 2001 From: Oliver Beddows Date: Mon, 17 Oct 2016 19:12:18 +0200 Subject: [PATCH 060/272] Setting headers once in first middleware. --- modules/transport.js | 21 ++------------------- 1 file changed, 2 insertions(+), 19 deletions(-) diff --git a/modules/transport.js b/modules/transport.js index 45a9ab79c71..484db756822 100644 --- a/modules/transport.js +++ b/modules/transport.js @@ -35,6 +35,8 @@ __private.attachApi = function () { var router = new Router(); router.use(function (req, res, next) { + res.set(__private.headers); + if (modules && __private.loaded) { return next(); } res.status(500).send({success: false, error: 'Blockchain is loading'}); }); @@ -91,15 +93,12 @@ __private.attachApi = function () { }); router.get('/list', function (req, res) { - res.set(__private.headers); modules.peers.list({limit: 100}, function (err, peers) { return res.status(200).json({peers: !err ? peers : []}); }); }); router.get('/blocks/common', function (req, res, next) { - res.set(__private.headers); - req.sanitize(req.query, schema.commonBlock, function (err, report, query) { if (err) { return next(err); } if (!report.isValid) { return res.json({success: false, error: report.issues}); } @@ -130,8 +129,6 @@ __private.attachApi = function () { }); router.get('/blocks', function (req, res, next) { - res.set(__private.headers); - req.sanitize(req.query, schema.blocks, function (err, report, query) { if (err) { return next(err); } if (!report.isValid) { return res.json({success: false, error: report.issues}); } @@ -153,8 +150,6 @@ __private.attachApi = function () { }); router.post('/blocks', function (req, res) { - res.set(__private.headers); - var block = req.body.block; var id = (block ? block.id : 'null'); @@ -178,8 +173,6 @@ __private.attachApi = function () { }); router.post('/signatures', function (req, res) { - res.set(__private.headers); - library.schema.validate(req.body, schema.signatures, function (err) { if (err) { return res.status(200).json({success: false, error: 'Signature validation failed'}); @@ -196,8 +189,6 @@ __private.attachApi = function () { }); router.get('/signatures', function (req, res) { - res.set(__private.headers); - var unconfirmedList = modules.transactions.getUnconfirmedTransactionList(); var signatures = []; @@ -216,13 +207,10 @@ __private.attachApi = function () { }); router.get('/transactions', function (req, res) { - res.set(__private.headers); res.status(200).json({success: true, transactions: modules.transactions.getUnconfirmedTransactionList()}); }); router.post('/transactions', function (req, res) { - res.set(__private.headers); - var transaction = req.body.transaction; var id = (transaction? transaction.id : 'null'); @@ -256,7 +244,6 @@ __private.attachApi = function () { }); router.get('/height', function (req, res) { - res.set(__private.headers); res.status(200).json({ success: true, height: modules.blocks.getLastBlock().height @@ -264,8 +251,6 @@ __private.attachApi = function () { }); router.post('/dapp/message', function (req, res) { - res.set(__private.headers); - try { if (!req.body.dappid) { return res.status(200).json({success: false, message: 'Missing dappid'}); @@ -306,8 +291,6 @@ __private.attachApi = function () { }); router.post('/dapp/request', function (req, res) { - res.set(__private.headers); - try { if (!req.body.dappid) { return res.status(200).json({success: false, message: 'Missing dappid'}); From 28d518ef6e113310248b0d471b30c65991990b8e Mon Sep 17 00:00:00 2001 From: Oliver Beddows Date: Mon, 17 Oct 2016 21:33:47 +0200 Subject: [PATCH 061/272] Adding broadhash and height properties #297. --- test/node.js | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/test/node.js b/test/node.js index 6fd058e0dac..4339919d1b7 100644 --- a/test/node.js +++ b/test/node.js @@ -195,7 +195,9 @@ node.addPeers = function (numOfPeers, cb) { version: version, port: port, nethash: node.config.nethash, - os: os + os: os, + broadhash: node.config.nethash, + height: 1 } }); From 4a9aee203fba8555d7822752f26fcbc20a25fdfb Mon Sep 17 00:00:00 2001 From: Oliver Beddows Date: Mon, 17 Oct 2016 23:22:44 +0200 Subject: [PATCH 062/272] Adding broadhash and height properties #297. --- modules/transport.js | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/modules/transport.js b/modules/transport.js index 484db756822..cc73d00b52d 100644 --- a/modules/transport.js +++ b/modules/transport.js @@ -466,7 +466,9 @@ Transport.prototype.getFromPeer = function (peer, options, cb) { port: headers.port, state: 2, os: headers.os, - version: headers.version + version: headers.version, + broadhash: headers.broadhash, + height: headers.height }); } From f6539a2fce9a35de80470723f85701d2c9cecc58 Mon Sep 17 00:00:00 2001 From: Oliver Beddows Date: Tue, 18 Oct 2016 08:17:50 +0200 Subject: [PATCH 063/272] Using extend. --- logic/transaction.js | 3 ++- modules/transport.js | 2 +- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/logic/transaction.js b/logic/transaction.js index e18cac596e7..f741509ebb5 100644 --- a/logic/transaction.js +++ b/logic/transaction.js @@ -6,6 +6,7 @@ var ByteBuffer = require('bytebuffer'); var constants = require('../helpers/constants.js'); var crypto = require('crypto'); var exceptions = require('../helpers/exceptions.js'); +var extend = require('extend'); var slots = require('../helpers/slots.js'); var sql = require('../sql/transactions.js'); @@ -820,7 +821,7 @@ Transaction.prototype.dbRead = function (raw) { var asset = __private.types[tx.type].dbRead.call(this, raw); if (asset) { - tx.asset = _.extend(tx.asset, asset); + tx.asset = extend(tx.asset, asset); } return tx; diff --git a/modules/transport.js b/modules/transport.js index cc73d00b52d..f2c55d48395 100644 --- a/modules/transport.js +++ b/modules/transport.js @@ -422,7 +422,7 @@ Transport.prototype.getFromPeer = function (peer, options, cb) { var req = { url: 'http://' + peer.ip + ':' + peer.port + url, method: options.method, - headers: _.extend({}, __private.headers, options.headers), + headers: extend({}, __private.headers, options.headers), timeout: library.config.peers.options.timeout }; From 3d7baec3e2172583257b6a92636832fd8ea0eac4 Mon Sep 17 00:00:00 2001 From: Oliver Beddows Date: Sun, 16 Oct 2016 18:12:01 +0200 Subject: [PATCH 064/272] Implementing new Peer constructor #297. Handling acceptance, header parsing and object extension. --- helpers/peerSweeper.js | 3 -- logic/peer.js | 115 +++++++++++++++++++++++++++++++++++++++++ modules/blocks.js | 2 +- modules/loader.js | 2 +- modules/peers.js | 44 ++++------------ modules/transport.js | 42 +++++---------- 6 files changed, 142 insertions(+), 66 deletions(-) create mode 100644 logic/peer.js diff --git a/helpers/peerSweeper.js b/helpers/peerSweeper.js index 8bc7b2890bd..4c9980a867f 100644 --- a/helpers/peerSweeper.js +++ b/helpers/peerSweeper.js @@ -17,9 +17,6 @@ function PeerSweeper (scope) { // Public methods PeerSweeper.prototype.push = function (action, peer) { - if (peer.state === undefined) { - peer.state = 1; - } if (action) { peer.action = action; } else { diff --git a/logic/peer.js b/logic/peer.js new file mode 100644 index 00000000000..c9edb78e47e --- /dev/null +++ b/logic/peer.js @@ -0,0 +1,115 @@ +'use strict'; + +var extend = require('extend'); +var ip = require('ip'); + +// Constructor +function Peer (peer) { + return this.accept(peer || {}); +} + +// Public properties +Peer.prototype.properties = [ + 'ip', + 'port', + 'state', + 'os', + 'version', + 'dappid', + 'broadhash', + 'height' +]; + +Peer.prototype.nullable = [ + 'os', + 'version', + 'dappid', + 'broadhash', + 'height' +]; + +// Public methods +Peer.prototype.accept = function (peer) { + if (/^[0-9]+$/.test(peer.ip)) { + this.ip = ip.fromLong(peer.ip); + } else { + this.ip = peer.ip; + } + + this.port = this.parseInt(peer.port, 0); + + if (this.ip) { + this.string = (this.ip + ':' + this.port || 'unknown'); + } else { + this.string = 'unknown'; + } + + if (peer.state != null) { + this.state = peer.state; + } else { + this.state = 1; + } + + if (peer.dappid != null) { + this.dappid = peer.dappid; + } + + this.headers(peer); + return this; +}; + +Peer.prototype.parseInt = function (integer, fallback) { + integer = parseInt(integer); + integer = isNaN(integer) ? fallback : integer; + + return integer; +}; + +Peer.prototype.headers = function (headers) { + headers = headers || {}; + + headers.os = headers.os || 'unknown'; + headers.version = headers.version || '0.0.0'; + headers.port = this.parseInt(headers.port, 0); + + if (headers.height != null) { + headers.height = this.parseInt(headers.height, 1); + } + + this.nullable.forEach(function (property) { + if (headers[property] != null) { + this[property] = headers[property]; + } else { + delete headers[property]; + } + }.bind(this)); + + return headers; +}; + +Peer.prototype.extend = function (object) { + return this.headers(extend({}, this.object(), object)); +}; + +Peer.prototype.object = function () { + var object = {}; + + this.properties.forEach(function (property) { + object[property] = this[property]; + }.bind(this)); + + if (object.broadhash != null) { + object.broadhash = new Buffer(object.broadhash, 'hex'); + } + + this.nullable.forEach(function (property) { + if (object[property] == null) { + object[property] = null; + } + }); + + return object; +}; + +// Export +module.exports = Peer; diff --git a/modules/blocks.js b/modules/blocks.js index f56e0434166..3584c464d72 100644 --- a/modules/blocks.js +++ b/modules/blocks.js @@ -1068,7 +1068,7 @@ Blocks.prototype.simpleDeleteAfterBlock = function (blockId, cb) { Blocks.prototype.loadBlocksFromPeer = function (peer, cb) { var lastValidBlock = __private.lastBlock; - peer = modules.peers.inspect(peer); + peer = modules.peers.accept(peer); library.logger.info('Loading blocks from: ' + peer.string); modules.transport.getFromPeer(peer, { diff --git a/modules/loader.js b/modules/loader.js index 254a6c078ef..367b0d8f054 100644 --- a/modules/loader.js +++ b/modules/loader.js @@ -531,7 +531,7 @@ __private.findGoodPeers = function (heights) { __private.getPeer = function (peer, cb) { async.series({ validatePeer: function (seriesCb) { - peer = modules.peers.inspect(peer); + peer = modules.peers.accept(peer); library.schema.validate(peer, schema.getNetwork.peer, function (err) { if (err) { diff --git a/modules/peers.js b/modules/peers.js index 2f3ca18e67d..98eeef29bcd 100644 --- a/modules/peers.js +++ b/modules/peers.js @@ -4,9 +4,9 @@ var _ = require('lodash'); var async = require('async'); var extend = require('extend'); var fs = require('fs'); -var ip = require('ip'); var OrderBy = require('../helpers/orderBy.js'); var path = require('path'); +var Peer = require('../logic/peer.js'); var PeerSweeper = require('../helpers/peerSweeper.js'); var Router = require('../helpers/router.js'); var sandboxHelper = require('../helpers/sandbox.js'); @@ -92,8 +92,8 @@ __private.updatePeersList = function (cb) { library.logger.debug(['Picked', peers.length, 'of', res.body.peers.length, 'peers'].join(' ')); - async.eachLimit(peers, 2, function (peer, cb) { - peer = self.inspect(peer); + async.eachLimit(res.body.peers, 2, function (peer, cb) { + peer = self.accept(peer); library.schema.validate(peer, schema.updatePeersList.peer, function (err) { if (err) { @@ -208,28 +208,8 @@ __private.getByFilter = function (filter, cb) { }; // Public methods -Peers.prototype.inspect = function (peer) { - peer = peer || {}; - - if (/^[0-9]+$/.test(peer.ip)) { - peer.ip = ip.fromLong(peer.ip); - } - - peer.port = parseInt(peer.port); - peer.port = isNaN(peer.port) ? 0 : peer.port; - - if (peer.ip) { - peer.string = (peer.ip + ':' + peer.port || 'unknown'); - } else { - peer.string = 'unknown'; - } - - peer.os = peer.os || 'unknown'; - peer.version = peer.version || '0.0.0'; - peer.broadhash = peer.broadhash || ''; - peer.height = peer.height || ''; - - return peer; +Peers.prototype.accept = function (peer) { + return new Peer(peer); }; Peers.prototype.list = function (options, cb) { @@ -331,7 +311,7 @@ Peers.prototype.remove = function (pip, port) { }; Peers.prototype.update = function (peer) { - return __private.sweeper.push('upsert', self.inspect(peer)); + return __private.sweeper.push('upsert', self.accept(peer).object()); }; Peers.prototype.sandboxApi = function (call, args, cb) { @@ -345,13 +325,11 @@ Peers.prototype.onBind = function (scope) { Peers.prototype.onBlockchainReady = function () { async.eachSeries(library.config.peers.list, function (peer, cb) { - var params = { - ip: peer.ip, - port: peer.port, - state: 2 - }; - library.db.query(sql.insertSeed, params).then(function (res) { - library.logger.debug('Inserted seed peer', params); + peer = self.accept(peer); + peer.state = 2; + + library.db.query(sql.insertSeed, peer).then(function (res) { + library.logger.debug('Inserted seed peer', peer); return setImmediate(cb, null, res); }).catch(function (err) { library.logger.error(err.stack); diff --git a/modules/transport.js b/modules/transport.js index f2c55d48395..bc31a202c4e 100644 --- a/modules/transport.js +++ b/modules/transport.js @@ -42,16 +42,15 @@ __private.attachApi = function () { }); router.use(function (req, res, next) { - req.peer = modules.peers.inspect( + req.peer = modules.peers.accept( { ip: req.headers['x-forwarded-for'] || req.connection.remoteAddress, - port: req.headers.port + port: req.headers.port, + state: 2 } ); - var headers = req.headers; - headers.ip = req.peer.ip; - headers.port = req.peer.port; + var headers = req.peer.extend(req.headers); req.sanitize(headers, schema.headers, function (err, report) { if (err) { return next(err); } @@ -69,16 +68,6 @@ __private.attachApi = function () { return res.status(200).send({success: false, message: 'Request is made on the wrong network', expected: library.config.nethash, received: headers.nethash}); } - req.peer.state = 2; - req.peer.os = headers.os; - req.peer.version = headers.version; - req.peer.broadhash = headers.broadhash; - req.peer.height = headers.height; - - if (req.body && req.body.dappid) { - req.peer.dappid = req.body.dappid; - } - if (req.peer.version === library.config.version) { if (!modules.blocks.lastReceipt()) { modules.delegates.enableForging(); @@ -87,6 +76,12 @@ __private.attachApi = function () { modules.peers.update(req.peer); } + if (req.body && req.body.dappid) { + req.peer.dappid = req.body.dappid; + } + + modules.peers.update(req.peer); + return next(); }); @@ -417,7 +412,8 @@ Transport.prototype.getFromPeer = function (peer, options, cb) { url = options.url; } - peer = modules.peers.inspect(peer); + peer = modules.peers.accept(peer); + peer.state = 2; var req = { url: 'http://' + peer.ip + ':' + peer.port + url, @@ -441,9 +437,7 @@ Transport.prototype.getFromPeer = function (peer, options, cb) { return setImmediate(cb, ['Received bad response code', res.status, req.method, req.url].join(' ')); } else { - var headers = res.headers; - headers.ip = peer.ip; - headers.port = peer.port; + var headers = peer.extend(res.headers); var report = library.schema.validate(headers, schema.headers); if (!report) { @@ -461,15 +455,7 @@ Transport.prototype.getFromPeer = function (peer, options, cb) { } if (headers.version === library.config.version) { - modules.peers.update({ - ip: peer.ip, - port: headers.port, - state: 2, - os: headers.os, - version: headers.version, - broadhash: headers.broadhash, - height: headers.height - }); + modules.peers.update(peer); } return setImmediate(cb, null, {body: res.body, peer: peer}); From 9d3dbf4e5da557017396b04c3091e5fc1f4c438e Mon Sep 17 00:00:00 2001 From: Oliver Beddows Date: Tue, 18 Oct 2016 17:32:22 +0200 Subject: [PATCH 065/272] Moving PeerSweeper from helpers to logic. --- {helpers => logic}/peerSweeper.js | 0 modules/peers.js | 2 +- 2 files changed, 1 insertion(+), 1 deletion(-) rename {helpers => logic}/peerSweeper.js (100%) diff --git a/helpers/peerSweeper.js b/logic/peerSweeper.js similarity index 100% rename from helpers/peerSweeper.js rename to logic/peerSweeper.js diff --git a/modules/peers.js b/modules/peers.js index 98eeef29bcd..8473ab1c2d1 100644 --- a/modules/peers.js +++ b/modules/peers.js @@ -7,7 +7,7 @@ var fs = require('fs'); var OrderBy = require('../helpers/orderBy.js'); var path = require('path'); var Peer = require('../logic/peer.js'); -var PeerSweeper = require('../helpers/peerSweeper.js'); +var PeerSweeper = require('../logic/peerSweeper.js'); var Router = require('../helpers/router.js'); var sandboxHelper = require('../helpers/sandbox.js'); var schema = require('../schema/peers.js'); From 02cee65d6b514686641a2414f4d34adf59973136 Mon Sep 17 00:00:00 2001 From: Oliver Beddows Date: Tue, 18 Oct 2016 17:33:53 +0200 Subject: [PATCH 066/272] Adding comment. --- logic/peerSweeper.js | 1 + 1 file changed, 1 insertion(+) diff --git a/logic/peerSweeper.js b/logic/peerSweeper.js index 4c9980a867f..ff611460f85 100644 --- a/logic/peerSweeper.js +++ b/logic/peerSweeper.js @@ -87,4 +87,5 @@ PeerSweeper.prototype.addDapps = function (peers) { }); }; +// Export module.exports = PeerSweeper; From b5a9f81485772d258faa43592ba52ed3fb982973 Mon Sep 17 00:00:00 2001 From: Oliver Beddows Date: Tue, 18 Oct 2016 21:43:29 +0200 Subject: [PATCH 067/272] Removing checks on peer version. --- modules/transport.js | 12 +++--------- 1 file changed, 3 insertions(+), 9 deletions(-) diff --git a/modules/transport.js b/modules/transport.js index bc31a202c4e..3324a2bb5c4 100644 --- a/modules/transport.js +++ b/modules/transport.js @@ -68,12 +68,8 @@ __private.attachApi = function () { return res.status(200).send({success: false, message: 'Request is made on the wrong network', expected: library.config.nethash, received: headers.nethash}); } - if (req.peer.version === library.config.version) { - if (!modules.blocks.lastReceipt()) { - modules.delegates.enableForging(); - } - - modules.peers.update(req.peer); + if (!modules.blocks.lastReceipt()) { + modules.delegates.enableForging(); } if (req.body && req.body.dappid) { @@ -454,9 +450,7 @@ Transport.prototype.getFromPeer = function (peer, options, cb) { return setImmediate(cb, ['Peer is not on the same network', headers.nethash, req.method, req.url].join(' ')); } - if (headers.version === library.config.version) { - modules.peers.update(peer); - } + modules.peers.update(peer); return setImmediate(cb, null, {body: res.body, peer: peer}); } From b4589e2201e0db2fb5bc61878f36c0ed269ceb50 Mon Sep 17 00:00:00 2001 From: Oliver Beddows Date: Tue, 18 Oct 2016 22:56:49 +0200 Subject: [PATCH 068/272] Normalising height as integer across schema. --- schema/blocks.js | 3 ++- schema/loader.js | 5 +++-- schema/peers.js | 6 ++++-- schema/transport.js | 3 ++- 4 files changed, 11 insertions(+), 6 deletions(-) diff --git a/schema/blocks.js b/schema/blocks.js index b20bd49ed7b..5aa8c1bb263 100644 --- a/schema/blocks.js +++ b/schema/blocks.js @@ -56,7 +56,8 @@ module.exports = { type: 'string' }, height: { - type: 'integer' + type: 'integer', + minimum: 1 } } } diff --git a/schema/loader.js b/schema/loader.js index 38ba5f3dfcd..7655631fec9 100644 --- a/schema/loader.js +++ b/schema/loader.js @@ -64,7 +64,8 @@ module.exports = { format: 'hex' }, height: { - type: 'string' + type: 'integer', + minimum: 1 } }, required: ['ip', 'port', 'state'] @@ -75,7 +76,7 @@ module.exports = { properties: { height: { type: 'integer', - minimum: 0 + minimum: 1 } }, required: ['height'] diff --git a/schema/peers.js b/schema/peers.js index a12c405339d..410766cc05b 100644 --- a/schema/peers.js +++ b/schema/peers.js @@ -45,7 +45,8 @@ module.exports = { format: 'hex' }, height: { - type: 'string' + type: 'integer', + minimum: 1 } }, required: ['ip', 'port', 'state'] @@ -82,7 +83,8 @@ module.exports = { format: 'hex' }, height: { - type: 'integer' + type: 'integer', + minimum: 1 }, orderBy: { type: 'string' diff --git a/schema/transport.js b/schema/transport.js index 4c84c343bad..2ff9ae3bcbd 100644 --- a/schema/transport.js +++ b/schema/transport.js @@ -31,7 +31,8 @@ module.exports = { format: 'hex' }, height: { - type: 'string' + type: 'integer', + minimum: 1 } }, required: ['ip', 'port', 'version', 'nethash'] From 5e5de8def8122bb221de88918bdb849810cec430 Mon Sep 17 00:00:00 2001 From: Oliver Beddows Date: Tue, 18 Oct 2016 23:37:53 +0200 Subject: [PATCH 069/272] Recording height on Transport.prototype.getFromPeer #297. --- modules/transport.js | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/modules/transport.js b/modules/transport.js index 3324a2bb5c4..52ec2f618b0 100644 --- a/modules/transport.js +++ b/modules/transport.js @@ -450,6 +450,10 @@ Transport.prototype.getFromPeer = function (peer, options, cb) { return setImmediate(cb, ['Peer is not on the same network', headers.nethash, req.method, req.url].join(' ')); } + if (res.body.height) { + peer.height = res.body.height; + } + modules.peers.update(peer); return setImmediate(cb, null, {body: res.body, peer: peer}); From 02737fc2d3807516f9d27779c5f6b9b03b96b5fa Mon Sep 17 00:00:00 2001 From: Oliver Beddows Date: Tue, 18 Oct 2016 23:48:09 +0200 Subject: [PATCH 070/272] Revising __private.getPeer #297. Do not get peer height when >= last block height. --- modules/loader.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/modules/loader.js b/modules/loader.js index 367b0d8f054..0af8c7635d7 100644 --- a/modules/loader.js +++ b/modules/loader.js @@ -542,7 +542,7 @@ __private.getPeer = function (peer, cb) { }); }, getHeight: function (seriesCb) { - if (peer.height) { + if (peer.height >= modules.blocks.getLastBlock().height) { return setImmediate(seriesCb); } else { modules.transport.getFromPeer(peer, { From c5153659c122e2651428f41097bb494aa5abfc00 Mon Sep 17 00:00:00 2001 From: Oliver Beddows Date: Wed, 19 Oct 2016 12:08:24 +0200 Subject: [PATCH 071/272] Using modules.system.getNethash. --- modules/blocks.js | 4 ++-- modules/transport.js | 6 +++--- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/modules/blocks.js b/modules/blocks.js index 3584c464d72..18799eabddc 100644 --- a/modules/blocks.js +++ b/modules/blocks.js @@ -1334,7 +1334,7 @@ shared.getNethash = function (req, cb) { return setImmediate(cb, 'Blockchain is loading'); } - return setImmediate(cb, null, {nethash: library.config.nethash}); + return setImmediate(cb, null, {nethash: modules.system.getNethash()}); }; shared.getMilestone = function (req, cb) { @@ -1372,7 +1372,7 @@ shared.getStatus = function (req, cb) { height: __private.lastBlock.height, fee: library.logic.block.calculateFee(), milestone: __private.blockReward.calcMilestone(__private.lastBlock.height), - nethash: library.config.nethash, + nethash: modules.system.getNethash(), reward: __private.blockReward.calcReward(__private.lastBlock.height), supply: __private.blockReward.calcSupply(__private.lastBlock.height) }); diff --git a/modules/transport.js b/modules/transport.js index 52ec2f618b0..e1f26a844a1 100644 --- a/modules/transport.js +++ b/modules/transport.js @@ -61,11 +61,11 @@ __private.attachApi = function () { return res.status(500).send({status: false, error: report.issues}); } - if (headers.nethash !== library.config.nethash) { + if (headers.nethash !== modules.system.getNethash()) { // Remove peer __private.removePeer({peer: req.peer, code: 'ENETHASH', req: req}); - return res.status(200).send({success: false, message: 'Request is made on the wrong network', expected: library.config.nethash, received: headers.nethash}); + return res.status(200).send({success: false, message: 'Request is made on the wrong network', expected: modules.system.getNethash(), received: headers.nethash}); } if (!modules.blocks.lastReceipt()) { @@ -443,7 +443,7 @@ Transport.prototype.getFromPeer = function (peer, options, cb) { return setImmediate(cb, ['Invalid response headers', JSON.stringify(headers), req.method, req.url].join(' ')); } - if (headers.nethash !== library.config.nethash) { + if (headers.nethash !== modules.system.getNethash()) { // Remove peer __private.removePeer({peer: peer, code: 'ENETHASH', req: req}); From c7d7a8fbe8f5eaa15311aca2dd75fddf67a4a3d6 Mon Sep 17 00:00:00 2001 From: Oliver Beddows Date: Wed, 19 Oct 2016 19:43:59 +0200 Subject: [PATCH 072/272] Removing maxUpdatePeers property. --- test/config.json | 1 - 1 file changed, 1 deletion(-) diff --git a/test/config.json b/test/config.json index 594b1c0e8bc..017ac309f75 100644 --- a/test/config.json +++ b/test/config.json @@ -42,7 +42,6 @@ "delayAfter": 0, "windowMs": 60000 }, - "maxUpdatePeers": 20, "timeout": 5000 } }, From 8a968b7ccb02dc77698286a0208c8a400962fa5e Mon Sep 17 00:00:00 2001 From: Oliver Beddows Date: Wed, 19 Oct 2016 19:46:30 +0200 Subject: [PATCH 073/272] Standardising code. --- test/config.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/config.json b/test/config.json index 017ac309f75..27903e71ef5 100644 --- a/test/config.json +++ b/test/config.json @@ -174,5 +174,5 @@ "masterpassword": "", "autoexec": [] }, - "nethash":"198f2b61a8eb95fbeed58b8216780b68f697f26b849acf00c8c93bb9b24f783d" + "nethash": "198f2b61a8eb95fbeed58b8216780b68f697f26b849acf00c8c93bb9b24f783d" } From 8bde7a08e894e1d4cc6d6829a83d128f47ea81fa Mon Sep 17 00:00:00 2001 From: Oliver Beddows Date: Wed, 19 Oct 2016 20:27:31 +0200 Subject: [PATCH 074/272] Inserting seed peers with broadhash and height #297. - Using peers sweeper to upsert peers. - Using async series to control flow. --- modules/peers.js | 32 ++++++++++++++++++-------------- sql/peers.js | 4 +--- 2 files changed, 19 insertions(+), 17 deletions(-) diff --git a/modules/peers.js b/modules/peers.js index 8473ab1c2d1..910cef62e48 100644 --- a/modules/peers.js +++ b/modules/peers.js @@ -324,22 +324,26 @@ Peers.prototype.onBind = function (scope) { }; Peers.prototype.onBlockchainReady = function () { - async.eachSeries(library.config.peers.list, function (peer, cb) { - peer = self.accept(peer); - peer.state = 2; + async.series({ + insertSeeds: function (seriesCb) { + async.eachSeries(library.config.peers.list, function (peer, eachCb) { + self.update({ + ip: peer.ip, + port: peer.port, + state: 2, + broadhash: modules.system.getBroadhash(), + height: 1 + }); - library.db.query(sql.insertSeed, peer).then(function (res) { - library.logger.debug('Inserted seed peer', peer); - return setImmediate(cb, null, res); - }).catch(function (err) { - library.logger.error(err.stack); - return setImmediate(cb, 'Peers#onBlockchainReady error'); - }); - }, function (err) { - if (err) { - library.logger.error(err); + return setImmediate(eachCb); + }, function (err) { + return setImmediate(seriesCb, err); + }); + }, + waitForSweep: function (seriesCb) { + return setTimeout(seriesCb, 1000); } - + }, function (err) { __private.count(function (err, count) { if (count) { __private.updatePeersList(function (err) { diff --git a/sql/peers.js b/sql/peers.js index 56dced25bce..bfd0a5546fe 100644 --- a/sql/peers.js +++ b/sql/peers.js @@ -35,9 +35,7 @@ var PeersSql = { addDapp: 'INSERT INTO peers_dapp ("peerId", "dappid") VALUES (${peerId}, ${dappId}) ON CONFLICT DO NOTHING', - upsert: 'INSERT INTO peers AS p ("ip", "port", "state", "os", "version", "broadhash", "height") VALUES (${ip}, ${port}, ${state}, ${os}, ${version}, ${broadhash}, ${height}) ON CONFLICT ("ip", "port") DO UPDATE SET ("ip", "port", "state", "os", "version", "broadhash", "height") = (${ip}, ${port}, (CASE WHEN p."state" = 0 THEN p."state" ELSE ${state} END), ${os}, ${version}, (CASE WHEN ${broadhash} IS NULL THEN p."broadhash" ELSE ${broadhash} END), (CASE WHEN ${height} IS NULL THEN p."height" ELSE ${height} END))', - - insertSeed: 'INSERT INTO peers("ip", "port", "state") VALUES(${ip}, ${port}, ${state}) ON CONFLICT DO NOTHING', + upsert: 'INSERT INTO peers AS p ("ip", "port", "state", "os", "version", "broadhash", "height") VALUES (${ip}, ${port}, ${state}, ${os}, ${version}, ${broadhash}, ${height}) ON CONFLICT ("ip", "port") DO UPDATE SET ("ip", "port", "state", "os", "version", "broadhash", "height") = (${ip}, ${port}, (CASE WHEN p."state" = 0 THEN p."state" ELSE ${state} END), ${os}, ${version}, (CASE WHEN ${broadhash} IS NULL THEN p."broadhash" ELSE ${broadhash} END), (CASE WHEN ${height} IS NULL THEN p."height" ELSE ${height} END))' }; module.exports = PeersSql; From d7c415ba001d564533856f6658f62108fae92e93 Mon Sep 17 00:00:00 2001 From: Oliver Beddows Date: Wed, 19 Oct 2016 23:44:37 +0200 Subject: [PATCH 075/272] Revising Transport.prototype.getFromRandomPeer #297. Removing async.retry. --- modules/transport.js | 16 ++++++---------- 1 file changed, 6 insertions(+), 10 deletions(-) diff --git a/modules/transport.js b/modules/transport.js index e1f26a844a1..e86e3f5eb3e 100644 --- a/modules/transport.js +++ b/modules/transport.js @@ -386,16 +386,12 @@ Transport.prototype.getFromRandomPeer = function (config, options, cb) { config = {}; } config.limit = 1; - async.retry(20, function (cb) { - modules.peers.list(config, function (err, peers) { - if (!err && peers.length) { - return self.getFromPeer(peers[0], options, cb); - } else { - return setImmediate(cb, err || 'No reachable peers in db'); - } - }); - }, function (err, results) { - return setImmediate(cb, err, results); + modules.peers.list(config, function (err, peers) { + if (!err && peers.length) { + return self.getFromPeer(peers[0], options, cb); + } else { + return setImmediate(cb, err || 'No reachable peers in db'); + } }); }; From 1ba3bdb07c04fd5da311dcf6e1e6ce8760cf0f6f Mon Sep 17 00:00:00 2001 From: Oliver Beddows Date: Thu, 20 Oct 2016 02:29:19 +0200 Subject: [PATCH 076/272] Standardising code. --- modules/loader.js | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/modules/loader.js b/modules/loader.js index 0af8c7635d7..9c8721c5f18 100644 --- a/modules/loader.js +++ b/modules/loader.js @@ -479,16 +479,16 @@ __private.findGoodPeers = function (heights) { var lastBlockHeight = modules.blocks.getLastBlock().height; heights = heights.filter(function (item) { - // Removing unreachable peers. + // Removing unreachable peers return item != null; }).filter(function (item) { - // Remove heights below last block height. + // Remove heights below last block height return item.height >= lastBlockHeight; }); // Assuming that the node reached at least 10% of the network if (heights.length < 10) { - return { height: 0, peers: [] }; + return {height: 0, peers: []}; } else { // Ordering the peers with descending height heights = heights.sort(function (a,b) { From d1995b6be4ddec59977cd216060aef1f52e8dbda Mon Sep 17 00:00:00 2001 From: Oliver Beddows Date: Thu, 20 Oct 2016 03:14:40 +0200 Subject: [PATCH 077/272] Revising __private.findGoodPeers #297. Accepting heights < 10 unless === 0. --- modules/loader.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/modules/loader.js b/modules/loader.js index 9c8721c5f18..49196ce935a 100644 --- a/modules/loader.js +++ b/modules/loader.js @@ -486,8 +486,8 @@ __private.findGoodPeers = function (heights) { return item.height >= lastBlockHeight; }); - // Assuming that the node reached at least 10% of the network - if (heights.length < 10) { + // No peers found + if (heights.length === 0) { return {height: 0, peers: []}; } else { // Ordering the peers with descending height From a6493da21650c26dfce973c89c20ed733f51a65c Mon Sep 17 00:00:00 2001 From: Oliver Beddows Date: Thu, 20 Oct 2016 14:59:52 +0200 Subject: [PATCH 078/272] Adjusting Peers.prototype.onPeersReady timer. --- modules/peers.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/modules/peers.js b/modules/peers.js index 910cef62e48..d7dbae0b2f1 100644 --- a/modules/peers.js +++ b/modules/peers.js @@ -381,7 +381,7 @@ Peers.prototype.onPeersReady = function () { }); } }, function (err) { - return setTimeout(nextSeries, 65000); + return setTimeout(nextSeries, 60000); }); }); }; From 5eb2198d57c2d558b7303ba349cd09b07e3378ef Mon Sep 17 00:00:00 2001 From: Oliver Beddows Date: Thu, 20 Oct 2016 15:56:09 +0200 Subject: [PATCH 079/272] Changing log. --- modules/delegates.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/modules/delegates.js b/modules/delegates.js index 0c85e9779e7..faf0a3d455a 100644 --- a/modules/delegates.js +++ b/modules/delegates.js @@ -231,7 +231,7 @@ __private.forge = function (cb) { } if (!__private.forging) { - library.logger.debug('Forging disabled due to timeout'); + library.logger.debug('Forging disabled'); return setImmediate(cb); } From 0f00175620d7bfce686db8a72da6a3458fa67de3 Mon Sep 17 00:00:00 2001 From: Oliver Beddows Date: Thu, 20 Oct 2016 20:59:33 +0200 Subject: [PATCH 080/272] Changing log. --- modules/transport.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/modules/transport.js b/modules/transport.js index e86e3f5eb3e..669f0501dc0 100644 --- a/modules/transport.js +++ b/modules/transport.js @@ -390,7 +390,7 @@ Transport.prototype.getFromRandomPeer = function (config, options, cb) { if (!err && peers.length) { return self.getFromPeer(peers[0], options, cb); } else { - return setImmediate(cb, err || 'No reachable peers in db'); + return setImmediate(cb, err || 'No acceptable peers found'); } }); }; From ba17e0c3e49e865ea410fdee3a06cdfbcfd2316a Mon Sep 17 00:00:00 2001 From: Oliver Beddows Date: Fri, 21 Oct 2016 21:05:59 +0200 Subject: [PATCH 081/272] Closes #297. Improving peers selection reliability. - Decreasing blockReceiptTimeOut to 120 seconds. - Setting TTL to 1 block for Loader.prototype.getNetwork. - Protecting "removed" nodes from overflow. - Increasing number of parallel broadcasts. - Logging beginning / end of broadcast. - Updating peer in __private.getPeer. --- helpers/constants.js | 2 +- modules/loader.js | 3 ++- modules/peers.js | 5 +++++ modules/transport.js | 5 +++-- 4 files changed, 11 insertions(+), 4 deletions(-) diff --git a/helpers/constants.js b/helpers/constants.js index 1c66aa7f319..9c09b93dcf4 100644 --- a/helpers/constants.js +++ b/helpers/constants.js @@ -4,7 +4,7 @@ module.exports = { activeDelegates: 101, addressLength: 208, blockHeaderLength: 248, - blockReceiptTimeOut: 500, // 50 blocks + blockReceiptTimeOut: 120, // 12 blocks confirmationLength: 77, epochTime: new Date(Date.UTC(2016, 4, 24, 17, 0, 0, 0)), fees:{ diff --git a/modules/loader.js b/modules/loader.js index 49196ce935a..36f1601db6a 100644 --- a/modules/loader.js +++ b/modules/loader.js @@ -553,6 +553,7 @@ __private.getPeer = function (peer, cb) { return setImmediate(seriesCb, 'Failed to get height from peer: ' + peer.string); } else { peer.height = res.body.height; + modules.peers.update(peer); return setImmediate(seriesCb); } }); @@ -584,7 +585,7 @@ __private.getPeer = function (peer, cb) { // - Then for each of them we grab the height of their blockchain. // - With this list we try to get a peer with sensibly good blockchain height (see __private.findGoodPeers for actual strategy). Loader.prototype.getNetwork = function (cb) { - if (__private.network.height > 0 && Math.abs(__private.network.height - modules.blocks.getLastBlock().height) < 101) { + if (__private.network.height > 0 && Math.abs(__private.network.height - modules.blocks.getLastBlock().height) === 1) { return setImmediate(cb, null, __private.network); } async.waterfall([ diff --git a/modules/peers.js b/modules/peers.js index d7dbae0b2f1..953bf94f494 100644 --- a/modules/peers.js +++ b/modules/peers.js @@ -73,6 +73,11 @@ __private.updatePeersList = function (cb) { return setImmediate(cb); } + // Protect removed nodes from overflow + if (removed.length > 100) { + removed = []; + } + // Removing nodes not behaving well library.logger.debug('Removed peers: ' + removed.length); var peers = res.body.peers.filter(function (peer) { diff --git a/modules/transport.js b/modules/transport.js index 669f0501dc0..1f16063b3d6 100644 --- a/modules/transport.js +++ b/modules/transport.js @@ -358,7 +358,7 @@ Transport.prototype.headers = function (headers) { }; Transport.prototype.broadcast = function (config, options, cb) { - library.logger.debug('Broadcast', options); + library.logger.debug('Begin broadcast', options); config.limit = config.limit || 1; config.broadhash = config.broadhash || null; @@ -366,9 +366,10 @@ Transport.prototype.broadcast = function (config, options, cb) { modules.peers.list(config, function (err, peers) { if (!err) { - async.eachLimit(peers, 3, function (peer, cb) { + async.eachLimit(peers, 20, function (peer, cb) { return self.getFromPeer(peer, options, cb); }, function (err) { + library.logger.debug('End broadcast'); if (cb) { return setImmediate(cb, null, {body: null, peer: peers}); } From c6cfe8d1eeaffcc493e8631d6df43f121fbcf0db Mon Sep 17 00:00:00 2001 From: Oliver Beddows Date: Fri, 21 Oct 2016 22:15:51 +0200 Subject: [PATCH 082/272] Fixing arguments; Iterating over filtered peers. --- modules/peers.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/modules/peers.js b/modules/peers.js index 953bf94f494..8588d57defd 100644 --- a/modules/peers.js +++ b/modules/peers.js @@ -97,7 +97,7 @@ __private.updatePeersList = function (cb) { library.logger.debug(['Picked', peers.length, 'of', res.body.peers.length, 'peers'].join(' ')); - async.eachLimit(res.body.peers, 2, function (peer, cb) { + async.eachLimit(peers, 2, function (peer, cb) { peer = self.accept(peer); library.schema.validate(peer, schema.updatePeersList.peer, function (err) { From f2ece65b81bb751c86f5c88d302d37924c0e61ed Mon Sep 17 00:00:00 2001 From: Oliver Beddows Date: Fri, 21 Oct 2016 22:17:13 +0200 Subject: [PATCH 083/272] Moving logs to to log level. --- modules/loader.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/modules/loader.js b/modules/loader.js index 36f1601db6a..d9530da648e 100644 --- a/modules/loader.js +++ b/modules/loader.js @@ -563,7 +563,7 @@ __private.getPeer = function (peer, cb) { var heightIsValid = library.schema.validate(peer, schema.getNetwork.height); if (heightIsValid) { - library.logger.info(['Received height:', peer.height, 'from peer:', peer.string].join(' ')); + library.logger.log(['Received height:', peer.height, 'from peer:', peer.string].join(' ')); return setImmediate(seriesCb); } else { return setImmediate(seriesCb, 'Received invalid height from peer: ' + peer.string); @@ -608,7 +608,7 @@ Loader.prototype.getNetwork = function (cb) { if (err) { return setImmediate(waterCb, err); } else { - library.logger.debug(['Received', peers.length, 'peers from'].join(' '), res.peer.string); + library.logger.log(['Received', peers.length, 'peers from'].join(' '), res.peer.string); return setImmediate(waterCb, null, peers); } }); From ca38806b5155cac56dd6a59aedd023bf93759a52 Mon Sep 17 00:00:00 2001 From: Oliver Beddows Date: Fri, 21 Oct 2016 21:13:25 +0000 Subject: [PATCH 084/272] Moving logs to to log level. --- modules/loader.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/modules/loader.js b/modules/loader.js index d9530da648e..691a81134f3 100644 --- a/modules/loader.js +++ b/modules/loader.js @@ -90,7 +90,7 @@ __private.loadSignatures = function (cb) { }); }, function (peer, waterCb) { - library.logger.info('Loading signatures from: ' + peer.string); + library.logger.log('Loading signatures from: ' + peer.string); modules.transport.getFromPeer(peer, { api: '/signatures', @@ -137,7 +137,7 @@ __private.loadUnconfirmedTransactions = function (cb) { }); }, function (peer, waterCb) { - library.logger.info('Loading unconfirmed transactions from: ' + peer.string); + library.logger.log('Loading unconfirmed transactions from: ' + peer.string); modules.transport.getFromPeer(peer, { api: '/transactions', From ad2025868931056d95e5294503a4113af8dd4003 Mon Sep 17 00:00:00 2001 From: Isabella Dell Date: Fri, 28 Oct 2016 09:41:12 -0500 Subject: [PATCH 085/272] Address missing publickey bug for /api/accounts/open When an account is created in Lisk, a publickey is not assigned to a wallet ID until the first transmitting transaction. To resolve issues related to not having a publicKey, we must assign it based on the hash of the Secret Key if the value is null when pulled from the Database. This pull request resolves this issue for lisk-ui https://github.com/LiskHQ/lisk-ui/issues/75 --- modules/accounts.js | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/modules/accounts.js b/modules/accounts.js index cb024e6619c..39cbdbc4f76 100644 --- a/modules/accounts.js +++ b/modules/accounts.js @@ -96,7 +96,7 @@ __private.attachApi = function () { router.use(function (req, res, next) { res.status(500).send({success: false, error: 'API endpoint was not found'}); }); - + library.network.app.use('/api/accounts', router); library.network.app.use(function (err, req, res, next) { if (!err) { return next(); } @@ -116,6 +116,9 @@ __private.openAccount = function (secret, cb) { } if (account) { + if (account.publicKey == null) { + account.publicKey = publicKey; + } return setImmediate(cb, null, account); } else { return setImmediate(cb, null, { From fe6dda81f6d1a99398d334392e413d2301652f20 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?V=C3=ADt=20Stanislav?= Date: Sun, 30 Oct 2016 20:56:47 +0100 Subject: [PATCH 086/272] Fix --config param when ./config.json is not valid It wasn't possible to specify a different config file in that case --- app.js | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/app.js b/app.js index ff62721a283..42c0edfc0ce 100644 --- a/app.js +++ b/app.js @@ -1,6 +1,5 @@ 'use strict'; -var appConfig = require('./config.json'); var async = require('async'); var checkIpInList = require('./helpers/checkIpInList.js'); var extend = require('extend'); @@ -35,8 +34,11 @@ program .option('-s, --snapshot ', 'verify snapshot') .parse(process.argv); +var appConfig; if (program.config) { appConfig = require(path.resolve(process.cwd(), program.config)); +} else { + appConfig = require('./config.json'); } if (program.port) { From 6168bca75f23eca7f6d6fa90a475fe1d7ad6dfad Mon Sep 17 00:00:00 2001 From: Isabello Date: Mon, 31 Oct 2016 17:34:31 -0500 Subject: [PATCH 087/272] Configured topAccounts api endpoint via config.json https://github.com/LiskHQ/lisk/issues/285 --- app.js | 3 +++ config.json | 1 + 2 files changed, 4 insertions(+) diff --git a/app.js b/app.js index 42c0edfc0ce..6f2561a2fad 100644 --- a/app.js +++ b/app.js @@ -73,6 +73,9 @@ if (program.snapshot) { ); } +//Define top endpoint availability +process.env.TOP = appConfig.topAccounts; + var config = { db: appConfig.db, modules: { diff --git a/config.json b/config.json index 644615c59a6..5483a60767b 100644 --- a/config.json +++ b/config.json @@ -6,6 +6,7 @@ "logFileName": "logs/lisk.log", "consoleLogLevel": "info", "trustProxy": false, + "topAccounts": false, "db": { "host": "localhost", "port": 5432, From 4ff96b3fd57e2990e315967ef8039e028b14585c Mon Sep 17 00:00:00 2001 From: Oliver Beddows Date: Tue, 1 Nov 2016 07:08:04 +0100 Subject: [PATCH 088/272] Beautifying code. --- app.js | 2 +- modules/accounts.js | 8 ++++---- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/app.js b/app.js index 6f2561a2fad..a698768c21e 100644 --- a/app.js +++ b/app.js @@ -73,7 +73,7 @@ if (program.snapshot) { ); } -//Define top endpoint availability +// Define top endpoint availability process.env.TOP = appConfig.topAccounts; var config = { diff --git a/modules/accounts.js b/modules/accounts.js index 39cbdbc4f76..e24f99b9504 100644 --- a/modules/accounts.js +++ b/modules/accounts.js @@ -96,7 +96,7 @@ __private.attachApi = function () { router.use(function (req, res, next) { res.status(500).send({success: false, error: 'API endpoint was not found'}); }); - + library.network.app.use('/api/accounts', router); library.network.app.use(function (err, req, res, next) { if (!err) { return next(); } @@ -116,9 +116,9 @@ __private.openAccount = function (secret, cb) { } if (account) { - if (account.publicKey == null) { - account.publicKey = publicKey; - } + if (account.publicKey == null) { + account.publicKey = publicKey; + } return setImmediate(cb, null, account); } else { return setImmediate(cb, null, { From 3e3bc003a5be5381874fae36896bac9e63714f31 Mon Sep 17 00:00:00 2001 From: Isabella Dell Date: Wed, 2 Nov 2016 13:14:56 -0500 Subject: [PATCH 089/272] Handle password assignment after DB creation Resolves https://github.com/LiskHQ/lisk/issues/241 --- README.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 925f44f710f..d2a4a68422c 100644 --- a/README.md +++ b/README.md @@ -17,8 +17,9 @@ Install PostgreSQL (version 9.5.2): ``` curl -sL "https://downloads.lisk.io/scripts/setup_postgresql.Linux" | bash - -sudo -u postgres createuser --createdb --password $USER +sudo -u postgres createuser --createdb $USER createdb lisk_test +sudo -u postgres psql -d lisk_test -c "alter user "$USER" with password 'password';" ``` Install Node.js (version 0.12.x) + npm: From 6eba29f790832f6668545d2b69d98be3ff78a8d2 Mon Sep 17 00:00:00 2001 From: Oliver Beddows Date: Mon, 24 Oct 2016 13:10:50 +0200 Subject: [PATCH 090/272] Refactoring __private.updatePeersList. Using async.waterfall to control flow with named functions. --- modules/peers.js | 102 +++++++++++++++++++++++++++-------------------- 1 file changed, 58 insertions(+), 44 deletions(-) diff --git a/modules/peers.js b/modules/peers.js index 8588d57defd..31fef50ab4c 100644 --- a/modules/peers.js +++ b/modules/peers.js @@ -60,59 +60,73 @@ __private.attachApi = function () { }; __private.updatePeersList = function (cb) { - modules.transport.getFromRandomPeer({ - api: '/list', - method: 'GET' - }, function (err, res) { - if (err) { - return setImmediate(cb); - } + function getFromRandomPeer (waterCb) { + modules.transport.getFromRandomPeer({ + api: '/list', + method: 'GET' + }, function (err, res) { + return setImmediate(waterCb, err, res); + }); + } + function validatePeersList (res, waterCb) { library.schema.validate(res.body, schema.updatePeersList.peers, function (err) { - if (err) { - return setImmediate(cb); - } + return setImmediate(waterCb, err, res.body.peers); + }); + } - // Protect removed nodes from overflow - if (removed.length > 100) { - removed = []; - } + function pickPeers (peers, waterCb) { + // Protect removed nodes from overflow + if (removed.length > 100) { + removed = []; + } - // Removing nodes not behaving well - library.logger.debug('Removed peers: ' + removed.length); - var peers = res.body.peers.filter(function (peer) { - return removed.indexOf(peer.ip); - }); + // Removing nodes not behaving well + library.logger.debug('Removed peers: ' + removed.length); + var picked = peers.filter(function (peer) { + return removed.indexOf(peer.ip); + }); - // Drop one random peer from removed array to give them a chance. - // This mitigates the issue that a node could be removed forever if it was offline for long. - // This is not harmful for the node, but prevents network from shrinking, increasing noise. - // To fine tune: decreasing random value threshold -> reduce noise. - if (Math.random() < 0.5) { // Every 60/0.5 = 120s - // Remove the first element, - // i.e. the one that have been placed first. - removed.shift(); - removed.pop(); - } + // Drop one random peer from removed array to give them a chance. + // This mitigates the issue that a node could be removed forever if it was offline for long. + // This is not harmful for the node, but prevents network from shrinking, increasing noise. + // To fine tune: decreasing random value threshold -> reduce noise. + if (Math.random() < 0.5) { // Every 60/0.5 = 120s + // Remove the first element, + // i.e. the one that have been placed first. + removed.shift(); + removed.pop(); + } - library.logger.debug(['Picked', peers.length, 'of', res.body.peers.length, 'peers'].join(' ')); + library.logger.debug(['Picked', picked.length, 'of', peers.length, 'peers'].join(' ')); + return setImmediate(waterCb, null, picked); + } - async.eachLimit(peers, 2, function (peer, cb) { - peer = self.accept(peer); + function updatePeers (peers, waterCb) { + async.eachLimit(peers, 2, function (peer, eachCb) { + peer = self.accept(peer); - library.schema.validate(peer, schema.updatePeersList.peer, function (err) { - if (err) { - err.forEach(function (e) { - library.logger.error(['Rejecting invalid peer:', peer.ip, e.path, e.message].join(' ')); - }); - } else { - self.update(peer); - } + library.schema.validate(peer, schema.updatePeersList.peer, function (err) { + if (err) { + err.forEach(function (e) { + library.logger.error(['Rejecting invalid peer:', peer.ip, e.path, e.message].join(' ')); + }); + } else { + self.update(peer); + } - return setImmediate(cb); - }); - }, cb); - }); + return setImmediate(eachCb); + }); + }, waterCb); + } + + async.waterfall([ + getFromRandomPeer, + validatePeersList, + pickPeers, + updatePeers + ], function (err) { + return setImmediate(cb, err); }); }; From aa9268b9cd25cb78dbfcff549bd0005f1969c06d Mon Sep 17 00:00:00 2001 From: Oliver Beddows Date: Mon, 24 Oct 2016 14:53:01 +0200 Subject: [PATCH 091/272] Allowing non-unique items in peers.updatePeersList.peers #298. --- schema/peers.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/schema/peers.js b/schema/peers.js index 410766cc05b..f8f20e42cfb 100644 --- a/schema/peers.js +++ b/schema/peers.js @@ -8,7 +8,7 @@ module.exports = { properties: { peers: { type: 'array', - uniqueItems: true, + uniqueItems: false, maxItems: 100 } }, From adee0f840ed39bed9502cefb687be2ac954ec54a Mon Sep 17 00:00:00 2001 From: Oliver Beddows Date: Sun, 23 Oct 2016 20:04:57 +0200 Subject: [PATCH 092/272] Closes #298. Adding Peers.prototype.acceptable function. - Removing private ip addresses. - Removing non-unique peers. --- modules/peers.js | 23 +++++++++++++++++++---- 1 file changed, 19 insertions(+), 4 deletions(-) diff --git a/modules/peers.js b/modules/peers.js index 31fef50ab4c..f91f1ab708e 100644 --- a/modules/peers.js +++ b/modules/peers.js @@ -4,6 +4,7 @@ var _ = require('lodash'); var async = require('async'); var extend = require('extend'); var fs = require('fs'); +var ip = require('ip'); var OrderBy = require('../helpers/orderBy.js'); var path = require('path'); var Peer = require('../logic/peer.js'); @@ -81,10 +82,14 @@ __private.updatePeersList = function (cb) { removed = []; } - // Removing nodes not behaving well library.logger.debug('Removed peers: ' + removed.length); - var picked = peers.filter(function (peer) { - return removed.indexOf(peer.ip); + + // Pick peers + // + // * Removing unacceptable peers + // * Removing nodes not behaving well + var picked = self.acceptable(peers).filter(function (peer) { + return removed.indexOf(peer.ip); }); // Drop one random peer from removed array to give them a chance. @@ -231,6 +236,16 @@ Peers.prototype.accept = function (peer) { return new Peer(peer); }; +Peers.prototype.acceptable = function (peers) { + return _.chain(peers).filter(function (peer) { + // Removing peers with private ip address + return !ip.isPrivate(peer.ip); + }).uniqWith(function (a, b) { + // Removing non-unique peers + return (a.ip + a.port) === (b.ip + b.port); + }).value(); +}; + Peers.prototype.list = function (options, cb) { options.limit = options.limit || 100; options.broadhash = options.broadhash || modules.system.getBroadhash(); @@ -249,7 +264,7 @@ Peers.prototype.list = function (options, cb) { function randomList (options, peers, cb) { library.db.query(sql.randomList(options), options).then(function (rows) { library.logger.debug(['Listing', rows.length, options.attempts[options.attempt], 'peers'].join(' ')); - return setImmediate(cb, null, peers.concat(rows)); + return setImmediate(cb, null, self.acceptable(peers.concat(rows))); }).catch(function (err) { library.logger.error(err.stack); return setImmediate(cb, 'Peers#list error'); From 035a48e03ba58f1f493c25781a57a96ccae434e9 Mon Sep 17 00:00:00 2001 From: Oliver Beddows Date: Sun, 23 Oct 2016 23:07:58 +0200 Subject: [PATCH 093/272] Resetting __private.network on error #299. --- modules/loader.js | 16 +++++++++++----- 1 file changed, 11 insertions(+), 5 deletions(-) diff --git a/modules/loader.js b/modules/loader.js index 691a81134f3..c2abb85fefb 100644 --- a/modules/loader.js +++ b/modules/loader.js @@ -14,11 +14,6 @@ require('colors'); // Private fields var modules, library, self, __private = {}, shared = {}; -__private.network = { - height: 0, // Network height - peers: [], // "Good" peers and with height close to network height -}; - __private.loaded = false; __private.isActive = false; __private.lastBlock = null; @@ -32,6 +27,7 @@ function Loader (cb, scope) { library = scope; self = this; + __private.initalize(); __private.attachApi(); __private.genesisBlock = __private.lastBlock = library.genesisblock; @@ -39,6 +35,13 @@ function Loader (cb, scope) { } // Private methods +__private.initalize = function () { + __private.network = { + height: 0, // Network height + peers: [], // "Good" peers and with height close to network height + }; +}; + __private.attachApi = function () { var router = new Router(); @@ -689,6 +692,9 @@ Loader.prototype.onPeersReady = function () { } } }, function (err) { + if (err) { + __private.initalize(); + } return setTimeout(nextSeries, 10000); }); }); From c8e7b1fc119083011a745874c20e7d8c8acc25e8 Mon Sep 17 00:00:00 2001 From: Oliver Beddows Date: Mon, 24 Oct 2016 18:21:40 +0200 Subject: [PATCH 094/272] Closes #299. Retrying 5 times before raising error. --- modules/loader.js | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/modules/loader.js b/modules/loader.js index c2abb85fefb..a19d0a93163 100644 --- a/modules/loader.js +++ b/modules/loader.js @@ -646,6 +646,8 @@ Loader.prototype.sandboxApi = function (call, args, cb) { // Events Loader.prototype.onPeersReady = function () { + var retries = 5; + setImmediate(function nextSeries () { async.series({ sync: function (seriesCb) { @@ -653,7 +655,7 @@ Loader.prototype.onPeersReady = function () { if (__private.loaded && !self.syncing() && (!lastReceipt || lastReceipt.stale)) { library.sequence.add(function (cb) { - __private.sync(cb); + async.retry(retries, __private.sync, cb); }, function (err) { if (err) { library.logger.warn('Sync timer', err); @@ -667,7 +669,7 @@ Loader.prototype.onPeersReady = function () { }, loadUnconfirmedTransactions: function (seriesCb) { if (__private.loaded) { - __private.loadUnconfirmedTransactions(function (err) { + async.retry(retries, __private.loadUnconfirmedTransactions, function (err) { if (err) { library.logger.warn('Unconfirmed transactions timer', err); } @@ -680,7 +682,7 @@ Loader.prototype.onPeersReady = function () { }, loadSignatures: function (seriesCb) { if (__private.loaded) { - __private.loadSignatures(function (err) { + async.retry(retries, __private.loadSignatures, function (err) { if (err) { library.logger.warn('Signatures timer', err); } From cc3e563ee9d2bcd6d11dc245a92cce37a4e4d2c6 Mon Sep 17 00:00:00 2001 From: Oliver Beddows Date: Mon, 24 Oct 2016 18:20:04 +0200 Subject: [PATCH 095/272] Mitigating against fork cause 2 #259. Checking for already confirmed transaction at end of Transaction.prototype.verify, instead of Transaction.prototype.process. Mitigates against fork cause 2, where transaction enters unconfirmed transaction list from another peer, before block has been processed and applied. Affects Blocks.prototype.generateBlock where each transaction is verified before inclusion. - Undoing offending transaction on fork cause 2. - Removing transaction from unconfirmed transaction list. - Refactoring code into separate prototype functions. - Reusing said functions in Blocks.prototype.processBlock. --- logic/transaction.js | 45 ++++++++++++++++++++++++++++------------- modules/blocks.js | 27 ++++++++++++++++--------- modules/transactions.js | 8 ++++---- sql/blocks.js | 2 -- 4 files changed, 53 insertions(+), 29 deletions(-) diff --git a/logic/transaction.js b/logic/transaction.js index f741509ebb5..fd7f04e2ace 100644 --- a/logic/transaction.js +++ b/logic/transaction.js @@ -11,7 +11,7 @@ var slots = require('../helpers/slots.js'); var sql = require('../sql/transactions.js'); // Private fields -var __private = {}, genesisblock = null; +var self, __private = {}, genesisblock = null; __private.types = {}; @@ -19,6 +19,7 @@ __private.types = {}; function Transaction (scope, cb) { this.scope = scope; genesisblock = this.scope.genesisblock; + self = this; if (cb) { return setImmediate(cb, null, this); } @@ -190,6 +191,27 @@ Transaction.prototype.ready = function (trs, sender) { return __private.types[trs.type].ready.call(this, trs, sender); }; +Transaction.prototype.countById = function (trs, cb) { + this.scope.db.one(sql.countById, { id: trs.id }).then(function (row) { + return setImmediate(cb, null, row.count); + }).catch(function (err) { + this.scope.logger.error(err.stack); + return setImmediate(cb, 'Transaction#countById error'); + }); +}; + +Transaction.prototype.checkConfirmed = function (trs, cb) { + this.countById(trs, function (err, count) { + if (err) { + return setImmediate(cb, err); + } else if (count > 0) { + return setImmediate(cb, 'Transaction is already confirmed: ' + trs.id, true); + } else { + return setImmediate(cb); + } + }); +}; + Transaction.prototype.process = function (trs, sender, requester, cb) { if (typeof requester === 'function') { cb = requester; @@ -245,19 +267,9 @@ Transaction.prototype.process = function (trs, sender, requester, cb) { __private.types[trs.type].process.call(this, trs, sender, function (err, trs) { if (err) { return setImmediate(cb, err); - } - - // Check for already confirmed transaction - this.scope.db.one(sql.countById, { id: trs.id }).then(function (row) { - if (row.count > 0) { - return setImmediate(cb, 'Transaction is already confirmed: ' + trs.id, trs, true); - } - + } else { return setImmediate(cb, null, trs); - }).catch(function (err) { - this.scope.logger.error(err.stack); - return setImmediate(cb, 'Transaction#process error'); - }); + } }.bind(this)); }; @@ -406,7 +418,12 @@ Transaction.prototype.verify = function (trs, sender, requester, cb) { // Call verify on transaction type __private.types[trs.type].verify.call(this, trs, sender, function (err) { - return setImmediate(cb, err); + if (err) { + return setImmediate(cb, err); + } else { + // Check for already confirmed transaction + return self.checkConfirmed(trs, cb); + } }); }; diff --git a/modules/blocks.js b/modules/blocks.js index 18799eabddc..a0f791f2de7 100644 --- a/modules/blocks.js +++ b/modules/blocks.js @@ -1015,22 +1015,31 @@ Blocks.prototype.processBlock = function (block, broadcast, cb, saveBlock) { return setImmediate(cb, e.toString()); } transaction.blockId = block.id; + return setImmediate(cb); + }, + function (cb) { // Check if transaction is already in database, otherwise fork 2. // DATABASE: read only - library.db.query(sql.getTransactionId, { id: transaction.id }).then(function (rows) { - if (rows.length > 0) { + library.logic.transaction.checkConfirmed(transaction, function (err) { + if (err) { + // Fork: Transaction already confirmed. modules.delegates.fork(block, 2); - return setImmediate(cb, ['Transaction', transaction.id, 'already exists'].join(' ')); + // Undo the offending transaction. + // DATABASE: write + modules.transactions.undoUnconfirmed(transaction, function (err2) { + modules.transactions.removeUnconfirmedTransaction(transaction.id); + return setImmediate(cb, err2 || err); + }); } else { - // Get account from database if any (otherwise cold wallet). - // DATABASE: read only - modules.accounts.getAccount({publicKey: transaction.senderPublicKey}, cb); + return setImmediate(cb); } - }).catch(function (err) { - library.logger.error(err.stack); - return setImmediate(cb, 'Blocks#processBlock error'); }); }, + function (cb) { + // Get account from database if any (otherwise cold wallet). + // DATABASE: read only + modules.accounts.getAccount({publicKey: transaction.senderPublicKey}, cb); + }, function (sender, cb) { // Check if transaction id valid against database state (mem_* tables). // DATABASE: read only diff --git a/modules/transactions.js b/modules/transactions.js index 4bdc2767522..571c923b5f5 100644 --- a/modules/transactions.js +++ b/modules/transactions.js @@ -282,18 +282,18 @@ Transactions.prototype.processUnconfirmedTransaction = function (transaction, br return done('Requester not found'); } - library.logic.transaction.process(transaction, sender, requester, function (err, transaction, ignore) { + library.logic.transaction.process(transaction, sender, requester, function (err, transaction) { if (err) { - return done(err, ignore); + return done(err); } library.logic.transaction.verify(transaction, sender, done); }); }); } else { - library.logic.transaction.process(transaction, sender, function (err, transaction, ignore) { + library.logic.transaction.process(transaction, sender, function (err, transaction) { if (err) { - return done(err, ignore); + return done(err); } library.logic.transaction.verify(transaction, sender, done); diff --git a/sql/blocks.js b/sql/blocks.js index fca183c0a0c..eeff3e161df 100644 --- a/sql/blocks.js +++ b/sql/blocks.js @@ -73,8 +73,6 @@ var BlocksSql = { getBlockId: 'SELECT "id" FROM blocks WHERE "id" = ${id}', - getTransactionId: 'SELECT "id" FROM trs WHERE "id" = ${id}', - simpleDeleteAfterBlock: 'DELETE FROM blocks WHERE "height" >= (SELECT "height" FROM blocks WHERE "id" = ${id});' }; From 6c8127762fc425f5e29e01b19b6b72b85ac58469 Mon Sep 17 00:00:00 2001 From: Oliver Beddows Date: Tue, 25 Oct 2016 11:24:10 +0200 Subject: [PATCH 096/272] Changing logs. --- modules/peers.js | 4 ++-- modules/transactions.js | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/modules/peers.js b/modules/peers.js index f91f1ab708e..72aab648e68 100644 --- a/modules/peers.js +++ b/modules/peers.js @@ -401,7 +401,7 @@ Peers.prototype.onPeersReady = function () { updatePeersList: function (seriesCb) { __private.updatePeersList(function (err) { if (err) { - library.logger.error('Peers timer:', err); + library.logger.error('Peers timer', err); } return setImmediate(seriesCb); }); @@ -409,7 +409,7 @@ Peers.prototype.onPeersReady = function () { nextBanManager: function (seriesCb) { __private.banManager(function (err) { if (err) { - library.logger.error('Ban manager timer:', err); + library.logger.error('Ban manager timer', err); } return setImmediate(seriesCb); }); diff --git a/modules/transactions.js b/modules/transactions.js index 571c923b5f5..818dc2b127b 100644 --- a/modules/transactions.js +++ b/modules/transactions.js @@ -437,7 +437,7 @@ Transactions.prototype.onPeersReady = function () { setImmediate(function nextUnconfirmedExpiry () { self.expireUnconfirmedList(function (err, ids) { if (err) { - library.logger.error('Unconfirmed transactions timer:', err); + library.logger.error('Unconfirmed transactions timer', err); } setTimeout(nextUnconfirmedExpiry, 30000); From 4c4bcc2a838d5ac4e8905682b6a622ddff948153 Mon Sep 17 00:00:00 2001 From: Oliver Beddows Date: Tue, 25 Oct 2016 11:40:30 +0200 Subject: [PATCH 097/272] Refactoring Blocks.prototype.processBlock. - Using async.series to control flow. - Moving logic into __private.checkTransaction. --- modules/blocks.js | 182 +++++++++++++++++++++++++--------------------- 1 file changed, 101 insertions(+), 81 deletions(-) diff --git a/modules/blocks.js b/modules/blocks.js index a0f791f2de7..8db39314995 100644 --- a/modules/blocks.js +++ b/modules/blocks.js @@ -963,6 +963,50 @@ __private.applyGenesisBlock = function (block, cb) { }); }; +__private.checkTransaction = function (block, transaction, cb) { + async.waterfall([ + function (waterCb) { + try { + transaction.id = library.logic.transaction.getId(transaction); + } catch (e) { + return setImmediate(waterCb, e.toString()); + } + transaction.blockId = block.id; + return setImmediate(waterCb); + }, + function (waterCb) { + // Check if transaction is already in database, otherwise fork 2. + // DATABASE: read only + library.logic.transaction.checkConfirmed(transaction, function (err) { + if (err) { + // Fork: Transaction already confirmed. + modules.delegates.fork(block, 2); + // Undo the offending transaction. + // DATABASE: write + modules.transactions.undoUnconfirmed(transaction, function (err2) { + modules.transactions.removeUnconfirmedTransaction(transaction.id); + return setImmediate(waterCb, err2 || err); + }); + } else { + return setImmediate(waterCb); + } + }); + }, + function (waterCb) { + // Get account from database if any (otherwise cold wallet). + // DATABASE: read only + modules.accounts.getAccount({publicKey: transaction.senderPublicKey}, waterCb); + }, + function (sender, waterCb) { + // Check if transaction id valid against database state (mem_* tables). + // DATABASE: read only + library.logic.transaction.verify(transaction, sender, waterCb); + } + ], function (err) { + return setImmediate(cb, err); + }); +}; + // Main function to process a Block. // * Verify the block looks ok // * Verify the block is compatible with database state (DATABASE readonly) @@ -974,94 +1018,70 @@ Blocks.prototype.processBlock = function (block, broadcast, cb, saveBlock) { return setImmediate(cb, 'Blockchain is loading'); } - try { - block = library.logic.block.objectNormalize(block); - } catch (err) { - return setImmediate(cb, err); - } - - // Sanity check of the block, if values are coherent. - // No access to database - var check = self.verifyBlock(block); - - if (!check.verified) { - library.logger.error(['Block', block.id, 'verification failed'].join(' '), check.errors.join(', ')); - return setImmediate(cb, check.errors[0]); - } - - // Check if block id is already in the database (very low probability of hash collision). - // TODO: In case of hash-collision, to me it would be a special autofork... - // DATABASE: read only - library.db.query(sql.getBlockId, { id: block.id }).then(function (rows) { - if (rows.length > 0) { - return setImmediate(cb, ['Block', block.id, 'already exists'].join(' ')); - } + async.series({ + normalizeBlock: function (seriesCb) { + try { + block = library.logic.block.objectNormalize(block); + } catch (err) { + return setImmediate(seriesCb, err); + } - // Check if block was generated by the right active delagate. Otherwise, fork 3. - // DATABASE: Read only to mem_accounts to extract active delegate list - modules.delegates.validateBlockSlot(block, function (err) { - if (err) { - modules.delegates.fork(block, 3); - return setImmediate(cb, err); + return setImmediate(seriesCb); + }, + verifyBlock: function (seriesCb) { + // Sanity check of the block, if values are coherent. + // No access to database + var check = self.verifyBlock(block); + + if (!check.verified) { + library.logger.error(['Block', block.id, 'verification failed'].join(' '), check.errors.join(', ')); + return setImmediate(seriesCb, check.errors[0]); } - // Check against the mem_* tables that we can perform the transactions included in the block. - async.eachSeries(block.transactions, function (transaction, cb) { - async.waterfall([ - function (cb) { - try { - transaction.id = library.logic.transaction.getId(transaction); - } catch (e) { - return setImmediate(cb, e.toString()); - } - transaction.blockId = block.id; - return setImmediate(cb); - }, - function (cb) { - // Check if transaction is already in database, otherwise fork 2. - // DATABASE: read only - library.logic.transaction.checkConfirmed(transaction, function (err) { - if (err) { - // Fork: Transaction already confirmed. - modules.delegates.fork(block, 2); - // Undo the offending transaction. - // DATABASE: write - modules.transactions.undoUnconfirmed(transaction, function (err2) { - modules.transactions.removeUnconfirmedTransaction(transaction.id); - return setImmediate(cb, err2 || err); - }); - } else { - return setImmediate(cb); - } - }); - }, - function (cb) { - // Get account from database if any (otherwise cold wallet). - // DATABASE: read only - modules.accounts.getAccount({publicKey: transaction.senderPublicKey}, cb); - }, - function (sender, cb) { - // Check if transaction id valid against database state (mem_* tables). - // DATABASE: read only - library.logic.transaction.verify(transaction, sender, cb); - } - ], - function (err) { - return setImmediate(cb, err); - }); - }, - function (err) { + return setImmediate(seriesCb); + }, + checkExists: function (seriesCb) { + // Check if block id is already in the database (very low probability of hash collision). + // TODO: In case of hash-collision, to me it would be a special autofork... + // DATABASE: read only + library.db.query(sql.getBlockId, { id: block.id }).then(function (rows) { + if (rows.length > 0) { + return setImmediate(seriesCb, ['Block', block.id, 'already exists'].join(' ')); + } else { + return setImmediate(seriesCb); + } + }); + }, + validateBlockSlot: function (seriesCb) { + // Check if block was generated by the right active delagate. Otherwise, fork 3. + // DATABASE: Read only to mem_accounts to extract active delegate list + modules.delegates.validateBlockSlot(block, function (err) { if (err) { - return setImmediate(cb, err); + modules.delegates.fork(block, 3); + return setImmediate(seriesCb, err); } else { - // The block and the transactions are OK i.e: - // * Block and transactions have valid values (signatures, block slots, etc...) - // * The check against database state passed (for instance sender has enough LSK, votes are under 101, etc...) - // We thus update the database with the transactions values, save the block and tick it. - __private.applyBlock(block, broadcast, cb, saveBlock); + return setImmediate(seriesCb); } }); - }); + }, + checkTransactions: function (seriesCb) { + // Check against the mem_* tables that we can perform the transactions included in the block. + async.eachSeries(block.transactions, function (transaction, eachSeriesCb) { + __private.checkTransaction(block, transaction, eachSeriesCb); + }, function (err) { + return setImmediate(seriesCb, err); + }); + } + }, function (err) { + if (err) { + return setImmediate(cb, err); + } else { + // The block and the transactions are OK i.e: + // * Block and transactions have valid values (signatures, block slots, etc...) + // * The check against database state passed (for instance sender has enough LSK, votes are under 101, etc...) + // We thus update the database with the transactions values, save the block and tick it. + __private.applyBlock(block, broadcast, cb, saveBlock); + } }); }; From bf964707e75f5b352b520e71997fd0f97b9fd3d7 Mon Sep 17 00:00:00 2001 From: Oliver Beddows Date: Tue, 25 Oct 2016 11:43:56 +0200 Subject: [PATCH 098/272] Moving private methods together. --- modules/blocks.js | 470 +++++++++++++++++++++++----------------------- 1 file changed, 235 insertions(+), 235 deletions(-) diff --git a/modules/blocks.js b/modules/blocks.js index 8db39314995..7a428a910cb 100644 --- a/modules/blocks.js +++ b/modules/blocks.js @@ -465,6 +465,241 @@ __private.applyTransaction = function (block, transaction, sender, cb) { }); }; +// Apply the block, provided it has been verified. +__private.applyBlock = function (block, broadcast, cb, saveBlock) { + // Prevent shutdown during database writes. + __private.isActive = true; + + // Transactions to rewind in case of error. + var appliedTransactions = {}; + + // List of currrently unconfirmed transactions. + var unconfirmedTransactions; + + async.series({ + // Rewind any unconfirmed transactions before applying block. + // TODO: It should be possible to remove this call if we can guarantee that only this function is processing transactions atomically. Then speed should be improved further. + // TODO: Other possibility, when we rebuild from block chain this action should be moved out of the rebuild function. + undoUnconfirmedList: function (seriesCb) { + modules.transactions.undoUnconfirmedList(function (err, transactions) { + if (err) { + // TODO: Send a numbered signal to be caught by forever to trigger a rebuild. + return process.exit(0); + } else { + unconfirmedTransactions = transactions; + return setImmediate(seriesCb); + } + }); + }, + // Apply transactions to unconfirmed mem_accounts fields. + applyUnconfirmed: function (seriesCb) { + async.eachSeries(block.transactions, function (transaction, eachSeriesCb) { + // DATABASE write + modules.accounts.setAccountAndGet({publicKey: transaction.senderPublicKey}, function (err, sender) { + // DATABASE: write + modules.transactions.applyUnconfirmed(transaction, sender, function (err) { + if (err) { + err = ['Failed to apply transaction:', transaction.id, '-', err].join(' '); + library.logger.error(err); + library.logger.error('Transaction', transaction); + return setImmediate(eachSeriesCb, err); + } + + appliedTransactions[transaction.id] = transaction; + + // Remove the transaction from the node queue, if it was present. + var index = unconfirmedTransactions.indexOf(transaction.id); + if (index >= 0) { + unconfirmedTransactions.splice(index, 1); + } + + return setImmediate(eachSeriesCb); + }); + }); + }, function (err) { + if (err) { + // Rewind any already applied unconfirmed transactions. + // Leaves the database state as per the previous block. + async.eachSeries(block.transactions, function (transaction, eachSeriesCb) { + modules.accounts.getAccount({publicKey: transaction.senderPublicKey}, function (err, sender) { + if (err) { + return setImmediate(eachSeriesCb, err); + } + // The transaction has been applied? + if (appliedTransactions[transaction.id]) { + // DATABASE: write + library.logic.transaction.undoUnconfirmed(transaction, sender, eachSeriesCb); + } else { + return setImmediate(eachSeriesCb); + } + }); + }, function (err) { + return setImmediate(seriesCb, err); + }); + } else { + return setImmediate(seriesCb); + } + }); + }, + // Block and transactions are ok. + // Apply transactions to confirmed mem_accounts fields. + applyConfirmed: function (seriesCb) { + async.eachSeries(block.transactions, function (transaction, eachSeriesCb) { + modules.accounts.getAccount({publicKey: transaction.senderPublicKey}, function (err, sender) { + if (err) { + err = ['Failed to apply transaction:', transaction.id, '-', err].join(' '); + library.logger.error(err); + library.logger.error('Transaction', transaction); + // TODO: Send a numbered signal to be caught by forever to trigger a rebuild. + process.exit(0); + } + // DATABASE: write + modules.transactions.apply(transaction, block, sender, function (err) { + if (err) { + err = ['Failed to apply transaction:', transaction.id, '-', err].join(' '); + library.logger.error(err); + library.logger.error('Transaction', transaction); + // TODO: Send a numbered signal to be caught by forever to trigger a rebuild. + process.exit(0); + } + // Transaction applied, removed from the unconfirmed list. + modules.transactions.removeUnconfirmedTransaction(transaction.id); + return setImmediate(eachSeriesCb); + }); + }); + }, function (err) { + return setImmediate(seriesCb, err); + }); + }, + // Optionally save the block to the database. + saveBlock: function (seriesCb) { + __private.lastBlock = block; + + if (saveBlock) { + // DATABASE: write + __private.saveBlock(block, function (err) { + if (err) { + library.logger.error('Failed to save block...'); + library.logger.error('Block', block); + // TODO: Send a numbered signal to be caught by forever to trigger a rebuild. + process.exit(0); + } + + library.logger.debug('Block applied correctly with ' + block.transactions.length + ' transactions'); + library.bus.message('newBlock', block, broadcast); + + // DATABASE write. Update delegates accounts + modules.rounds.tick(block, seriesCb); + }); + } else { + library.bus.message('newBlock', block, broadcast); + + // DATABASE write. Update delegates accounts + modules.rounds.tick(block, seriesCb); + } + }, + // Push back unconfirmed transactions list (minus the one that were on the block if applied correctly). + // TODO: See undoUnconfirmedList discussion above. + applyUnconfirmedList: function (seriesCb) { + // DATABASE write + modules.transactions.applyUnconfirmedList(unconfirmedTransactions, function (err) { + return setImmediate(seriesCb, err); + }); + }, + }, function (err) { + // Allow shutdown, database writes are finished. + __private.isActive = false; + + // Nullify large objects. + // Prevents memory leak during synchronisation. + appliedTransactions = unconfirmedTransactions = block = null; + + // Finish here if snapshotting. + if (err === 'Snapshot finished') { + library.logger.info(err); + process.emit('SIGTERM'); + } + + return setImmediate(cb, err); + }); +}; + +// Apply the genesis block, provided it has been verified. +// Shortcuting the unconfirmed/confirmed states. +__private.applyGenesisBlock = function (block, cb) { + block.transactions = block.transactions.sort(function (a, b) { + if (a.type === transactionTypes.VOTE) { + return 1; + } else { + return 0; + } + }); + async.eachSeries(block.transactions, function (transaction, cb) { + modules.accounts.setAccountAndGet({publicKey: transaction.senderPublicKey}, function (err, sender) { + if (err) { + return setImmediate(cb, { + message: err, + transaction: transaction, + block: block + }); + } + __private.applyTransaction(block, transaction, sender, cb); + }); + }, function (err) { + if (err) { + // If genesis block is invalid, kill the node... + return process.exit(0); + } else { + __private.lastBlock = block; + modules.rounds.tick(__private.lastBlock, cb); + } + }); +}; + +__private.checkTransaction = function (block, transaction, cb) { + async.waterfall([ + function (waterCb) { + try { + transaction.id = library.logic.transaction.getId(transaction); + } catch (e) { + return setImmediate(waterCb, e.toString()); + } + transaction.blockId = block.id; + return setImmediate(waterCb); + }, + function (waterCb) { + // Check if transaction is already in database, otherwise fork 2. + // DATABASE: read only + library.logic.transaction.checkConfirmed(transaction, function (err) { + if (err) { + // Fork: Transaction already confirmed. + modules.delegates.fork(block, 2); + // Undo the offending transaction. + // DATABASE: write + modules.transactions.undoUnconfirmed(transaction, function (err2) { + modules.transactions.removeUnconfirmedTransaction(transaction.id); + return setImmediate(waterCb, err2 || err); + }); + } else { + return setImmediate(waterCb); + } + }); + }, + function (waterCb) { + // Get account from database if any (otherwise cold wallet). + // DATABASE: read only + modules.accounts.getAccount({publicKey: transaction.senderPublicKey}, waterCb); + }, + function (sender, waterCb) { + // Check if transaction id valid against database state (mem_* tables). + // DATABASE: read only + library.logic.transaction.verify(transaction, sender, waterCb); + } + ], function (err) { + return setImmediate(cb, err); + }); +}; + // Public methods Blocks.prototype.lastReceipt = function (lastReceipt) { if (lastReceipt) { @@ -772,241 +1007,6 @@ Blocks.prototype.verifyBlock = function (block) { return result; }; -// Apply the block, provided it has been verified. -__private.applyBlock = function (block, broadcast, cb, saveBlock) { - // Prevent shutdown during database writes. - __private.isActive = true; - - // Transactions to rewind in case of error. - var appliedTransactions = {}; - - // List of currrently unconfirmed transactions. - var unconfirmedTransactions; - - async.series({ - // Rewind any unconfirmed transactions before applying block. - // TODO: It should be possible to remove this call if we can guarantee that only this function is processing transactions atomically. Then speed should be improved further. - // TODO: Other possibility, when we rebuild from block chain this action should be moved out of the rebuild function. - undoUnconfirmedList: function (seriesCb) { - modules.transactions.undoUnconfirmedList(function (err, transactions) { - if (err) { - // TODO: Send a numbered signal to be caught by forever to trigger a rebuild. - return process.exit(0); - } else { - unconfirmedTransactions = transactions; - return setImmediate(seriesCb); - } - }); - }, - // Apply transactions to unconfirmed mem_accounts fields. - applyUnconfirmed: function (seriesCb) { - async.eachSeries(block.transactions, function (transaction, eachSeriesCb) { - // DATABASE write - modules.accounts.setAccountAndGet({publicKey: transaction.senderPublicKey}, function (err, sender) { - // DATABASE: write - modules.transactions.applyUnconfirmed(transaction, sender, function (err) { - if (err) { - err = ['Failed to apply transaction:', transaction.id, '-', err].join(' '); - library.logger.error(err); - library.logger.error('Transaction', transaction); - return setImmediate(eachSeriesCb, err); - } - - appliedTransactions[transaction.id] = transaction; - - // Remove the transaction from the node queue, if it was present. - var index = unconfirmedTransactions.indexOf(transaction.id); - if (index >= 0) { - unconfirmedTransactions.splice(index, 1); - } - - return setImmediate(eachSeriesCb); - }); - }); - }, function (err) { - if (err) { - // Rewind any already applied unconfirmed transactions. - // Leaves the database state as per the previous block. - async.eachSeries(block.transactions, function (transaction, eachSeriesCb) { - modules.accounts.getAccount({publicKey: transaction.senderPublicKey}, function (err, sender) { - if (err) { - return setImmediate(eachSeriesCb, err); - } - // The transaction has been applied? - if (appliedTransactions[transaction.id]) { - // DATABASE: write - library.logic.transaction.undoUnconfirmed(transaction, sender, eachSeriesCb); - } else { - return setImmediate(eachSeriesCb); - } - }); - }, function (err) { - return setImmediate(seriesCb, err); - }); - } else { - return setImmediate(seriesCb); - } - }); - }, - // Block and transactions are ok. - // Apply transactions to confirmed mem_accounts fields. - applyConfirmed: function (seriesCb) { - async.eachSeries(block.transactions, function (transaction, eachSeriesCb) { - modules.accounts.getAccount({publicKey: transaction.senderPublicKey}, function (err, sender) { - if (err) { - err = ['Failed to apply transaction:', transaction.id, '-', err].join(' '); - library.logger.error(err); - library.logger.error('Transaction', transaction); - // TODO: Send a numbered signal to be caught by forever to trigger a rebuild. - process.exit(0); - } - // DATABASE: write - modules.transactions.apply(transaction, block, sender, function (err) { - if (err) { - err = ['Failed to apply transaction:', transaction.id, '-', err].join(' '); - library.logger.error(err); - library.logger.error('Transaction', transaction); - // TODO: Send a numbered signal to be caught by forever to trigger a rebuild. - process.exit(0); - } - // Transaction applied, removed from the unconfirmed list. - modules.transactions.removeUnconfirmedTransaction(transaction.id); - return setImmediate(eachSeriesCb); - }); - }); - }, function (err) { - return setImmediate(seriesCb, err); - }); - }, - // Optionally save the block to the database. - saveBlock: function (seriesCb) { - __private.lastBlock = block; - - if (saveBlock) { - // DATABASE: write - __private.saveBlock(block, function (err) { - if (err) { - library.logger.error('Failed to save block...'); - library.logger.error('Block', block); - // TODO: Send a numbered signal to be caught by forever to trigger a rebuild. - process.exit(0); - } - - library.logger.debug('Block applied correctly with ' + block.transactions.length + ' transactions'); - library.bus.message('newBlock', block, broadcast); - - // DATABASE write. Update delegates accounts - modules.rounds.tick(block, seriesCb); - }); - } else { - library.bus.message('newBlock', block, broadcast); - - // DATABASE write. Update delegates accounts - modules.rounds.tick(block, seriesCb); - } - }, - // Push back unconfirmed transactions list (minus the one that were on the block if applied correctly). - // TODO: See undoUnconfirmedList discussion above. - applyUnconfirmedList: function (seriesCb) { - // DATABASE write - modules.transactions.applyUnconfirmedList(unconfirmedTransactions, function (err) { - return setImmediate(seriesCb, err); - }); - }, - }, function (err) { - // Allow shutdown, database writes are finished. - __private.isActive = false; - - // Nullify large objects. - // Prevents memory leak during synchronisation. - appliedTransactions = unconfirmedTransactions = block = null; - - // Finish here if snapshotting. - if (err === 'Snapshot finished') { - library.logger.info(err); - process.emit('SIGTERM'); - } - - return setImmediate(cb, err); - }); -}; - -// Apply the genesis block, provided it has been verified. -// Shortcuting the unconfirmed/confirmed states. -__private.applyGenesisBlock = function (block, cb) { - block.transactions = block.transactions.sort(function (a, b) { - if (a.type === transactionTypes.VOTE) { - return 1; - } else { - return 0; - } - }); - async.eachSeries(block.transactions, function (transaction, cb) { - modules.accounts.setAccountAndGet({publicKey: transaction.senderPublicKey}, function (err, sender) { - if (err) { - return setImmediate(cb, { - message: err, - transaction: transaction, - block: block - }); - } - __private.applyTransaction(block, transaction, sender, cb); - }); - }, function (err) { - if (err) { - // If genesis block is invalid, kill the node... - return process.exit(0); - } else { - __private.lastBlock = block; - modules.rounds.tick(__private.lastBlock, cb); - } - }); -}; - -__private.checkTransaction = function (block, transaction, cb) { - async.waterfall([ - function (waterCb) { - try { - transaction.id = library.logic.transaction.getId(transaction); - } catch (e) { - return setImmediate(waterCb, e.toString()); - } - transaction.blockId = block.id; - return setImmediate(waterCb); - }, - function (waterCb) { - // Check if transaction is already in database, otherwise fork 2. - // DATABASE: read only - library.logic.transaction.checkConfirmed(transaction, function (err) { - if (err) { - // Fork: Transaction already confirmed. - modules.delegates.fork(block, 2); - // Undo the offending transaction. - // DATABASE: write - modules.transactions.undoUnconfirmed(transaction, function (err2) { - modules.transactions.removeUnconfirmedTransaction(transaction.id); - return setImmediate(waterCb, err2 || err); - }); - } else { - return setImmediate(waterCb); - } - }); - }, - function (waterCb) { - // Get account from database if any (otherwise cold wallet). - // DATABASE: read only - modules.accounts.getAccount({publicKey: transaction.senderPublicKey}, waterCb); - }, - function (sender, waterCb) { - // Check if transaction id valid against database state (mem_* tables). - // DATABASE: read only - library.logic.transaction.verify(transaction, sender, waterCb); - } - ], function (err) { - return setImmediate(cb, err); - }); -}; - // Main function to process a Block. // * Verify the block looks ok // * Verify the block is compatible with database state (DATABASE readonly) From 89a8eb9aa34c92eda437d1d39ef32a678748dd9e Mon Sep 17 00:00:00 2001 From: Oliver Beddows Date: Tue, 25 Oct 2016 11:47:41 +0200 Subject: [PATCH 099/272] Changing logical order of private methods. --- modules/blocks.js | 408 +++++++++++++++++++++++----------------------- 1 file changed, 204 insertions(+), 204 deletions(-) diff --git a/modules/blocks.js b/modules/blocks.js index 7a428a910cb..0181c7ab432 100644 --- a/modules/blocks.js +++ b/modules/blocks.js @@ -123,32 +123,6 @@ __private.attachApi = function () { }); }; -__private.saveGenesisBlock = function (cb) { - library.db.query(sql.getGenesisBlockId, { id: genesisblock.block.id }).then(function (rows) { - var blockId = rows.length && rows[0].id; - - if (!blockId) { - __private.saveBlock(genesisblock.block, function (err) { - return setImmediate(cb, err); - }); - } else { - return setImmediate(cb); - } - }).catch(function (err) { - library.logger.error(err.stack); - return setImmediate(cb, 'Blocks#saveGenesisBlock error'); - }); -}; - -__private.deleteBlock = function (blockId, cb) { - library.db.none(sql.deleteBlock, {id: blockId}).then(function () { - return setImmediate(cb); - }).catch(function (err) { - library.logger.error(err.stack); - return setImmediate(cb, 'Blocks#deleteBlock error'); - }); -}; - __private.list = function (filter, cb) { var params = {}, where = []; @@ -246,6 +220,44 @@ __private.list = function (filter, cb) { }); }; +__private.readDbRows = function (rows) { + var blocks = {}; + var order = []; + + for (var i = 0, length = rows.length; i < length; i++) { + var block = library.logic.block.dbRead(rows[i]); + + if (block) { + if (!blocks[block.id]) { + if (block.id === genesisblock.block.id) { + block.generationSignature = (new Array(65)).join('0'); + } + + order.push(block.id); + blocks[block.id] = block; + } + + var transaction = library.logic.transaction.dbRead(rows[i]); + blocks[block.id].transactions = blocks[block.id].transactions || {}; + + if (transaction) { + if (!blocks[block.id].transactions[transaction.id]) { + blocks[block.id].transactions[transaction.id] = transaction; + } + } + } + } + + blocks = order.map(function (v) { + blocks[v].transactions = Object.keys(blocks[v].transactions).map(function (t) { + return blocks[v].transactions[t]; + }); + return blocks[v]; + }); + + return blocks; +}; + __private.getById = function (id, cb) { library.db.query(sql.getById, {id: id}).then(function (rows) { if (!rows.length) { @@ -261,6 +273,94 @@ __private.getById = function (id, cb) { }); }; +__private.getIdSequence = function (height, cb) { + library.db.query(sql.getIdSequence, { height: height, limit: 5, delegates: slots.delegates, activeDelegates: constants.activeDelegates }).then(function (rows) { + if (rows.length === 0) { + return setImmediate(cb, 'Failed to get id sequence for height: ' + height); + } + + var ids = []; + + if (genesisblock && genesisblock.block) { + var __genesisblock = { + id: genesisblock.block.id, + height: genesisblock.block.height + }; + + if (!_.includes(rows, __genesisblock.id)) { + rows.push(__genesisblock); + } + } + + if (__private.lastBlock && !_.includes(rows, __private.lastBlock.id)) { + rows.unshift({ + id: __private.lastBlock.id, + height: __private.lastBlock.height + }); + } + + rows.forEach(function (row) { + if (!_.includes(ids, row.id)) { + ids.push(row.id); + } + }); + + return setImmediate(cb, null, { firstHeight: rows[0].height, ids: ids.join(',') }); + }).catch(function (err) { + library.logger.error(err.stack); + return setImmediate(cb, 'Blocks#getIdSequence error'); + }); +}; + +__private.saveGenesisBlock = function (cb) { + library.db.query(sql.getGenesisBlockId, { id: genesisblock.block.id }).then(function (rows) { + var blockId = rows.length && rows[0].id; + + if (!blockId) { + __private.saveBlock(genesisblock.block, function (err) { + return setImmediate(cb, err); + }); + } else { + return setImmediate(cb); + } + }).catch(function (err) { + library.logger.error(err.stack); + return setImmediate(cb, 'Blocks#saveGenesisBlock error'); + }); +}; + +// Apply the genesis block, provided it has been verified. +// Shortcuting the unconfirmed/confirmed states. +__private.applyGenesisBlock = function (block, cb) { + block.transactions = block.transactions.sort(function (a, b) { + if (a.type === transactionTypes.VOTE) { + return 1; + } else { + return 0; + } + }); + async.eachSeries(block.transactions, function (transaction, cb) { + modules.accounts.setAccountAndGet({publicKey: transaction.senderPublicKey}, function (err, sender) { + if (err) { + return setImmediate(cb, { + message: err, + transaction: transaction, + block: block + }); + } + __private.applyTransaction(block, transaction, sender, cb); + }); + }, function (err) { + if (err) { + // If genesis block is invalid, kill the node... + return process.exit(0); + } else { + __private.lastBlock = block; + modules.rounds.tick(__private.lastBlock, cb); + } + }); +}; + __private.saveBlock = function (block, cb) { library.db.tx(function (t) { var promise = library.logic.block.dbSave(block); @@ -319,152 +419,6 @@ __private.promiseTransactions = function (t, block, blockPromises) { return t; }; -__private.afterSave = function (block, cb) { - async.eachSeries(block.transactions, function (transaction, cb) { - return library.logic.transaction.afterSave(transaction, cb); - }, function (err) { - return setImmediate(cb, err); - }); -}; - -__private.popLastBlock = function (oldLastBlock, cb) { - library.balancesSequence.add(function (cb) { - self.loadBlocksPart({ id: oldLastBlock.previousBlock }, function (err, previousBlock) { - if (err || !previousBlock.length) { - return setImmediate(cb, err || 'previousBlock is null'); - } - previousBlock = previousBlock[0]; - - async.eachSeries(oldLastBlock.transactions.reverse(), function (transaction, cb) { - async.series([ - function (cb) { - modules.accounts.getAccount({publicKey: transaction.senderPublicKey}, function (err, sender) { - if (err) { - return setImmediate(cb, err); - } - modules.transactions.undo(transaction, oldLastBlock, sender, cb); - }); - }, function (cb) { - modules.transactions.undoUnconfirmed(transaction, cb); - }, function (cb) { - return setImmediate(cb); - } - ], cb); - }, function (err) { - modules.rounds.backwardTick(oldLastBlock, previousBlock, function () { - __private.deleteBlock(oldLastBlock.id, function (err) { - if (err) { - return setImmediate(cb, err); - } - - return setImmediate(cb, null, previousBlock); - }); - }); - }); - }); - }, cb); -}; - -__private.getIdSequence = function (height, cb) { - library.db.query(sql.getIdSequence, { height: height, limit: 5, delegates: slots.delegates, activeDelegates: constants.activeDelegates }).then(function (rows) { - if (rows.length === 0) { - return setImmediate(cb, 'Failed to get id sequence for height: ' + height); - } - - var ids = []; - - if (genesisblock && genesisblock.block) { - var __genesisblock = { - id: genesisblock.block.id, - height: genesisblock.block.height - }; - - if (!_.includes(rows, __genesisblock.id)) { - rows.push(__genesisblock); - } - } - - if (__private.lastBlock && !_.includes(rows, __private.lastBlock.id)) { - rows.unshift({ - id: __private.lastBlock.id, - height: __private.lastBlock.height - }); - } - - rows.forEach(function (row) { - if (!_.includes(ids, row.id)) { - ids.push(row.id); - } - }); - - return setImmediate(cb, null, { firstHeight: rows[0].height, ids: ids.join(',') }); - }).catch(function (err) { - library.logger.error(err.stack); - return setImmediate(cb, 'Blocks#getIdSequence error'); - }); -}; - -__private.readDbRows = function (rows) { - var blocks = {}; - var order = []; - - for (var i = 0, length = rows.length; i < length; i++) { - var block = library.logic.block.dbRead(rows[i]); - - if (block) { - if (!blocks[block.id]) { - if (block.id === genesisblock.block.id) { - block.generationSignature = (new Array(65)).join('0'); - } - - order.push(block.id); - blocks[block.id] = block; - } - - var transaction = library.logic.transaction.dbRead(rows[i]); - blocks[block.id].transactions = blocks[block.id].transactions || {}; - - if (transaction) { - if (!blocks[block.id].transactions[transaction.id]) { - blocks[block.id].transactions[transaction.id] = transaction; - } - } - } - } - - blocks = order.map(function (v) { - blocks[v].transactions = Object.keys(blocks[v].transactions).map(function (t) { - return blocks[v].transactions[t]; - }); - return blocks[v]; - }); - - return blocks; -}; - -__private.applyTransaction = function (block, transaction, sender, cb) { - modules.transactions.applyUnconfirmed(transaction, sender, function (err) { - if (err) { - return setImmediate(cb, { - message: err, - transaction: transaction, - block: block - }); - } - - modules.transactions.apply(transaction, block, sender, function (err) { - if (err) { - return setImmediate(cb, { - message: 'Failed to apply transaction: ' + transaction.id, - transaction: transaction, - block: block - }); - } - return setImmediate(cb); - }); - }); -}; - // Apply the block, provided it has been verified. __private.applyBlock = function (block, broadcast, cb, saveBlock) { // Prevent shutdown during database writes. @@ -624,38 +578,6 @@ __private.applyBlock = function (block, broadcast, cb, saveBlock) { }); }; -// Apply the genesis block, provided it has been verified. -// Shortcuting the unconfirmed/confirmed states. -__private.applyGenesisBlock = function (block, cb) { - block.transactions = block.transactions.sort(function (a, b) { - if (a.type === transactionTypes.VOTE) { - return 1; - } else { - return 0; - } - }); - async.eachSeries(block.transactions, function (transaction, cb) { - modules.accounts.setAccountAndGet({publicKey: transaction.senderPublicKey}, function (err, sender) { - if (err) { - return setImmediate(cb, { - message: err, - transaction: transaction, - block: block - }); - } - __private.applyTransaction(block, transaction, sender, cb); - }); - }, function (err) { - if (err) { - // If genesis block is invalid, kill the node... - return process.exit(0); - } else { - __private.lastBlock = block; - modules.rounds.tick(__private.lastBlock, cb); - } - }); -}; - __private.checkTransaction = function (block, transaction, cb) { async.waterfall([ function (waterCb) { @@ -700,6 +622,84 @@ __private.checkTransaction = function (block, transaction, cb) { }); }; +__private.applyTransaction = function (block, transaction, sender, cb) { + modules.transactions.applyUnconfirmed(transaction, sender, function (err) { + if (err) { + return setImmediate(cb, { + message: err, + transaction: transaction, + block: block + }); + } + + modules.transactions.apply(transaction, block, sender, function (err) { + if (err) { + return setImmediate(cb, { + message: 'Failed to apply transaction: ' + transaction.id, + transaction: transaction, + block: block + }); + } + return setImmediate(cb); + }); + }); +}; + +__private.afterSave = function (block, cb) { + async.eachSeries(block.transactions, function (transaction, cb) { + return library.logic.transaction.afterSave(transaction, cb); + }, function (err) { + return setImmediate(cb, err); + }); +}; + +__private.popLastBlock = function (oldLastBlock, cb) { + library.balancesSequence.add(function (cb) { + self.loadBlocksPart({ id: oldLastBlock.previousBlock }, function (err, previousBlock) { + if (err || !previousBlock.length) { + return setImmediate(cb, err || 'previousBlock is null'); + } + previousBlock = previousBlock[0]; + + async.eachSeries(oldLastBlock.transactions.reverse(), function (transaction, cb) { + async.series([ + function (cb) { + modules.accounts.getAccount({publicKey: transaction.senderPublicKey}, function (err, sender) { + if (err) { + return setImmediate(cb, err); + } + modules.transactions.undo(transaction, oldLastBlock, sender, cb); + }); + }, function (cb) { + modules.transactions.undoUnconfirmed(transaction, cb); + }, function (cb) { + return setImmediate(cb); + } + ], cb); + }, function (err) { + modules.rounds.backwardTick(oldLastBlock, previousBlock, function () { + __private.deleteBlock(oldLastBlock.id, function (err) { + if (err) { + return setImmediate(cb, err); + } + + return setImmediate(cb, null, previousBlock); + }); + }); + }); + }); + }, cb); +}; + +__private.deleteBlock = function (blockId, cb) { + library.db.none(sql.deleteBlock, {id: blockId}).then(function () { + return setImmediate(cb); + }).catch(function (err) { + library.logger.error(err.stack); + return setImmediate(cb, 'Blocks#deleteBlock error'); + }); +}; + // Public methods Blocks.prototype.lastReceipt = function (lastReceipt) { if (lastReceipt) { From 535035b8f70565772a0d1febf4b4bc9c6180473a Mon Sep 17 00:00:00 2001 From: Oliver Beddows Date: Tue, 25 Oct 2016 11:50:03 +0200 Subject: [PATCH 100/272] Changing logical order of public methods. --- modules/blocks.js | 394 +++++++++++++++++++++++----------------------- 1 file changed, 197 insertions(+), 197 deletions(-) diff --git a/modules/blocks.js b/modules/blocks.js index 0181c7ab432..f84516f4241 100644 --- a/modules/blocks.js +++ b/modules/blocks.js @@ -701,6 +701,30 @@ __private.deleteBlock = function (blockId, cb) { }; // Public methods +Blocks.prototype.count = function (cb) { + library.db.query(sql.countByRowId).then(function (rows) { + var res = rows.length ? rows[0].count : 0; + + return setImmediate(cb, null, res); + }).catch(function (err) { + library.logger.error(err.stack); + return setImmediate(cb, 'Blocks#count error'); + }); +}; + +Blocks.prototype.getLastBlock = function () { + if (__private.lastBlock) { + var epoch = constants.epochTime / 1000; + var lastBlockTime = epoch + __private.lastBlock.timestamp; + var currentTime = new Date().getTime() / 1000; + + __private.lastBlock.secondsAgo = currentTime - lastBlockTime; + __private.lastBlock.fresh = (__private.lastBlock.secondsAgo < constants.blockReceiptTimeOut); + } + + return __private.lastBlock; +}; + Blocks.prototype.lastReceipt = function (lastReceipt) { if (lastReceipt) { __private.lastReceipt = lastReceipt; @@ -759,14 +783,63 @@ Blocks.prototype.getCommonBlock = function (peer, height, cb) { }); }; -Blocks.prototype.count = function (cb) { - library.db.query(sql.countByRowId).then(function (rows) { - var res = rows.length ? rows[0].count : 0; +Blocks.prototype.loadBlocksFromPeer = function (peer, cb) { + var lastValidBlock = __private.lastBlock; - return setImmediate(cb, null, res); - }).catch(function (err) { - library.logger.error(err.stack); - return setImmediate(cb, 'Blocks#count error'); + peer = modules.peers.accept(peer); + library.logger.info('Loading blocks from: ' + peer.string); + + modules.transport.getFromPeer(peer, { + method: 'GET', + api: '/blocks?lastBlockId=' + lastValidBlock.id + }, function (err, res) { + if (err || res.body.error) { + return setImmediate(cb, err, lastValidBlock); + } + + var report = library.schema.validate(res.body.blocks, schema.loadBlocksFromPeer); + + if (!report) { + return setImmediate(cb, 'Received invalid blocks data', lastValidBlock); + } + + var blocks = __private.readDbRows(res.body.blocks); + + if (blocks.length === 0) { + return setImmediate(cb, null, lastValidBlock); + } else { + async.eachSeries(blocks, function (block, cb) { + if (__private.cleanup) { + return setImmediate(cb); + } + + self.processBlock(block, false, function (err) { + if (!err) { + lastValidBlock = block; + library.logger.info(['Block', block.id, 'loaded from:', peer.string].join(' '), 'height: ' + block.height); + } else { + var id = (block ? block.id : 'null'); + + library.logger.error(['Block', id].join(' '), err.toString()); + if (block) { library.logger.error('Block', block); } + + library.logger.warn(['Block', id, 'is not valid, ban 60 min'].join(' '), peer.string); + modules.peers.state(peer.ip, peer.port, 0, 3600); + } + return cb(err); + }, true); + }, function (err) { + // Nullify large array of blocks. + // Prevents memory leak during synchronisation. + res = blocks = null; + + if (err) { + return setImmediate(cb, 'Error loading blocks: ' + (err.message || err), lastValidBlock); + } else { + return setImmediate(cb, null, lastValidBlock); + } + }); + } }); }; @@ -890,17 +963,120 @@ Blocks.prototype.loadLastBlock = function (cb) { }, cb); }; -Blocks.prototype.getLastBlock = function () { - if (__private.lastBlock) { - var epoch = constants.epochTime / 1000; - var lastBlockTime = epoch + __private.lastBlock.timestamp; - var currentTime = new Date().getTime() / 1000; +Blocks.prototype.generateBlock = function (keypair, timestamp, cb) { + var transactions = modules.transactions.getUnconfirmedTransactionList(false, constants.maxTxsPerBlock); + var ready = []; - __private.lastBlock.secondsAgo = currentTime - lastBlockTime; - __private.lastBlock.fresh = (__private.lastBlock.secondsAgo < constants.blockReceiptTimeOut); + async.eachSeries(transactions, function (transaction, cb) { + modules.accounts.getAccount({ publicKey: transaction.senderPublicKey }, function (err, sender) { + if (err || !sender) { + return setImmediate(cb, 'Sender not found'); + } + + if (library.logic.transaction.ready(transaction, sender)) { + library.logic.transaction.verify(transaction, sender, function (err) { + ready.push(transaction); + return setImmediate(cb); + }); + } else { + return setImmediate(cb); + } + }); + }, function () { + var block; + + try { + block = library.logic.block.create({ + keypair: keypair, + timestamp: timestamp, + previousBlock: __private.lastBlock, + transactions: ready + }); + } catch (e) { + library.logger.error(e.stack); + return setImmediate(cb, e); + } + + self.processBlock(block, true, cb, true); + }); +}; + +// Main function to process a Block. +// * Verify the block looks ok +// * Verify the block is compatible with database state (DATABASE readonly) +// * Apply the block to database if both verifications are ok +Blocks.prototype.processBlock = function (block, broadcast, cb, saveBlock) { + if (__private.cleanup) { + return setImmediate(cb, 'Cleaning up'); + } else if (!__private.loaded) { + return setImmediate(cb, 'Blockchain is loading'); } - return __private.lastBlock; + async.series({ + normalizeBlock: function (seriesCb) { + try { + block = library.logic.block.objectNormalize(block); + } catch (err) { + return setImmediate(seriesCb, err); + } + + return setImmediate(seriesCb); + }, + verifyBlock: function (seriesCb) { + // Sanity check of the block, if values are coherent. + // No access to database + var check = self.verifyBlock(block); + + if (!check.verified) { + library.logger.error(['Block', block.id, 'verification failed'].join(' '), check.errors.join(', ')); + return setImmediate(seriesCb, check.errors[0]); + } + + return setImmediate(seriesCb); + }, + checkExists: function (seriesCb) { + // Check if block id is already in the database (very low probability of hash collision). + // TODO: In case of hash-collision, to me it would be a special autofork... + // DATABASE: read only + library.db.query(sql.getBlockId, { id: block.id }).then(function (rows) { + if (rows.length > 0) { + return setImmediate(seriesCb, ['Block', block.id, 'already exists'].join(' ')); + } else { + return setImmediate(seriesCb); + } + }); + }, + validateBlockSlot: function (seriesCb) { + // Check if block was generated by the right active delagate. Otherwise, fork 3. + // DATABASE: Read only to mem_accounts to extract active delegate list + modules.delegates.validateBlockSlot(block, function (err) { + if (err) { + modules.delegates.fork(block, 3); + return setImmediate(seriesCb, err); + } else { + return setImmediate(seriesCb); + } + }); + }, + checkTransactions: function (seriesCb) { + // Check against the mem_* tables that we can perform the transactions included in the block. + async.eachSeries(block.transactions, function (transaction, eachSeriesCb) { + __private.checkTransaction(block, transaction, eachSeriesCb); + }, function (err) { + return setImmediate(seriesCb, err); + }); + } + }, function (err) { + if (err) { + return setImmediate(cb, err); + } else { + // The block and the transactions are OK i.e: + // * Block and transactions have valid values (signatures, block slots, etc...) + // * The check against database state passed (for instance sender has enough LSK, votes are under 101, etc...) + // We thus update the database with the transactions values, save the block and tick it. + __private.applyBlock(block, broadcast, cb, saveBlock); + } + }); }; // Will return all possible errors that are intrinsic to the block. @@ -1007,153 +1183,6 @@ Blocks.prototype.verifyBlock = function (block) { return result; }; -// Main function to process a Block. -// * Verify the block looks ok -// * Verify the block is compatible with database state (DATABASE readonly) -// * Apply the block to database if both verifications are ok -Blocks.prototype.processBlock = function (block, broadcast, cb, saveBlock) { - if (__private.cleanup) { - return setImmediate(cb, 'Cleaning up'); - } else if (!__private.loaded) { - return setImmediate(cb, 'Blockchain is loading'); - } - - async.series({ - normalizeBlock: function (seriesCb) { - try { - block = library.logic.block.objectNormalize(block); - } catch (err) { - return setImmediate(seriesCb, err); - } - - return setImmediate(seriesCb); - }, - verifyBlock: function (seriesCb) { - // Sanity check of the block, if values are coherent. - // No access to database - var check = self.verifyBlock(block); - - if (!check.verified) { - library.logger.error(['Block', block.id, 'verification failed'].join(' '), check.errors.join(', ')); - return setImmediate(seriesCb, check.errors[0]); - } - - return setImmediate(seriesCb); - }, - checkExists: function (seriesCb) { - // Check if block id is already in the database (very low probability of hash collision). - // TODO: In case of hash-collision, to me it would be a special autofork... - // DATABASE: read only - library.db.query(sql.getBlockId, { id: block.id }).then(function (rows) { - if (rows.length > 0) { - return setImmediate(seriesCb, ['Block', block.id, 'already exists'].join(' ')); - } else { - return setImmediate(seriesCb); - } - }); - }, - validateBlockSlot: function (seriesCb) { - // Check if block was generated by the right active delagate. Otherwise, fork 3. - // DATABASE: Read only to mem_accounts to extract active delegate list - modules.delegates.validateBlockSlot(block, function (err) { - if (err) { - modules.delegates.fork(block, 3); - return setImmediate(seriesCb, err); - } else { - return setImmediate(seriesCb); - } - }); - }, - checkTransactions: function (seriesCb) { - // Check against the mem_* tables that we can perform the transactions included in the block. - async.eachSeries(block.transactions, function (transaction, eachSeriesCb) { - __private.checkTransaction(block, transaction, eachSeriesCb); - }, function (err) { - return setImmediate(seriesCb, err); - }); - } - }, function (err) { - if (err) { - return setImmediate(cb, err); - } else { - // The block and the transactions are OK i.e: - // * Block and transactions have valid values (signatures, block slots, etc...) - // * The check against database state passed (for instance sender has enough LSK, votes are under 101, etc...) - // We thus update the database with the transactions values, save the block and tick it. - __private.applyBlock(block, broadcast, cb, saveBlock); - } - }); -}; - -Blocks.prototype.simpleDeleteAfterBlock = function (blockId, cb) { - library.db.query(sql.simpleDeleteAfterBlock, {id: blockId}).then(function (res) { - return setImmediate(cb, null, res); - }).catch(function (err) { - library.logger.error(err.stack); - return setImmediate(cb, 'Blocks#simpleDeleteAfterBlock error'); - }); -}; - -Blocks.prototype.loadBlocksFromPeer = function (peer, cb) { - var lastValidBlock = __private.lastBlock; - - peer = modules.peers.accept(peer); - library.logger.info('Loading blocks from: ' + peer.string); - - modules.transport.getFromPeer(peer, { - method: 'GET', - api: '/blocks?lastBlockId=' + lastValidBlock.id - }, function (err, res) { - if (err || res.body.error) { - return setImmediate(cb, err, lastValidBlock); - } - - var report = library.schema.validate(res.body.blocks, schema.loadBlocksFromPeer); - - if (!report) { - return setImmediate(cb, 'Received invalid blocks data', lastValidBlock); - } - - var blocks = __private.readDbRows(res.body.blocks); - - if (blocks.length === 0) { - return setImmediate(cb, null, lastValidBlock); - } else { - async.eachSeries(blocks, function (block, cb) { - if (__private.cleanup) { - return setImmediate(cb); - } - - self.processBlock(block, false, function (err) { - if (!err) { - lastValidBlock = block; - library.logger.info(['Block', block.id, 'loaded from:', peer.string].join(' '), 'height: ' + block.height); - } else { - var id = (block ? block.id : 'null'); - - library.logger.error(['Block', id].join(' '), err.toString()); - if (block) { library.logger.error('Block', block); } - - library.logger.warn(['Block', id, 'is not valid, ban 60 min'].join(' '), peer.string); - modules.peers.state(peer.ip, peer.port, 0, 3600); - } - return cb(err); - }, true); - }, function (err) { - // Nullify large array of blocks. - // Prevents memory leak during synchronisation. - res = blocks = null; - - if (err) { - return setImmediate(cb, 'Error loading blocks: ' + (err.message || err), lastValidBlock); - } else { - return setImmediate(cb, null, lastValidBlock); - } - }); - } - }); -}; - Blocks.prototype.deleteBlocksBefore = function (block, cb) { var blocks = []; @@ -1174,41 +1203,12 @@ Blocks.prototype.deleteBlocksBefore = function (block, cb) { ); }; -Blocks.prototype.generateBlock = function (keypair, timestamp, cb) { - var transactions = modules.transactions.getUnconfirmedTransactionList(false, constants.maxTxsPerBlock); - var ready = []; - - async.eachSeries(transactions, function (transaction, cb) { - modules.accounts.getAccount({ publicKey: transaction.senderPublicKey }, function (err, sender) { - if (err || !sender) { - return setImmediate(cb, 'Sender not found'); - } - - if (library.logic.transaction.ready(transaction, sender)) { - library.logic.transaction.verify(transaction, sender, function (err) { - ready.push(transaction); - return setImmediate(cb); - }); - } else { - return setImmediate(cb); - } - }); - }, function () { - var block; - - try { - block = library.logic.block.create({ - keypair: keypair, - timestamp: timestamp, - previousBlock: __private.lastBlock, - transactions: ready - }); - } catch (e) { - library.logger.error(e.stack); - return setImmediate(cb, e); - } - - self.processBlock(block, true, cb, true); +Blocks.prototype.simpleDeleteAfterBlock = function (blockId, cb) { + library.db.query(sql.simpleDeleteAfterBlock, {id: blockId}).then(function (res) { + return setImmediate(cb, null, res); + }).catch(function (err) { + library.logger.error(err.stack); + return setImmediate(cb, 'Blocks#simpleDeleteAfterBlock error'); }); }; From 2cb8d420555456b28f59113a37325c9ddc5298bd Mon Sep 17 00:00:00 2001 From: Oliver Beddows Date: Tue, 25 Oct 2016 12:17:36 +0200 Subject: [PATCH 101/272] Refactoring Blocks.prototype.loadBlocksFromPeer. Using async.waterfall to control flow. --- modules/blocks.js | 96 +++++++++++++++++++++++++++-------------------- 1 file changed, 56 insertions(+), 40 deletions(-) diff --git a/modules/blocks.js b/modules/blocks.js index f84516f4241..f4d7213f9f0 100644 --- a/modules/blocks.js +++ b/modules/blocks.js @@ -789,56 +789,72 @@ Blocks.prototype.loadBlocksFromPeer = function (peer, cb) { peer = modules.peers.accept(peer); library.logger.info('Loading blocks from: ' + peer.string); - modules.transport.getFromPeer(peer, { - method: 'GET', - api: '/blocks?lastBlockId=' + lastValidBlock.id - }, function (err, res) { - if (err || res.body.error) { - return setImmediate(cb, err, lastValidBlock); - } + function getFromPeer (seriesCb) { + modules.transport.getFromPeer(peer, { + method: 'GET', + api: '/blocks?lastBlockId=' + lastValidBlock.id + }, function (err, res) { + err = err || res.body.error; + if (err) { + return setImmediate(seriesCb, err); + } else { + return setImmediate(seriesCb, null, res.body.blocks); + } + }); + } - var report = library.schema.validate(res.body.blocks, schema.loadBlocksFromPeer); + function validateBlocks (blocks, seriesCb) { + var report = library.schema.validate(blocks, schema.loadBlocksFromPeer); if (!report) { - return setImmediate(cb, 'Received invalid blocks data', lastValidBlock); + return setImmediate(seriesCb, 'Received invalid blocks data'); + } else { + return setImmediate(seriesCb, null, blocks); } + } - var blocks = __private.readDbRows(res.body.blocks); - + function processBlocks (blocks, seriesCb) { if (blocks.length === 0) { - return setImmediate(cb, null, lastValidBlock); - } else { - async.eachSeries(blocks, function (block, cb) { - if (__private.cleanup) { - return setImmediate(cb); - } + return setImmediate(seriesCb); + } + async.eachSeries(__private.readDbRows(blocks), function (block, eachSeriesCb) { + if (__private.cleanup) { + return setImmediate(eachSeriesCb); + } else { + return processBlock(block, eachSeriesCb); + } + }, function (err) { + return setImmediate(seriesCb, err); + }); + } - self.processBlock(block, false, function (err) { - if (!err) { - lastValidBlock = block; - library.logger.info(['Block', block.id, 'loaded from:', peer.string].join(' '), 'height: ' + block.height); - } else { - var id = (block ? block.id : 'null'); + function processBlock (block, seriesCb) { + self.processBlock(block, false, function (err) { + if (!err) { + lastValidBlock = block; + library.logger.info(['Block', block.id, 'loaded from:', peer.string].join(' '), 'height: ' + block.height); + } else { + var id = (block ? block.id : 'null'); - library.logger.error(['Block', id].join(' '), err.toString()); - if (block) { library.logger.error('Block', block); } + library.logger.error(['Block', id].join(' '), err.toString()); + if (block) { library.logger.error('Block', block); } - library.logger.warn(['Block', id, 'is not valid, ban 60 min'].join(' '), peer.string); - modules.peers.state(peer.ip, peer.port, 0, 3600); - } - return cb(err); - }, true); - }, function (err) { - // Nullify large array of blocks. - // Prevents memory leak during synchronisation. - res = blocks = null; + library.logger.warn(['Block', id, 'is not valid, ban 60 min'].join(' '), peer.string); + modules.peers.state(peer.ip, peer.port, 0, 3600); + } + return seriesCb(err); + }, true); + } - if (err) { - return setImmediate(cb, 'Error loading blocks: ' + (err.message || err), lastValidBlock); - } else { - return setImmediate(cb, null, lastValidBlock); - } - }); + async.waterfall([ + getFromPeer, + validateBlocks, + processBlocks + ], function (err) { + if (err) { + return setImmediate(cb, 'Error loading blocks: ' + (err.message || err), lastValidBlock); + } else { + return setImmediate(cb, null, lastValidBlock); } }); }; From 36572d9540f4895fe00690587683c5fe1818620a Mon Sep 17 00:00:00 2001 From: Oliver Beddows Date: Tue, 25 Oct 2016 14:06:58 +0200 Subject: [PATCH 102/272] Adding / changing comments. --- modules/blocks.js | 5 +++-- modules/peers.js | 4 ++-- 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/modules/blocks.js b/modules/blocks.js index f4d7213f9f0..44d31accfc1 100644 --- a/modules/blocks.js +++ b/modules/blocks.js @@ -1067,6 +1067,7 @@ Blocks.prototype.processBlock = function (block, broadcast, cb, saveBlock) { // DATABASE: Read only to mem_accounts to extract active delegate list modules.delegates.validateBlockSlot(block, function (err) { if (err) { + // Fork: Delegate does not match calculated slot. modules.delegates.fork(block, 3); return setImmediate(seriesCb, err); } else { @@ -1254,11 +1255,11 @@ Blocks.prototype.onReceiveBlock = function (block) { self.lastReceipt(new Date()); self.processBlock(block, true, cb, true); } else if (block.previousBlock !== __private.lastBlock.id && __private.lastBlock.height + 1 === block.height) { - // Fork: Same height but different previous block id + // Fork: Same height but different previous block id. modules.delegates.fork(block, 1); return setImmediate(cb, 'Fork'); } else if (block.previousBlock === __private.lastBlock.previousBlock && block.height === __private.lastBlock.height && block.id !== __private.lastBlock.id) { - // Fork: Same height and previous block id, but different block id + // Fork: Same height and previous block id, but different block id. modules.delegates.fork(block, 5); return setImmediate(cb, 'Fork'); } else { diff --git a/modules/peers.js b/modules/peers.js index 72aab648e68..c3fef56d2fe 100644 --- a/modules/peers.js +++ b/modules/peers.js @@ -283,11 +283,11 @@ Peers.prototype.list = function (options, cb) { } async.waterfall([ - // Broadhash + // Matched broadhash function (waterCb) { return randomList(options, [], waterCb); }, - // Unmatched + // Unmatched broadhash function (peers, waterCb) { if (peers.length < options.limit && (options.broadhash || options.height)) { nextAttempt(peers); From d67ac3f5c35cff106d58783ea68378cdcf440276 Mon Sep 17 00:00:00 2001 From: Oliver Beddows Date: Tue, 25 Oct 2016 18:28:47 +0200 Subject: [PATCH 103/272] Revising Peers.prototype.list. - Ordering peers by descending height after randomization. - Deleting broadhash option on fallback. - Fixing decrement on remaining peers. - Removing height option. --- modules/peers.js | 28 ++++++++-------------------- sql/peers.js | 3 +-- 2 files changed, 9 insertions(+), 22 deletions(-) diff --git a/modules/peers.js b/modules/peers.js index c3fef56d2fe..8dc9660826f 100644 --- a/modules/peers.js +++ b/modules/peers.js @@ -249,7 +249,6 @@ Peers.prototype.acceptable = function (peers) { Peers.prototype.list = function (options, cb) { options.limit = options.limit || 100; options.broadhash = options.broadhash || modules.system.getBroadhash(); - options.height = options.height || modules.system.getHeight(); options.attempts = ['matched broadhash', 'unmatched broadhash', 'legacy']; options.attempt = 0; @@ -257,12 +256,10 @@ Peers.prototype.list = function (options, cb) { delete options.broadhash; } - if (!options.height) { - delete options.height; - } - function randomList (options, peers, cb) { library.db.query(sql.randomList(options), options).then(function (rows) { + options.limit -= rows.length; + if (options.attempt === 0 && rows.length > 0) { options.matched = rows.length; } library.logger.debug(['Listing', rows.length, options.attempts[options.attempt], 'peers'].join(' ')); return setImmediate(cb, null, self.acceptable(peers.concat(rows))); }).catch(function (err) { @@ -271,17 +268,6 @@ Peers.prototype.list = function (options, cb) { }); } - function nextAttempt (peers) { - options.limit = 100; - options.limit = (options.limit - peers.length); - options.attempt += 1; - - if (options.attempt === 2) { - delete options.broadhash; - delete options.height; - } - } - async.waterfall([ // Matched broadhash function (waterCb) { @@ -289,8 +275,8 @@ Peers.prototype.list = function (options, cb) { }, // Unmatched broadhash function (peers, waterCb) { - if (peers.length < options.limit && (options.broadhash || options.height)) { - nextAttempt(peers); + if (options.limit > 0) { + options.attempt += 1; return randomList(options, peers, waterCb); } else { @@ -299,8 +285,10 @@ Peers.prototype.list = function (options, cb) { }, // Fallback function (peers, waterCb) { - if (peers.length < options.limit) { - nextAttempt(peers); + delete options.broadhash; + + if (options.limit > 0) { + options.attempt += 1; return randomList(options, peers, waterCb); } else { diff --git a/sql/peers.js b/sql/peers.js index bfd0a5546fe..d707cf83831 100644 --- a/sql/peers.js +++ b/sql/peers.js @@ -22,8 +22,7 @@ var PeersSql = { (params.dappid ? 'INNER JOIN peers_dapp AS pd ON p."id" = pd."peerId" AND pd."dappid" = ${dappid}' : ''), 'WHERE p."state" > 0', (params.broadhash ? 'AND "broadhash" ' + (params.attempt === 0 ? '=' : '!=') + ' DECODE(${broadhash}, \'hex\')' : 'AND "broadhash" IS NULL'), - (params.height ? params.attempt === 0 ? 'AND "height" = ${height}' : 'OR "height" > ${height}' : 'OR "height" IS NULL'), - 'ORDER BY RANDOM() LIMIT ${limit}' + 'ORDER BY RANDOM(), "height" DESC LIMIT ${limit}' ].filter(Boolean).join(' '); }, From 1d27ec83d1ce7bdff3f07abc5675563183965ce9 Mon Sep 17 00:00:00 2001 From: Oliver Beddows Date: Wed, 26 Oct 2016 18:08:33 +0200 Subject: [PATCH 104/272] Do not randomise port in node.addPeers. --- test/node.js | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/test/node.js b/test/node.js index 4339919d1b7..09142bfc955 100644 --- a/test/node.js +++ b/test/node.js @@ -177,9 +177,8 @@ node.waitForNewBlock = function (height, cb) { // Adds peers to local node node.addPeers = function (numOfPeers, cb) { var operatingSystems = ['win32','win64','ubuntu','debian', 'centos']; - var ports = [4000, 5000, 7000, 8000]; - - var os, version, port; + var port = 4000; + var os, version; var i = 0; node.async.whilst(function () { @@ -187,7 +186,6 @@ node.addPeers = function (numOfPeers, cb) { }, function (next) { os = operatingSystems[node.randomizeSelection(operatingSystems.length)]; version = node.config.version; - port = ports[node.randomizeSelection(ports.length)]; var request = node.popsicle.get({ url: node.baseUrl + '/peer/height', From 9d669be1a6c9d104000de2c4c2b4dca4aeb4004f Mon Sep 17 00:00:00 2001 From: Oliver Beddows Date: Wed, 26 Oct 2016 18:08:45 +0200 Subject: [PATCH 105/272] Sorting properties. --- test/node.js | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/test/node.js b/test/node.js index 09142bfc955..96cd0fdccfb 100644 --- a/test/node.js +++ b/test/node.js @@ -190,12 +190,12 @@ node.addPeers = function (numOfPeers, cb) { var request = node.popsicle.get({ url: node.baseUrl + '/peer/height', headers: { - version: version, - port: port, + broadhash: node.config.nethash, + height: 1, nethash: node.config.nethash, os: os, - broadhash: node.config.nethash, - height: 1 + port: port, + version: version } }); From b3af52fd591a10dc0b69bda1d16fb154ef0c88f8 Mon Sep 17 00:00:00 2001 From: Oliver Beddows Date: Wed, 26 Oct 2016 18:15:01 +0200 Subject: [PATCH 106/272] Waiting for peer to be swept to db. --- test/node.js | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/test/node.js b/test/node.js index 96cd0fdccfb..fbca5f87bc8 100644 --- a/test/node.js +++ b/test/node.js @@ -214,7 +214,10 @@ node.addPeers = function (numOfPeers, cb) { return next(err); }); }, function (err) { - return cb(err, {os: os, version: version, port: port}); + // Wait for peer to be swept to db + setTimeout(function () { + return cb(err, {os: os, version: version, port: port}); + }, 3000); }); }; From 19fc6d49930de1a1dc82a5f38580c2162b3c70c9 Mon Sep 17 00:00:00 2001 From: Oliver Beddows Date: Thu, 27 Oct 2016 11:56:59 +0200 Subject: [PATCH 107/272] Removing forging timeout functionality. --- config.json | 1 - helpers/constants.js | 1 - modules/delegates.js | 47 -------------------------------------------- modules/transport.js | 4 ---- test/config.json | 1 - 5 files changed, 54 deletions(-) diff --git a/config.json b/config.json index 5483a60767b..5fa4b7a5b74 100644 --- a/config.json +++ b/config.json @@ -68,7 +68,6 @@ } }, "forging": { - "force": false, "secret": [], "access": { "whiteList": [ diff --git a/helpers/constants.js b/helpers/constants.js index 9c09b93dcf4..8e5712c1bd9 100644 --- a/helpers/constants.js +++ b/helpers/constants.js @@ -18,7 +18,6 @@ module.exports = { feeStart: 1, feeStartVolume: 10000 * 100000000, fixedPoint : Math.pow(10, 8), - forgingTimeOut: 500, // 50 blocks maxAddressesLength: 208 * 128, maxAmount: 100000000, maxClientConnections: 100, diff --git a/modules/delegates.js b/modules/delegates.js index faf0a3d455a..1001327728b 100644 --- a/modules/delegates.js +++ b/modules/delegates.js @@ -22,7 +22,6 @@ var modules, library, self, __private = {}, shared = {}; __private.assetTypes = {}; __private.loaded = false; -__private.forging = false; __private.blockReward = new BlockReward(); __private.keypairs = {}; @@ -230,11 +229,6 @@ __private.forge = function (cb) { return __private.loadDelegates(cb); } - if (!__private.forging) { - library.logger.debug('Forging disabled'); - return setImmediate(cb); - } - // When client is not loaded, is syncing or round is ticking // Do not try to forge new blocks as client is not ready if (!__private.loaded || modules.loader.syncing() || !modules.rounds.loaded() || modules.rounds.ticking()) { @@ -498,8 +492,6 @@ Delegates.prototype.fork = function (block, cause) { cause: cause }); - self.disableForging('fork'); - var fork = { delegatePublicKey: block.generatorPublicKey, blockTimestamp: block.timestamp, @@ -555,7 +547,6 @@ Delegates.prototype.onBlockchainReady = function () { library.logger.error('Failed to load delegates', err); } - __private.toggleForgingOnReceipt(); __private.forge(function () { setTimeout(nextForge, 1000); }); @@ -567,45 +558,7 @@ Delegates.prototype.cleanup = function (cb) { return setImmediate(cb); }; -Delegates.prototype.enableForging = function () { - if (!__private.forging) { - library.logger.debug('Enabling forging'); - __private.forging = true; - } - - return __private.forging; -}; - -Delegates.prototype.disableForging = function (reason) { - if (__private.forging) { - library.logger.debug('Disabling forging due to:', reason); - __private.forging = false; - } - - return __private.forging; -}; - // Private -__private.toggleForgingOnReceipt = function () { - var lastReceipt = modules.blocks.lastReceipt(); - - // Enforce local forging if configured - if (!lastReceipt && library.config.forging.force) { - lastReceipt = modules.blocks.lastReceipt(new Date()); - } - - if (lastReceipt) { - var timeOut = Number(constants.forgingTimeOut); - - library.logger.debug('Last block received: ' + lastReceipt.secondsAgo + ' seconds ago'); - - if (lastReceipt.secondsAgo > timeOut) { - return self.disableForging('timeout'); - } else { - return self.enableForging(); - } - } -}; // Shared shared.getDelegate = function (req, cb) { diff --git a/modules/transport.js b/modules/transport.js index 1f16063b3d6..7c3aa5d17de 100644 --- a/modules/transport.js +++ b/modules/transport.js @@ -68,10 +68,6 @@ __private.attachApi = function () { return res.status(200).send({success: false, message: 'Request is made on the wrong network', expected: modules.system.getNethash(), received: headers.nethash}); } - if (!modules.blocks.lastReceipt()) { - modules.delegates.enableForging(); - } - if (req.body && req.body.dappid) { req.peer.dappid = req.body.dappid; } diff --git a/test/config.json b/test/config.json index 27903e71ef5..8261139a144 100644 --- a/test/config.json +++ b/test/config.json @@ -46,7 +46,6 @@ } }, "forging": { - "force": true, "secret": [ "robust swift grocery peasant forget share enable convince deputy road keep cheap", "weapon van trap again sustain write useless great pottery urge month nominee", From bf499d356a8e6ffdd6816af6ac9c515aa16f261b Mon Sep 17 00:00:00 2001 From: Oliver Beddows Date: Thu, 27 Oct 2016 17:15:38 +0200 Subject: [PATCH 108/272] Yielding Peers.prototype.list broadhash efficiency. --- modules/peers.js | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/modules/peers.js b/modules/peers.js index 8dc9660826f..7a90e37d670 100644 --- a/modules/peers.js +++ b/modules/peers.js @@ -251,6 +251,7 @@ Peers.prototype.list = function (options, cb) { options.broadhash = options.broadhash || modules.system.getBroadhash(); options.attempts = ['matched broadhash', 'unmatched broadhash', 'legacy']; options.attempt = 0; + options.matched = 0; if (!options.broadhash) { delete options.broadhash; @@ -296,8 +297,12 @@ Peers.prototype.list = function (options, cb) { } } ], function (err, peers) { + var efficiency = Math.round(options.matched / peers.length * 100 * 1e2) / 1e2; + efficiency = isNaN(efficiency) ? 0 : efficiency; + + library.logger.debug(['Listing efficiency', efficiency, '%'].join(' ')); library.logger.debug(['Listing', peers.length, 'total peers'].join(' ')); - return setImmediate(cb, err, peers); + return setImmediate(cb, err, peers, efficiency); }); }; From 096bc98ca36fa68a2bfc29b706ffab8f8d512a1a Mon Sep 17 00:00:00 2001 From: Oliver Beddows Date: Thu, 27 Oct 2016 18:46:27 +0200 Subject: [PATCH 109/272] Registering new "version" schema format. --- helpers/z_schema.js | 4 ++++ schema/loader.js | 3 ++- schema/peers.js | 4 ++-- schema/transport.js | 2 +- 4 files changed, 9 insertions(+), 4 deletions(-) diff --git a/helpers/z_schema.js b/helpers/z_schema.js index 502bc6f8579..00053881d97 100644 --- a/helpers/z_schema.js +++ b/helpers/z_schema.js @@ -77,6 +77,10 @@ z_schema.registerFormat('ip', function (str) { return ip.isV4Format(str); }); +z_schema.registerFormat('version', function (str) { + return /^([0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3})([a-z]{1})?$/g.test(str); +}); + // var registeredFormats = z_schema.getRegisteredFormats(); // console.log(registeredFormats); diff --git a/schema/loader.js b/schema/loader.js index 7655631fec9..cfa1edad9d4 100644 --- a/schema/loader.js +++ b/schema/loader.js @@ -57,7 +57,8 @@ module.exports = { type: 'string' }, version: { - type: 'string' + type: 'string', + format: 'version' }, broadhash: { type: 'string', diff --git a/schema/peers.js b/schema/peers.js index f8f20e42cfb..a0571071464 100644 --- a/schema/peers.js +++ b/schema/peers.js @@ -38,7 +38,7 @@ module.exports = { }, version: { type: 'string', - maxLength: 11 + format: 'version' }, broadhash: { type: 'string', @@ -76,7 +76,7 @@ module.exports = { }, version: { type: 'string', - maxLength: 11 + format: 'version' }, broadhash: { type: 'string', diff --git a/schema/transport.js b/schema/transport.js index 2ff9ae3bcbd..2adabd848ff 100644 --- a/schema/transport.js +++ b/schema/transport.js @@ -20,7 +20,7 @@ module.exports = { }, version: { type: 'string', - maxLength: 11 + format: 'version' }, nethash: { type: 'string', From f425cc20ed5b8f75c7dae1129fd1cdd10aed06b1 Mon Sep 17 00:00:00 2001 From: Oliver Beddows Date: Thu, 27 Oct 2016 18:50:52 +0200 Subject: [PATCH 110/272] Describing GET /api/peers?version=. --- test/api/peers.js | 49 ++++++++++++++++++++++++++++++++++++++++++++--- 1 file changed, 46 insertions(+), 3 deletions(-) diff --git a/test/api/peers.js b/test/api/peers.js index a0a6198f308..6b4031746e5 100644 --- a/test/api/peers.js +++ b/test/api/peers.js @@ -159,7 +159,7 @@ describe('GET /api/peers', function () { }); }); - it('using version with length == 11 characters should be ok', function (done) { + it('using version == "999.999.999" characters should be ok', function (done) { var version = '999.999.999'; var params = 'version=' + version; @@ -169,13 +169,56 @@ describe('GET /api/peers', function () { }); }); - it('using version with length > 11 characters should fail', function (done) { + it('using version == "9999.999.999" characters should fail', function (done) { var version = '9999.999.999'; var params = 'version=' + version; node.get('/api/peers?' + params, function (err, res) { node.expect(res.body).to.have.property('success').to.be.not.ok; - node.expect(res.body).to.have.property('error').to.equal('String is too long (12 chars), maximum 11'); + node.expect(res.body).to.have.property('error').to.equal('Object didn\'t pass validation for format version: 9999.999.999'); + done(); + }); + }); + + it('using version == "999.9999.999" characters should fail', function (done) { + var version = '999.9999.999'; + var params = 'version=' + version; + + node.get('/api/peers?' + params, function (err, res) { + node.expect(res.body).to.have.property('success').to.be.not.ok; + node.expect(res.body).to.have.property('error').to.equal('Object didn\'t pass validation for format version: 999.9999.999'); + done(); + }); + }); + + it('using version == "999.999.9999" characters should fail', function (done) { + var version = '999.999.9999'; + var params = 'version=' + version; + + node.get('/api/peers?' + params, function (err, res) { + node.expect(res.body).to.have.property('success').to.be.not.ok; + node.expect(res.body).to.have.property('error').to.equal('Object didn\'t pass validation for format version: 999.999.9999'); + done(); + }); + }); + + it('using version == "999.999.999a" characters should be ok', function (done) { + var version = '999.999.999a'; + var params = 'version=' + version; + + node.get('/api/peers?' + params, function (err, res) { + node.expect(res.body).to.have.property('success').to.be.ok; + done(); + }); + }); + + it('using version == "999.999.999ab" characters should fail', function (done) { + var version = '999.999.999ab'; + var params = 'version=' + version; + + node.get('/api/peers?' + params, function (err, res) { + node.expect(res.body).to.have.property('success').to.be.not.ok; + node.expect(res.body).to.have.property('error').to.equal('Object didn\'t pass validation for format version: 999.999.999ab'); done(); }); }); From e00f3c0256452c12cc478e31e1aab16fe426a24d Mon Sep 17 00:00:00 2001 From: Oliver Beddows Date: Thu, 27 Oct 2016 18:57:23 +0200 Subject: [PATCH 111/272] Registering new "os" schema format. --- helpers/z_schema.js | 4 ++++ schema/loader.js | 4 +++- schema/peers.js | 2 ++ schema/transport.js | 1 + 4 files changed, 10 insertions(+), 1 deletion(-) diff --git a/helpers/z_schema.js b/helpers/z_schema.js index 00053881d97..bf041240e90 100644 --- a/helpers/z_schema.js +++ b/helpers/z_schema.js @@ -77,6 +77,10 @@ z_schema.registerFormat('ip', function (str) { return ip.isV4Format(str); }); +z_schema.registerFormat('os', function (str) { + return /^[a-z0-9-_.]+$/ig.test(str); +}); + z_schema.registerFormat('version', function (str) { return /^([0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3})([a-z]{1})?$/g.test(str); }); diff --git a/schema/loader.js b/schema/loader.js index cfa1edad9d4..89302ceaec2 100644 --- a/schema/loader.js +++ b/schema/loader.js @@ -54,7 +54,9 @@ module.exports = { maximum: 3 }, os: { - type: 'string' + type: 'string', + format: 'os', + maxLength: 64 }, version: { type: 'string', diff --git a/schema/peers.js b/schema/peers.js index a0571071464..003e9eb2db7 100644 --- a/schema/peers.js +++ b/schema/peers.js @@ -34,6 +34,7 @@ module.exports = { }, os: { type: 'string', + format: 'os', maxLength: 64 }, version: { @@ -72,6 +73,7 @@ module.exports = { }, os: { type: 'string', + format: 'os', maxLength: 64 }, version: { diff --git a/schema/transport.js b/schema/transport.js index 2adabd848ff..52da99c55ce 100644 --- a/schema/transport.js +++ b/schema/transport.js @@ -16,6 +16,7 @@ module.exports = { }, os: { type: 'string', + format: 'os', maxLength: 64 }, version: { From c905f255f89ed9146d2658495d2430a81fa188f4 Mon Sep 17 00:00:00 2001 From: Oliver Beddows Date: Thu, 27 Oct 2016 19:27:19 +0200 Subject: [PATCH 112/272] Describing GET /api/peers?os=. --- test/api/peers.js | 71 +++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 71 insertions(+) diff --git a/test/api/peers.js b/test/api/peers.js index 6b4031746e5..7109e25ae64 100644 --- a/test/api/peers.js +++ b/test/api/peers.js @@ -159,6 +159,77 @@ describe('GET /api/peers', function () { }); }); + it('using os == "freebsd10" should be ok', function (done) { + var os = 'freebsd10'; + var params = 'os=' + os; + + node.get('/api/peers?' + params, function (err, res) { + node.expect(res.body).to.have.property('success').to.be.ok; + done(); + }); + }); + + it('using os == "freebsd10.3" should be ok', function (done) { + var os = 'freebsd10.3'; + var params = 'os=' + os; + + node.get('/api/peers?' + params, function (err, res) { + node.expect(res.body).to.have.property('success').to.be.ok; + done(); + }); + }); + + it('using os == "freebsd10.3-" should be ok', function (done) { + var os = 'freebsd10.3-'; + var params = 'os=' + os; + + node.get('/api/peers?' + params, function (err, res) { + node.expect(res.body).to.have.property('success').to.be.ok; + done(); + }); + }); + + it('using os == "freebsd10.3_" should be ok', function (done) { + var os = 'freebsd10.3_'; + var params = 'os=' + os; + + node.get('/api/peers?' + params, function (err, res) { + node.expect(res.body).to.have.property('success').to.be.ok; + done(); + }); + }); + + it('using os == "freebsd10.3_RELEASE" should be ok', function (done) { + var os = 'freebsd10.3_RELEASE'; + var params = 'os=' + os; + + node.get('/api/peers?' + params, function (err, res) { + node.expect(res.body).to.have.property('success').to.be.ok; + done(); + }); + }); + + it('using os == "freebsd10.3_RELEASE-p7" should be ok', function (done) { + var os = 'freebsd10.3_RELEASE-p7'; + var params = 'os=' + os; + + node.get('/api/peers?' + params, function (err, res) { + node.expect(res.body).to.have.property('success').to.be.ok; + done(); + }); + }); + + it('using os == "freebsd10.3_RELEASE-p7-@" should fail', function (done) { + var os = 'freebsd10.3_RELEASE-p7-@'; + var params = 'os=' + os; + + node.get('/api/peers?' + params, function (err, res) { + node.expect(res.body).to.have.property('success').to.be.not.ok; + node.expect(res.body).to.have.property('error').to.equal('Object didn\'t pass validation for format os: freebsd10.3_RELEASE-p7-@'); + done(); + }); + }); + it('using version == "999.999.999" characters should be ok', function (done) { var version = '999.999.999'; var params = 'version=' + version; From 6939a48930399f128c649d6bfa186946af934ecc Mon Sep 17 00:00:00 2001 From: Oliver Beddows Date: Thu, 27 Oct 2016 22:02:15 +0200 Subject: [PATCH 113/272] Adding Blocks.prototype.getCommonBlock schema validations. --- modules/blocks.js | 9 +++++++++ schema/blocks.js | 18 ++++++++++++++++++ 2 files changed, 27 insertions(+) diff --git a/modules/blocks.js b/modules/blocks.js index 44d31accfc1..938f1e01b8d 100644 --- a/modules/blocks.js +++ b/modules/blocks.js @@ -762,6 +762,15 @@ Blocks.prototype.getCommonBlock = function (peer, height, cb) { } }); }, + function (res, waterCb) { + library.schema.validate(res.body.common, schema.getCommonBlock, function (err) { + if (err) { + return setImmediate(waterCb, err[0].message); + } else { + return setImmediate(waterCb, null, res); + } + }); + }, function (res, waterCb) { library.db.query(sql.getCommonBlock(res.body.common.previousBlock), { id: res.body.common.id, diff --git a/schema/blocks.js b/schema/blocks.js index 5aa8c1bb263..bb0d666bb61 100644 --- a/schema/blocks.js +++ b/schema/blocks.js @@ -60,5 +60,23 @@ module.exports = { minimum: 1 } } + }, + getCommonBlock: { + id: 'blocks.getCommonBlock', + type: 'object', + properties: { + id: { + type: 'string', + minLength: 1 + }, + previousBlock: { + type: 'string' + }, + height: { + type: 'integer', + minimum: 1 + } + }, + required: ['id', 'previousBlock', 'height'] } }; From 69af166259a3356c4e628e51397de399b42e44b2 Mon Sep 17 00:00:00 2001 From: Oliver Beddows Date: Thu, 27 Oct 2016 22:06:05 +0200 Subject: [PATCH 114/272] Registering new "id" schema format. --- helpers/z_schema.js | 4 ++++ schema/blocks.js | 18 ++++++++++++++---- schema/dapps.js | 24 +++++++++++++++++++----- schema/multisignatures.js | 5 ++++- schema/transactions.js | 13 ++++++++++--- schema/transport.js | 10 ++++++++-- 6 files changed, 59 insertions(+), 15 deletions(-) diff --git a/helpers/z_schema.js b/helpers/z_schema.js index bf041240e90..7ecfea49b4b 100644 --- a/helpers/z_schema.js +++ b/helpers/z_schema.js @@ -3,6 +3,10 @@ var ip = require('ip'); var z_schema = require('z-schema'); +z_schema.registerFormat('id', function (str) { + return /^[0-9]+$/g.test(str); +}); + z_schema.registerFormat('hex', function (str) { try { new Buffer(str, 'hex'); diff --git a/schema/blocks.js b/schema/blocks.js index bb0d666bb61..99c3e8f0b71 100644 --- a/schema/blocks.js +++ b/schema/blocks.js @@ -13,7 +13,9 @@ module.exports = { properties: { id: { type: 'string', - minLength: 1 + format: 'id', + minLength: 1, + maxLength: 20 } }, required: ['id'] @@ -53,7 +55,10 @@ module.exports = { minimum: 0 }, previousBlock: { - type: 'string' + type: 'string', + format: 'id', + minLength: 1, + maxLength: 20 }, height: { type: 'integer', @@ -67,10 +72,15 @@ module.exports = { properties: { id: { type: 'string', - minLength: 1 + format: 'id', + minLength: 1, + maxLength: 20 }, previousBlock: { - type: 'string' + type: 'string', + format: 'id', + minLength: 1, + maxLength: 20 }, height: { type: 'integer', diff --git a/schema/dapps.js b/schema/dapps.js index 83af776ba26..5e4dd30aa2f 100644 --- a/schema/dapps.js +++ b/schema/dapps.js @@ -64,7 +64,9 @@ module.exports = { properties: { id: { type: 'string', - minLength: 1 + format: 'id', + minLength: 1, + maxLength: 20 } }, required: ['id'] @@ -75,6 +77,7 @@ module.exports = { properties: { id: { type: 'string', + format: 'id', minLength: 1, maxLength: 20 }, @@ -126,7 +129,9 @@ module.exports = { }, id: { type: 'string', - minLength: 1 + format: 'id', + minLength: 1, + maxLength: 20 }, master: { type: 'string', @@ -160,6 +165,7 @@ module.exports = { }, dappId: { type: 'string', + format: 'id', minLength: 1, maxLength: 20 }, @@ -196,11 +202,13 @@ module.exports = { }, dappId: { type: 'string', + format: 'id', minLength: 1, maxLength: 20 }, transactionId: { type: 'string', + format: 'id', minLength: 1, maxLength: 20 }, @@ -238,7 +246,9 @@ module.exports = { properties: { id: { type: 'string', - minLength: 1 + format: 'id', + minLength: 1, + maxLength: 20 }, master: { type: 'string', @@ -253,7 +263,9 @@ module.exports = { properties: { id: { type: 'string', - minLength: 1 + format: 'id', + minLength: 1, + maxLength: 20 }, master: { type: 'string', @@ -268,7 +280,9 @@ module.exports = { properties: { id: { type: 'string', - minLength: 1 + format: 'id', + minLength: 1, + maxLength: 20 }, master: { type: 'string', diff --git a/schema/multisignatures.js b/schema/multisignatures.js index 98581b58257..cccc0cb00da 100644 --- a/schema/multisignatures.js +++ b/schema/multisignatures.js @@ -42,7 +42,10 @@ module.exports = { format: 'publicKey' }, transactionId: { - type: 'string' + type: 'string', + format: 'id', + minLength: 1, + maxLength: 20 } }, required: ['transactionId', 'secret'] diff --git a/schema/transactions.js b/schema/transactions.js index a826c60190d..85003414493 100644 --- a/schema/transactions.js +++ b/schema/transactions.js @@ -8,7 +8,10 @@ module.exports = { type: 'object', properties: { blockId: { - type: 'string' + type: 'string', + format: 'id', + minLength: 1, + maxLength: 20 }, type: { type: 'integer', @@ -62,7 +65,9 @@ module.exports = { properties: { id: { type: 'string', - minLength: 1 + format: 'id', + minLength: 1, + maxLength: 20 } }, required: ['id'] @@ -73,7 +78,9 @@ module.exports = { properties: { id: { type: 'string', - minLength: 1 + format: 'id', + minLength: 1, + maxLength: 20 } }, required: ['id'] diff --git a/schema/transport.js b/schema/transport.js index 52da99c55ce..4f27f52dd68 100644 --- a/schema/transport.js +++ b/schema/transport.js @@ -54,7 +54,10 @@ module.exports = { type: 'object', properties: { lastBlockId: { - type: 'string' + type: 'string', + format: 'id', + minLength: 1, + maxLength: 20 } }, }, @@ -66,7 +69,10 @@ module.exports = { type: 'object', properties: { transaction: { - type: 'string' + type: 'string', + format: 'id', + minLength: 1, + maxLength: 20 }, signature: { type: 'string', From 19814280fe3692b41f0bf387660440b1f7d5a4ec Mon Sep 17 00:00:00 2001 From: Oliver Beddows Date: Thu, 27 Oct 2016 22:15:58 +0200 Subject: [PATCH 115/272] Fixing regular expression range. --- modules/dapps.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/modules/dapps.js b/modules/dapps.js index af92db250c0..a8af787db99 100644 --- a/modules/dapps.js +++ b/modules/dapps.js @@ -672,7 +672,7 @@ __private.getInstalledIds = function (cb) { if (err) { return setImmediate(cb, err); } else { - var regExp = new RegExp(/[0-9]{18,20}/); + var regExp = new RegExp(/[0-9]{1,20}/); ids = _.filter(ids, function (f) { return regExp.test(f.toString()); From 98505baa7a41910e6ade8c5a8a39cd7b2acd3a78 Mon Sep 17 00:00:00 2001 From: Oliver Beddows Date: Thu, 27 Oct 2016 23:29:02 +0200 Subject: [PATCH 116/272] Changing error message. --- modules/multisignatures.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/modules/multisignatures.js b/modules/multisignatures.js index bd978f4ad13..628fde07c0e 100644 --- a/modules/multisignatures.js +++ b/modules/multisignatures.js @@ -230,7 +230,7 @@ Multisignatures.prototype.processSignature = function (tx, cb) { } if (!transaction) { - return setImmediate(cb, 'Missing transaction'); + return setImmediate(cb, 'Transaction not found'); } if (transaction.type === transactionTypes.MULTI) { From f6842ae8e525aff913a1b6edc42b164e9e3be75e Mon Sep 17 00:00:00 2001 From: Oliver Beddows Date: Thu, 27 Oct 2016 23:37:05 +0200 Subject: [PATCH 117/272] Registering new "address" schema format. --- helpers/z_schema.js | 4 ++++ schema/accounts.js | 8 ++++---- schema/dapps.js | 3 +-- schema/transactions.js | 14 +++++++++----- 4 files changed, 18 insertions(+), 11 deletions(-) diff --git a/helpers/z_schema.js b/helpers/z_schema.js index 7ecfea49b4b..6f8d1e08d56 100644 --- a/helpers/z_schema.js +++ b/helpers/z_schema.js @@ -7,6 +7,10 @@ z_schema.registerFormat('id', function (str) { return /^[0-9]+$/g.test(str); }); +z_schema.registerFormat('address', function (str) { + return /^[0-9]{1,21}[L|l]$/g.test(str); +}); + z_schema.registerFormat('hex', function (str) { try { new Buffer(str, 'hex'); diff --git a/schema/accounts.js b/schema/accounts.js index 0bd3f091233..bfbf88745eb 100644 --- a/schema/accounts.js +++ b/schema/accounts.js @@ -19,7 +19,7 @@ module.exports = { properties: { address: { type: 'string', - minLength: 1 + format: 'address' } }, required: ['address'] @@ -30,7 +30,7 @@ module.exports = { properties: { address: { type: 'string', - minLength: 1 + format: 'address' } }, required: ['address'] @@ -52,7 +52,7 @@ module.exports = { properties: { address: { type: 'string', - minLength: 1 + format: 'address' } }, required: ['address'] @@ -81,7 +81,7 @@ module.exports = { properties: { address: { type: 'string', - minLength: 1 + format: 'address' } }, required: ['address'] diff --git a/schema/dapps.js b/schema/dapps.js index 5e4dd30aa2f..2f1e48dc979 100644 --- a/schema/dapps.js +++ b/schema/dapps.js @@ -192,8 +192,7 @@ module.exports = { }, recipientId: { type: 'string', - minLength: 2, - maxLength: 22 + format: 'address' }, secondSecret: { type: 'string', diff --git a/schema/transactions.js b/schema/transactions.js index 85003414493..9747e2805c2 100644 --- a/schema/transactions.js +++ b/schema/transactions.js @@ -19,7 +19,8 @@ module.exports = { maximum: 10 }, senderId: { - type: 'string' + type: 'string', + format: 'address' }, senderPublicKey: { type: 'string', @@ -30,10 +31,12 @@ module.exports = { format: 'publicKey' }, ownerAddress: { - type: 'string' + type: 'string', + format: 'address' }, recipientId: { - type: 'string' + type: 'string', + format: 'address' }, amount: { type: 'integer', @@ -94,7 +97,8 @@ module.exports = { format: 'publicKey' }, address: { - type: 'string' + type: 'string', + format: 'address' } } }, @@ -114,7 +118,7 @@ module.exports = { }, recipientId: { type: 'string', - minLength: 1 + format: 'address' }, publicKey: { type: 'string', From 030ee23c83541dc77ef75cb953eb422d8a84de3f Mon Sep 17 00:00:00 2001 From: Oliver Beddows Date: Thu, 27 Oct 2016 23:37:25 +0200 Subject: [PATCH 118/272] Normalising schema for secrets. --- schema/accounts.js | 9 ++++++--- schema/signatures.js | 6 ++++-- 2 files changed, 10 insertions(+), 5 deletions(-) diff --git a/schema/accounts.js b/schema/accounts.js index bfbf88745eb..39cf51efe48 100644 --- a/schema/accounts.js +++ b/schema/accounts.js @@ -41,7 +41,8 @@ module.exports = { properties: { secret: { type: 'string', - minLength: 1 + minLength: 1, + maxLength: 100 } }, required: ['secret'] @@ -63,7 +64,8 @@ module.exports = { properties: { secret: { type: 'string', - minLength: 1 + minLength: 1, + maxLength: 100 }, publicKey: { type: 'string', @@ -71,7 +73,8 @@ module.exports = { }, secondSecret: { type: 'string', - minLength: 1 + minLength: 1, + maxLength: 100 } } }, diff --git a/schema/signatures.js b/schema/signatures.js index 80738f65702..2f8c8b62f6f 100644 --- a/schema/signatures.js +++ b/schema/signatures.js @@ -7,11 +7,13 @@ module.exports = { properties: { secret: { type: 'string', - minLength: 1 + minLength: 1, + maxLength: 100 }, secondSecret: { type: 'string', - minLength: 1 + minLength: 1, + maxLength: 100 }, publicKey: { type: 'string', From 1832953ab51fb806ae227421e00638735a5f6846 Mon Sep 17 00:00:00 2001 From: Oliver Beddows Date: Thu, 27 Oct 2016 23:40:13 +0200 Subject: [PATCH 119/272] Adding minLength / maxLength to username. --- schema/delegates.js | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/schema/delegates.js b/schema/delegates.js index 36dd2aaaf97..51c0554c969 100644 --- a/schema/delegates.js +++ b/schema/delegates.js @@ -54,7 +54,9 @@ module.exports = { type: 'string' }, username: { - type: 'string' + type: 'string', + minLength: 1, + maxLength: 20 } } }, @@ -134,7 +136,9 @@ module.exports = { maxLength: 100 }, username: { - type: 'string' + type: 'string', + minLength: 1, + maxLength: 20 } }, required: ['secret'] From c3107a96f642e07decbde53e2365773df8b53f15 Mon Sep 17 00:00:00 2001 From: Oliver Beddows Date: Thu, 27 Oct 2016 23:44:22 +0200 Subject: [PATCH 120/272] Registering new "username" schema format. --- helpers/z_schema.js | 4 ++++ schema/delegates.js | 2 ++ 2 files changed, 6 insertions(+) diff --git a/helpers/z_schema.js b/helpers/z_schema.js index 6f8d1e08d56..ed00803b3b6 100644 --- a/helpers/z_schema.js +++ b/helpers/z_schema.js @@ -11,6 +11,10 @@ z_schema.registerFormat('address', function (str) { return /^[0-9]{1,21}[L|l]$/g.test(str); }); +z_schema.registerFormat('username', function (str) { + return /^[a-z0-9!@$&_.]+$/g.test(str); +}); + z_schema.registerFormat('hex', function (str) { try { new Buffer(str, 'hex'); diff --git a/schema/delegates.js b/schema/delegates.js index 51c0554c969..1b6b681f0c9 100644 --- a/schema/delegates.js +++ b/schema/delegates.js @@ -55,6 +55,7 @@ module.exports = { }, username: { type: 'string', + format: 'username', minLength: 1, maxLength: 20 } @@ -137,6 +138,7 @@ module.exports = { }, username: { type: 'string', + format: 'username', minLength: 1, maxLength: 20 } From 50d2ac1313983f4ac6bc2d5a308b1a599af84a23 Mon Sep 17 00:00:00 2001 From: Oliver Beddows Date: Tue, 1 Nov 2016 14:28:49 +0100 Subject: [PATCH 121/272] Adding minLength / maxLength to address. --- helpers/z_schema.js | 2 +- schema/accounts.js | 16 ++++++++++++---- schema/dapps.js | 4 +++- schema/transactions.js | 20 +++++++++++++++----- 4 files changed, 31 insertions(+), 11 deletions(-) diff --git a/helpers/z_schema.js b/helpers/z_schema.js index ed00803b3b6..e70e5d85f81 100644 --- a/helpers/z_schema.js +++ b/helpers/z_schema.js @@ -8,7 +8,7 @@ z_schema.registerFormat('id', function (str) { }); z_schema.registerFormat('address', function (str) { - return /^[0-9]{1,21}[L|l]$/g.test(str); + return /^[0-9]+[L|l]$/g.test(str); }); z_schema.registerFormat('username', function (str) { diff --git a/schema/accounts.js b/schema/accounts.js index 39cf51efe48..44f6db70c71 100644 --- a/schema/accounts.js +++ b/schema/accounts.js @@ -19,7 +19,9 @@ module.exports = { properties: { address: { type: 'string', - format: 'address' + format: 'address', + minLength: 1, + maxLength: 22 } }, required: ['address'] @@ -30,7 +32,9 @@ module.exports = { properties: { address: { type: 'string', - format: 'address' + format: 'address', + minLength: 1, + maxLength: 22 } }, required: ['address'] @@ -53,7 +57,9 @@ module.exports = { properties: { address: { type: 'string', - format: 'address' + format: 'address', + minLength: 1, + maxLength: 22 } }, required: ['address'] @@ -84,7 +90,9 @@ module.exports = { properties: { address: { type: 'string', - format: 'address' + format: 'address', + minLength: 1, + maxLength: 22 } }, required: ['address'] diff --git a/schema/dapps.js b/schema/dapps.js index 2f1e48dc979..4b7d9fd0f02 100644 --- a/schema/dapps.js +++ b/schema/dapps.js @@ -192,7 +192,9 @@ module.exports = { }, recipientId: { type: 'string', - format: 'address' + format: 'address', + minLength: 1, + maxLength: 22 }, secondSecret: { type: 'string', diff --git a/schema/transactions.js b/schema/transactions.js index 9747e2805c2..e8eed073f0a 100644 --- a/schema/transactions.js +++ b/schema/transactions.js @@ -20,7 +20,9 @@ module.exports = { }, senderId: { type: 'string', - format: 'address' + format: 'address', + minLength: 1, + maxLength: 22 }, senderPublicKey: { type: 'string', @@ -32,11 +34,15 @@ module.exports = { }, ownerAddress: { type: 'string', - format: 'address' + format: 'address', + minLength: 1, + maxLength: 22 }, recipientId: { type: 'string', - format: 'address' + format: 'address', + minLength: 1, + maxLength: 22 }, amount: { type: 'integer', @@ -98,7 +104,9 @@ module.exports = { }, address: { type: 'string', - format: 'address' + format: 'address', + minLength: 1, + maxLength: 22 } } }, @@ -118,7 +126,9 @@ module.exports = { }, recipientId: { type: 'string', - format: 'address' + format: 'address', + minLength: 1, + maxLength: 22 }, publicKey: { type: 'string', From 11f3b6e89a56d1a19be91a0cabdf90371ad507df Mon Sep 17 00:00:00 2001 From: Oliver Beddows Date: Tue, 1 Nov 2016 14:32:20 +0100 Subject: [PATCH 122/272] Adding topAccounts property. --- test/config.json | 1 + 1 file changed, 1 insertion(+) diff --git a/test/config.json b/test/config.json index 8261139a144..542d656cf21 100644 --- a/test/config.json +++ b/test/config.json @@ -6,6 +6,7 @@ "logFileName": "logs/lisk.log", "consoleLogLevel": "debug", "trustProxy": false, + "topAccounts": false, "db": { "host": "localhost", "port": 5432, From f7220f4db3f8206f3b19df2122574e65d8918280 Mon Sep 17 00:00:00 2001 From: Oliver Beddows Date: Tue, 1 Nov 2016 14:47:08 +0100 Subject: [PATCH 123/272] Changing regular expression. Allowing both lower and upper case username. --- helpers/z_schema.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/helpers/z_schema.js b/helpers/z_schema.js index e70e5d85f81..c03a472e4a5 100644 --- a/helpers/z_schema.js +++ b/helpers/z_schema.js @@ -12,7 +12,7 @@ z_schema.registerFormat('address', function (str) { }); z_schema.registerFormat('username', function (str) { - return /^[a-z0-9!@$&_.]+$/g.test(str); + return /^[a-z0-9!@$&_.]+$/ig.test(str); }); z_schema.registerFormat('hex', function (str) { From fd90d5b4404ec415db730190892f25689b7eefac Mon Sep 17 00:00:00 2001 From: Oliver Beddows Date: Tue, 1 Nov 2016 14:48:45 +0100 Subject: [PATCH 124/272] Changing regular expression. Allowing both lower and upper case address. --- helpers/z_schema.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/helpers/z_schema.js b/helpers/z_schema.js index c03a472e4a5..359ef2bd4d5 100644 --- a/helpers/z_schema.js +++ b/helpers/z_schema.js @@ -8,7 +8,7 @@ z_schema.registerFormat('id', function (str) { }); z_schema.registerFormat('address', function (str) { - return /^[0-9]+[L|l]$/g.test(str); + return /^[0-9]+[L]$/ig.test(str); }); z_schema.registerFormat('username', function (str) { From 0955463b232ef04e21abe272cb916ba556cde347 Mon Sep 17 00:00:00 2001 From: Oliver Beddows Date: Tue, 1 Nov 2016 15:39:37 +0100 Subject: [PATCH 125/272] Adding missing parameter. --- test/api/transactions.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/api/transactions.js b/test/api/transactions.js index 4a38efa2738..95bf0f83253 100644 --- a/test/api/transactions.js +++ b/test/api/transactions.js @@ -73,7 +73,7 @@ describe('GET /api/transactions', function () { var orderBy = 'amount:asc'; var params = [ - 'blockId=', + 'blockId=' + '1', 'senderId=' + node.gAccount.address, 'recipientId=' + account.address, 'limit=' + limit, From af82d6ca7ab5437e4a7dbebce7755db720b98bae Mon Sep 17 00:00:00 2001 From: Oliver Beddows Date: Tue, 1 Nov 2016 15:40:02 +0100 Subject: [PATCH 126/272] Returning true on zero string length. --- helpers/z_schema.js | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+) diff --git a/helpers/z_schema.js b/helpers/z_schema.js index 359ef2bd4d5..978089f6225 100644 --- a/helpers/z_schema.js +++ b/helpers/z_schema.js @@ -4,14 +4,26 @@ var ip = require('ip'); var z_schema = require('z-schema'); z_schema.registerFormat('id', function (str) { + if (str.length === 0) { + return true; + } + return /^[0-9]+$/g.test(str); }); z_schema.registerFormat('address', function (str) { + if (str.length === 0) { + return true; + } + return /^[0-9]+[L]$/ig.test(str); }); z_schema.registerFormat('username', function (str) { + if (str.length === 0) { + return true; + } + return /^[a-z0-9!@$&_.]+$/ig.test(str); }); @@ -90,10 +102,18 @@ z_schema.registerFormat('ip', function (str) { }); z_schema.registerFormat('os', function (str) { + if (str.length === 0) { + return true; + } + return /^[a-z0-9-_.]+$/ig.test(str); }); z_schema.registerFormat('version', function (str) { + if (str.length === 0) { + return true; + } + return /^([0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3})([a-z]{1})?$/g.test(str); }); From 711e92f0a98d9b57d8f92a297227c4f58137ea9f Mon Sep 17 00:00:00 2001 From: Oliver Beddows Date: Tue, 1 Nov 2016 15:42:32 +0100 Subject: [PATCH 127/272] Adding minLength to os. --- schema/loader.js | 1 + schema/peers.js | 2 ++ schema/transport.js | 1 + 3 files changed, 4 insertions(+) diff --git a/schema/loader.js b/schema/loader.js index 89302ceaec2..099daa24ca8 100644 --- a/schema/loader.js +++ b/schema/loader.js @@ -56,6 +56,7 @@ module.exports = { os: { type: 'string', format: 'os', + minLength: 1, maxLength: 64 }, version: { diff --git a/schema/peers.js b/schema/peers.js index 003e9eb2db7..f01648d82e6 100644 --- a/schema/peers.js +++ b/schema/peers.js @@ -35,6 +35,7 @@ module.exports = { os: { type: 'string', format: 'os', + minLength: 1, maxLength: 64 }, version: { @@ -74,6 +75,7 @@ module.exports = { os: { type: 'string', format: 'os', + minLength: 1, maxLength: 64 }, version: { diff --git a/schema/transport.js b/schema/transport.js index 4f27f52dd68..ae0c321cc4f 100644 --- a/schema/transport.js +++ b/schema/transport.js @@ -17,6 +17,7 @@ module.exports = { os: { type: 'string', format: 'os', + minLength: 1, maxLength: 64 }, version: { From f07dc6e5e83b8e3904ef147dfeeaaab808797d2a Mon Sep 17 00:00:00 2001 From: Oliver Beddows Date: Tue, 1 Nov 2016 15:43:43 +0100 Subject: [PATCH 128/272] Adding minLength / maxLength to version. --- schema/loader.js | 4 +++- schema/peers.js | 8 ++++++-- schema/transport.js | 4 +++- 3 files changed, 12 insertions(+), 4 deletions(-) diff --git a/schema/loader.js b/schema/loader.js index 099daa24ca8..62a08ec6f60 100644 --- a/schema/loader.js +++ b/schema/loader.js @@ -61,7 +61,9 @@ module.exports = { }, version: { type: 'string', - format: 'version' + format: 'version', + minLength: 5, + maxLength: 12 }, broadhash: { type: 'string', diff --git a/schema/peers.js b/schema/peers.js index f01648d82e6..0fffd1f0a90 100644 --- a/schema/peers.js +++ b/schema/peers.js @@ -40,7 +40,9 @@ module.exports = { }, version: { type: 'string', - format: 'version' + format: 'version', + minLength: 5, + maxLength: 12 }, broadhash: { type: 'string', @@ -80,7 +82,9 @@ module.exports = { }, version: { type: 'string', - format: 'version' + format: 'version', + minLength: 5, + maxLength: 12 }, broadhash: { type: 'string', diff --git a/schema/transport.js b/schema/transport.js index ae0c321cc4f..655ebed61d9 100644 --- a/schema/transport.js +++ b/schema/transport.js @@ -22,7 +22,9 @@ module.exports = { }, version: { type: 'string', - format: 'version' + format: 'version', + minLength: 5, + maxLength: 12 }, nethash: { type: 'string', From 07d1c9b652b6340e86a4f5d1b9d804747e838c8f Mon Sep 17 00:00:00 2001 From: Oliver Beddows Date: Tue, 1 Nov 2016 15:59:11 +0100 Subject: [PATCH 129/272] Removing duplicate validations now handled by schema. --- modules/accounts.js | 15 --------------- modules/dapps.js | 5 ----- 2 files changed, 20 deletions(-) diff --git a/modules/accounts.js b/modules/accounts.js index e24f99b9504..8c49e4c5e84 100644 --- a/modules/accounts.js +++ b/modules/accounts.js @@ -256,11 +256,6 @@ shared.getBalance = function (req, cb) { return setImmediate(cb, err[0].message); } - var isAddress = /^[0-9]{1,21}[L|l]$/g; - if (!isAddress.test(req.body.address)) { - return setImmediate(cb, 'Invalid address'); - } - self.getAccount({ address: req.body.address }, function (err, account) { if (err) { return setImmediate(cb, err); @@ -280,11 +275,6 @@ shared.getPublickey = function (req, cb) { return setImmediate(cb, err[0].message); } - var isAddress = /^[0-9]{1,21}[L|l]$/g; - if (!isAddress.test(req.body.address)) { - return setImmediate(cb, 'Invalid address'); - } - self.getAccount({ address: req.body.address }, function (err, account) { if (err) { return setImmediate(cb, err); @@ -483,11 +473,6 @@ shared.getAccount = function (req, cb) { return setImmediate(cb, err[0].message); } - var isAddress = /^[0-9]{1,21}[L|l]$/g; - if (!isAddress.test(req.body.address)) { - return setImmediate(cb, 'Invalid address'); - } - self.getAccount({ address: req.body.address }, function (err, account) { if (err) { return setImmediate(cb, err); diff --git a/modules/dapps.js b/modules/dapps.js index a8af787db99..c2da7be587b 100644 --- a/modules/dapps.js +++ b/modules/dapps.js @@ -1267,11 +1267,6 @@ __private.sendWithdrawal = function (req, cb) { var keypair = library.ed.makeKeypair(hash); var query = {}; - var isAddress = /^[0-9]{1,21}[L|l]$/g; - if (!isAddress.test(req.body.recipientId)) { - return setImmediate(cb, 'Invalid recipient'); - } - library.balancesSequence.add(function (cb) { if (req.body.multisigAccountPublicKey && req.body.multisigAccountPublicKey !== keypair.publicKey.toString('hex')) { modules.accounts.getAccount({publicKey: req.body.multisigAccountPublicKey}, function (err, account) { From 62f79a59542a585145474613add283423a3e9abc Mon Sep 17 00:00:00 2001 From: Oliver Beddows Date: Tue, 1 Nov 2016 16:07:41 +0100 Subject: [PATCH 130/272] Changing expectations. --- test/api/accounts.js | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/test/api/accounts.js b/test/api/accounts.js index b5704271b6a..4a5b9b4b9c9 100644 --- a/test/api/accounts.js +++ b/test/api/accounts.js @@ -121,7 +121,7 @@ describe('GET /api/accounts/getBalance?address=', function () { it('using invalid address should fail', function (done) { getBalance('thisIsNOTALiskAddress', function (err, res) { node.expect(res.body).to.have.property('success').to.be.not.ok; - node.expect(res.body).to.have.property('error').to.eql('Invalid address'); + node.expect(res.body).to.have.property('error').to.eql('Object didn\'t pass validation for format address: thisIsNOTALiskAddress'); done(); }); }); @@ -161,7 +161,7 @@ describe('GET /api/accounts/getPublicKey?address=', function () { it('using invalid address should fail', function (done) { getPublicKey('thisIsNOTALiskAddress', function (err, res) { node.expect(res.body).to.have.property('success').to.be.not.ok; - node.expect(res.body).to.have.property('error').to.contain('Invalid address'); + node.expect(res.body).to.have.property('error').to.contain('Object didn\'t pass validation for format address: thisIsNOTALiskAddress'); done(); }); }); @@ -281,10 +281,10 @@ describe('GET /accounts?address=', function () { }); it('using invalid address should fail', function (done) { - getAccounts('thisIsNOTAValidLiskAddress', function (err, res) { + getAccounts('thisIsNOTALiskAddress', function (err, res) { node.expect(res.body).to.have.property('success').to.be.not.ok; node.expect(res.body).to.have.property('error'); - node.expect(res.body.error).to.contain('Invalid address'); + node.expect(res.body.error).to.contain('Object didn\'t pass validation for format address: thisIsNOTALiskAddress'); done(); }); }); From a04577376b06f219ec3c59689f3fcf6712c82137 Mon Sep 17 00:00:00 2001 From: Oliver Beddows Date: Tue, 1 Nov 2016 20:44:10 +0100 Subject: [PATCH 131/272] Changing expectations. --- test/api/peer.js | 1 - test/api/peer.signatures.js | 2 +- 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/test/api/peer.js b/test/api/peer.js index 5536fb42f37..58ebcd0d525 100644 --- a/test/api/peer.js +++ b/test/api/peer.js @@ -24,7 +24,6 @@ describe('GET /peer/list', function () { .end(function (err, res) { node.debug('> Response:'.grey, JSON.stringify(res.body)); node.expect(res.body).to.have.property('peers').that.is.an('array'); - node.expect(res.body.peers).to.have.length.of.at.least(1); res.body.peers.forEach(function (peer) { node.expect(peer).to.have.property('ip').that.is.a('string'); node.expect(peer).to.have.property('port').that.is.a('number'); diff --git a/test/api/peer.signatures.js b/test/api/peer.signatures.js index b6a1b76c3ba..881c8333e62 100644 --- a/test/api/peer.signatures.js +++ b/test/api/peer.signatures.js @@ -66,7 +66,7 @@ describe('POST /peer/signatures', function () { }); it('using unprocessable signature should fail', function (done) { - validParams.signature.transaction = 'invalidId'; + validParams.signature.transaction = '1'; node.post('/peer/signatures', validParams) .end(function (err, res) { From 9a4471e4bb8fee06ca36ba529637f4b5ceefda64 Mon Sep 17 00:00:00 2001 From: Oliver Beddows Date: Thu, 3 Nov 2016 13:17:09 +0100 Subject: [PATCH 132/272] Strengthening logic schema validations. --- logic/block.js | 10 ++++++++-- logic/inTransfer.js | 4 +++- logic/outTransfer.js | 8 ++++++-- logic/transaction.js | 20 ++++++++++++++++---- 4 files changed, 33 insertions(+), 9 deletions(-) diff --git a/logic/block.js b/logic/block.js index 36e611e267d..af17d212966 100644 --- a/logic/block.js +++ b/logic/block.js @@ -231,7 +231,10 @@ Block.prototype.schema = { type: 'object', properties: { id: { - type: 'string' + type: 'string', + format: 'id', + minLength: 1, + maxLength: 20 }, height: { type: 'integer' @@ -255,7 +258,10 @@ Block.prototype.schema = { type: 'integer' }, previousBlock: { - type: 'string' + type: 'string', + format: 'id', + minLength: 1, + maxLength: 20 }, timestamp: { type: 'integer' diff --git a/logic/inTransfer.js b/logic/inTransfer.js index e7229fa73f1..b47208d1cab 100644 --- a/logic/inTransfer.js +++ b/logic/inTransfer.js @@ -123,7 +123,9 @@ InTransfer.prototype.schema = { properties: { dappId: { type: 'string', - minLength: 1 + format: 'id', + minLength: 1, + maxLength: 20 }, }, required: ['dappId'] diff --git a/logic/outTransfer.js b/logic/outTransfer.js index 9cbd343695f..a768f9d7acc 100644 --- a/logic/outTransfer.js +++ b/logic/outTransfer.js @@ -155,11 +155,15 @@ OutTransfer.prototype.schema = { properties: { dappId: { type: 'string', - minLength: 1 + format: 'id', + minLength: 1, + maxLength: 20 }, transactionId: { type: 'string', - minLength: 1 + format: 'id', + minLength: 1, + maxLength: 20 } }, required: ['dappId', 'transactionId'] diff --git a/logic/transaction.js b/logic/transaction.js index fd7f04e2ace..474f61fbe73 100644 --- a/logic/transaction.js +++ b/logic/transaction.js @@ -727,13 +727,19 @@ Transaction.prototype.schema = { type: 'object', properties: { id: { - type: 'string' + type: 'string', + format: 'id', + minLength: 1, + maxLength: 20 }, height: { type: 'integer' }, blockId: { - type: 'string' + type: 'string', + format: 'id', + minLength: 1, + maxLength: 20 }, type: { type: 'integer' @@ -750,10 +756,16 @@ Transaction.prototype.schema = { format: 'publicKey' }, senderId: { - type: 'string' + type: 'string', + format: 'address', + minLength: 1, + maxLength: 22 }, recipientId: { - type: 'string' + type: 'string', + format: 'address', + minLength: 1, + maxLength: 22 }, amount: { type: 'integer', From 8530f567a4b15ff960e7afb1f51576b67369520c Mon Sep 17 00:00:00 2001 From: Oliver Beddows Date: Thu, 3 Nov 2016 13:36:32 +0100 Subject: [PATCH 133/272] Performing bignum arithmetic on transaction amount. --- logic/transaction.js | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/logic/transaction.js b/logic/transaction.js index 474f61fbe73..286d4daf51f 100644 --- a/logic/transaction.js +++ b/logic/transaction.js @@ -538,7 +538,8 @@ Transaction.prototype.undo = function (trs, block, sender, cb) { return setImmediate(cb, 'Unknown transaction type ' + trs.type); } - var amount = trs.amount + trs.fee; + var amount = bignum(trs.amount.toString()); + amount = amount.plus(trs.fee.toString()).toNumber(); this.scope.account.merge(sender.address, { balance: amount, @@ -624,7 +625,8 @@ Transaction.prototype.undoUnconfirmed = function (trs, sender, cb) { return setImmediate(cb, 'Unknown transaction type ' + trs.type); } - var amount = trs.amount + trs.fee; + var amount = bignum(trs.amount.toString()); + amount = amount.plus(trs.fee.toString()).toNumber(); this.scope.account.merge(sender.address, {u_balance: amount}, function (err, sender) { if (err) { From f5e2f164a5312db3c3a86e6c23539f2fd193d1bd Mon Sep 17 00:00:00 2001 From: Oliver Beddows Date: Thu, 3 Nov 2016 13:54:22 +0100 Subject: [PATCH 134/272] Refactoring code. Adding Transaction.prototype.checkBalance. --- logic/transaction.js | 33 +++++++++++++++++++++------------ 1 file changed, 21 insertions(+), 12 deletions(-) diff --git a/logic/transaction.js b/logic/transaction.js index 286d4daf51f..02d0427891f 100644 --- a/logic/transaction.js +++ b/logic/transaction.js @@ -212,6 +212,19 @@ Transaction.prototype.checkConfirmed = function (trs, cb) { }); }; +Transaction.prototype.checkBalance = function (amount, balance, trs, sender) { + var exceededBalance = bignum(sender[balance].toString()).lessThan(amount); + var exceeded = (trs.blockId !== genesisblock.block.id && exceededBalance); + + return { + exceeded: exceeded, + error: exceeded ? [ + 'Account does not have enough LSK:', sender.address, + 'balance:', bignum(sender[balance].toString() || '0').div(Math.pow(10,8)) + ].join(' ') : null + }; +}; + Transaction.prototype.process = function (trs, sender, requester, cb) { if (typeof requester === 'function') { cb = requester; @@ -496,14 +509,12 @@ Transaction.prototype.apply = function (trs, block, sender, cb) { return setImmediate(cb, 'Transaction is not ready'); } + // Check sender balance var amount = bignum(trs.amount.toString()).plus(trs.fee.toString()); - var exceedsBalance = bignum(sender.balance.toString()).lessThan(amount); + var senderBalance = this.checkBalance(amount, 'balance', trs, sender); - if (trs.blockId !== genesisblock.block.id && exceedsBalance) { - return setImmediate(cb, [ - 'Account does not have enough LSK:', sender.address, - 'balance:', bignum(sender.u_balance || 0).div(Math.pow(10,8)) - ].join(' ')); + if (senderBalance.exceeded) { + return setImmediate(cb, senderBalance.error); } amount = amount.toNumber(); @@ -591,14 +602,12 @@ Transaction.prototype.applyUnconfirmed = function (trs, sender, requester, cb) { return setImmediate(cb, 'Requester does not have a second signature'); } + // Check sender balance var amount = bignum(trs.amount.toString()).plus(trs.fee.toString()); - var exceedsBalance = bignum(sender.u_balance.toString()).lessThan(amount); + var senderBalance = this.checkBalance(amount, 'u_balance', trs, sender); - if (trs.blockId !== genesisblock.block.id && exceedsBalance) { - return setImmediate(cb, [ - 'Account does not have enough LSK:', sender.address, - 'balance:', bignum(sender.balance || 0).div(Math.pow(10,8)) - ].join(' ')); + if (senderBalance.exceeded) { + return setImmediate(cb, senderBalance.error); } amount = amount.toNumber(); From 639ae12e77b8e5eb661d17ad47b1f25505a96e5d Mon Sep 17 00:00:00 2001 From: Oliver Beddows Date: Thu, 3 Nov 2016 17:45:48 +0100 Subject: [PATCH 135/272] Implementing minimum required broadhash efficiency. Do not forge block when broadhash efficiency has fallen below threshold. --- config.json | 1 + helpers/constants.js | 1 + modules/delegates.js | 8 ++++++++ modules/transport.js | 15 ++++++++++++++- test/config.json | 1 + 5 files changed, 25 insertions(+), 1 deletion(-) diff --git a/config.json b/config.json index 5fa4b7a5b74..5483a60767b 100644 --- a/config.json +++ b/config.json @@ -68,6 +68,7 @@ } }, "forging": { + "force": false, "secret": [], "access": { "whiteList": [ diff --git a/helpers/constants.js b/helpers/constants.js index 8e5712c1bd9..d0aaac5e87f 100644 --- a/helpers/constants.js +++ b/helpers/constants.js @@ -26,6 +26,7 @@ module.exports = { maxRequests: 10000 * 12, maxSignaturesLength: 196 * 256, maxTxsPerBlock: 25, + minBroadhashEfficiency: 10, numberLength: 100000000, requestLength: 104, rewards: { diff --git a/modules/delegates.js b/modules/delegates.js index 1001327728b..bcd8d867f38 100644 --- a/modules/delegates.js +++ b/modules/delegates.js @@ -236,6 +236,14 @@ __private.forge = function (cb) { return setImmediate(cb); } + // Do not forge when broadhash efficiency is below threshold + var efficiency = modules.transport.efficiency(); + + if (efficiency <= constants.minBroadhashEfficiency) { + library.logger.debug(['Poor broadhash efficiency', efficiency, '%'].join(' ')); + return setImmediate(cb); + } + var currentSlot = slots.getSlotNumber(); var lastBlock = modules.blocks.getLastBlock(); diff --git a/modules/transport.js b/modules/transport.js index 7c3aa5d17de..65a2a8cd52f 100644 --- a/modules/transport.js +++ b/modules/transport.js @@ -27,6 +27,11 @@ function Transport (cb, scope) { __private.attachApi(); + // Optionally ignore broadhash efficiency + if (!library.config.forging.force) { + __private.efficiency = 100; + } + setImmediate(cb, null, self); } @@ -353,6 +358,10 @@ Transport.prototype.headers = function (headers) { return __private.headers; }; +Transport.prototype.efficiency = function () { + return __private.efficiency === undefined ? 100 : __private.efficiency; +}; + Transport.prototype.broadcast = function (config, options, cb) { library.logger.debug('Begin broadcast', options); @@ -360,8 +369,12 @@ Transport.prototype.broadcast = function (config, options, cb) { config.broadhash = config.broadhash || null; config.height = config.height || null; - modules.peers.list(config, function (err, peers) { + modules.peers.list(config, function (err, peers, efficiency) { if (!err) { + if (__private.efficiency !== undefined) { + __private.efficiency = efficiency; + } + async.eachLimit(peers, 20, function (peer, cb) { return self.getFromPeer(peer, options, cb); }, function (err) { diff --git a/test/config.json b/test/config.json index 542d656cf21..142988b3c70 100644 --- a/test/config.json +++ b/test/config.json @@ -47,6 +47,7 @@ } }, "forging": { + "force": true, "secret": [ "robust swift grocery peasant forget share enable convince deputy road keep cheap", "weapon van trap again sustain write useless great pottery urge month nominee", From ee7d0d95e52d3ae0e472c16f83dc6d6a9dee88f5 Mon Sep 17 00:00:00 2001 From: Oliver Beddows Date: Thu, 3 Nov 2016 19:27:54 +0100 Subject: [PATCH 136/272] Changing expectations. --- test/api/dapps.js | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/test/api/dapps.js b/test/api/dapps.js index 063730de381..74395b85397 100644 --- a/test/api/dapps.js +++ b/test/api/dapps.js @@ -600,7 +600,7 @@ describe('PUT /api/dapps/withdrawal', function () { putWithdrawal(validParams, function (err, res) { node.expect(res.body).to.have.property('success').to.not.be.ok; - node.expect(res.body).to.have.property('error').to.equal('Application not found: 1L'); + node.expect(res.body).to.have.property('error').to.equal('Object didn\'t pass validation for format id: 1L'); done(); }); }); @@ -660,7 +660,7 @@ describe('PUT /api/dapps/withdrawal', function () { putWithdrawal(validParams, function (err, res) { node.expect(res.body).to.have.property('success').to.not.be.ok; - node.expect(res.body).to.have.property('error').to.equal('Invalid outTransfer transactionId'); + node.expect(res.body).to.have.property('error').to.equal('Object didn\'t pass validation for format id: 1L'); done(); }); }); @@ -710,7 +710,7 @@ describe('PUT /api/dapps/withdrawal', function () { putWithdrawal(validParams, function (err, res) { node.expect(res.body).to.have.property('success').to.not.be.ok; - node.expect(res.body).to.have.property('error').to.equal('String is too short (1 chars), minimum 2'); + node.expect(res.body).to.have.property('error').to.equal('Object didn\'t pass validation for format address: 1'); done(); }); }); @@ -730,7 +730,7 @@ describe('PUT /api/dapps/withdrawal', function () { putWithdrawal(validParams, function (err, res) { node.expect(res.body).to.have.property('success').to.not.be.ok; - node.expect(res.body).to.have.property('error').to.equal('Invalid recipient'); + node.expect(res.body).to.have.property('error').to.match(/Object didn\'t pass validation for format address: [0-9]+/); done(); }); }); From 9cbe8c8af4c8efebe3f78d214c61f1e68e207dbf Mon Sep 17 00:00:00 2001 From: Oliver Beddows Date: Thu, 3 Nov 2016 23:51:28 +0100 Subject: [PATCH 137/272] Adding dapp peer using modules.peers.update. --- modules/dapps.js | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/modules/dapps.js b/modules/dapps.js index c2da7be587b..1410747afe3 100644 --- a/modules/dapps.js +++ b/modules/dapps.js @@ -1045,11 +1045,12 @@ __private.createSandbox = function (dapp, params, cb) { } async.eachSeries(dappConfig.peers, function (peer, eachSeriesCb) { - modules.peers.addDapp({ + modules.peers.update({ ip: peer.ip, port: peer.port, dappid: dapp.transactionId - }, eachSeriesCb); + }); + return eachSeriesCb(); }, function (err) { if (err) { return setImmediate(cb, err); From 1735527acfbc8e3cffdc67529d9c44dccfb2ba73 Mon Sep 17 00:00:00 2001 From: Oliver Beddows Date: Fri, 4 Nov 2016 22:13:05 +0100 Subject: [PATCH 138/272] Updating lisk-ui submodule. --- public | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/public b/public index dfbfa6e0910..03474e641d5 160000 --- a/public +++ b/public @@ -1 +1 @@ -Subproject commit dfbfa6e0910ec5c7a415714fc91c7086d1c291a9 +Subproject commit 03474e641d5eac8c27ab8af562f4c86d4dbe11df From 4ef41a35ab10444fcd292582eab4e628946cbeb7 Mon Sep 17 00:00:00 2001 From: Oliver Beddows Date: Sat, 5 Nov 2016 23:34:53 +0100 Subject: [PATCH 139/272] Setting peer state to connected on update. --- modules/peers.js | 1 + modules/transport.js | 4 +--- 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/modules/peers.js b/modules/peers.js index 7a90e37d670..af55c8b3663 100644 --- a/modules/peers.js +++ b/modules/peers.js @@ -338,6 +338,7 @@ Peers.prototype.remove = function (pip, port) { }; Peers.prototype.update = function (peer) { + peer.state = 2; return __private.sweeper.push('upsert', self.accept(peer).object()); }; diff --git a/modules/transport.js b/modules/transport.js index 65a2a8cd52f..268fb6e19a2 100644 --- a/modules/transport.js +++ b/modules/transport.js @@ -50,8 +50,7 @@ __private.attachApi = function () { req.peer = modules.peers.accept( { ip: req.headers['x-forwarded-for'] || req.connection.remoteAddress, - port: req.headers.port, - state: 2 + port: req.headers.port } ); @@ -415,7 +414,6 @@ Transport.prototype.getFromPeer = function (peer, options, cb) { } peer = modules.peers.accept(peer); - peer.state = 2; var req = { url: 'http://' + peer.ip + ':' + peer.port + url, From 776e26f90ed9db482a490c50e3e9b07d6e6a20ab Mon Sep 17 00:00:00 2001 From: Oliver Beddows Date: Tue, 8 Nov 2016 22:29:17 +0100 Subject: [PATCH 140/272] Extending properties. --- logic/peer.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/logic/peer.js b/logic/peer.js index c9edb78e47e..a5d87c928d5 100644 --- a/logic/peer.js +++ b/logic/peer.js @@ -88,7 +88,7 @@ Peer.prototype.headers = function (headers) { }; Peer.prototype.extend = function (object) { - return this.headers(extend({}, this.object(), object)); + return this.headers(extend({}, this.properties, object)); }; Peer.prototype.object = function () { From f120e8916b9ed33311725ed4d6db6e3d1ba62fa0 Mon Sep 17 00:00:00 2001 From: Oliver Beddows Date: Tue, 8 Nov 2016 23:37:28 +0100 Subject: [PATCH 141/272] Standardising property naming. --- modules/transport.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/modules/transport.js b/modules/transport.js index 268fb6e19a2..3118324ce3d 100644 --- a/modules/transport.js +++ b/modules/transport.js @@ -62,7 +62,7 @@ __private.attachApi = function () { // Remove peer __private.removePeer({peer: req.peer, code: 'EHEADERS', req: req}); - return res.status(500).send({status: false, error: report.issues}); + return res.status(500).send({success: false, error: report.issues}); } if (headers.nethash !== modules.system.getNethash()) { From 0940122a55bcfd91c6610f6e17b9a29209561309 Mon Sep 17 00:00:00 2001 From: Oliver Beddows Date: Tue, 8 Nov 2016 23:37:59 +0100 Subject: [PATCH 142/272] Restoring sane API behaviour #259. - Returning error on already processed transaction. - Returning error on already confirmed transaction. --- logic/transaction.js | 2 +- modules/transactions.js | 12 +++--------- test/api/dapps.js | 7 +++---- test/api/peer.transactions.js | 21 ++++++++++++--------- 4 files changed, 19 insertions(+), 23 deletions(-) diff --git a/logic/transaction.js b/logic/transaction.js index 02d0427891f..a20172013ce 100644 --- a/logic/transaction.js +++ b/logic/transaction.js @@ -205,7 +205,7 @@ Transaction.prototype.checkConfirmed = function (trs, cb) { if (err) { return setImmediate(cb, err); } else if (count > 0) { - return setImmediate(cb, 'Transaction is already confirmed: ' + trs.id, true); + return setImmediate(cb, 'Transaction is already confirmed: ' + trs.id); } else { return setImmediate(cb); } diff --git a/modules/transactions.js b/modules/transactions.js index 818dc2b127b..1fbacb5b398 100644 --- a/modules/transactions.js +++ b/modules/transactions.js @@ -242,19 +242,13 @@ Transactions.prototype.processUnconfirmedTransaction = function (transaction, br // Check transaction indexes if (__private.unconfirmedTransactionsIdIndex[transaction.id] !== undefined) { - library.logger.debug('Transaction is already processed: ' + transaction.id); - return setImmediate(cb); + return setImmediate(cb, 'Transaction is already processed: ' + transaction.id); } modules.accounts.setAccountAndGet({publicKey: transaction.senderPublicKey}, function (err, sender) { - function done (err, ignore) { + function done (err) { if (err) { - if (ignore) { - library.logger.debug(err); - return setImmediate(cb); - } else { - return setImmediate(cb, err); - } + return setImmediate(cb, err); } __private.addUnconfirmedTransaction(transaction, sender, function (err) { diff --git a/test/api/dapps.js b/test/api/dapps.js index 74395b85397..20b41e4bc42 100644 --- a/test/api/dapps.js +++ b/test/api/dapps.js @@ -466,9 +466,10 @@ describe('PUT /api/dapps/withdrawal', function () { var validParams; beforeEach(function (done) { - var keys = node.lisk.crypto.getKeys(account.password); + var randomAccount = node.randomTxAccount(); + var keys = node.lisk.crypto.getKeys(randomAccount.password); var recipientId = node.lisk.crypto.getAddress(keys.publicKey); - var transaction = node.lisk.transaction.createTransaction('1L', 100000000, account.password); + var transaction = node.lisk.transaction.createTransaction(randomAccount.address, 100000000, account.password); validParams = { secret: account.password, @@ -481,8 +482,6 @@ describe('PUT /api/dapps/withdrawal', function () { done(); }); - var randomAccount = node.randomTxAccount(); - it('using no secret should fail', function (done) { delete validParams.secret; diff --git a/test/api/peer.transactions.js b/test/api/peer.transactions.js index 2da2efd38fa..87b6f64c06c 100644 --- a/test/api/peer.transactions.js +++ b/test/api/peer.transactions.js @@ -52,7 +52,8 @@ describe('POST /peer/transactions', function () { }); it('using valid headers should be ok', function (done) { - var transaction = node.lisk.transaction.createTransaction('1L', 1, node.gAccount.password); + var account = node.randomAccount(); + var transaction = node.lisk.transaction.createTransaction(account.address, 1, node.gAccount.password); postTransaction(transaction, function (err, res) { node.expect(res.body).to.have.property('success').to.be.ok; @@ -61,23 +62,25 @@ describe('POST /peer/transactions', function () { }); }); - it('using already processed transaction should be ok', function (done) { - var transaction = node.lisk.transaction.createTransaction('1L', 1, node.gAccount.password); + it('using already processed transaction should fail', function (done) { + var account = node.randomAccount(); + var transaction = node.lisk.transaction.createTransaction(account.address, 1, node.gAccount.password); postTransaction(transaction, function (err, res) { node.expect(res.body).to.have.property('success').to.be.ok; node.expect(res.body).to.have.property('transactionId').to.equal(transaction.id); postTransaction(transaction, function (err, res) { - node.expect(res.body).to.have.property('success').to.be.ok; - node.expect(res.body).to.have.property('transactionId').to.equal(transaction.id); + node.expect(res.body).to.have.property('success').to.be.not.ok; + node.expect(res.body).to.have.property('message').to.match(/Transaction is already processed: [0-9]+/); done(); }); }); }); - it('using already confirmed transaction should be ok', function (done) { - var transaction = node.lisk.transaction.createTransaction('1L', 1, node.gAccount.password); + it('using already confirmed transaction should fail', function (done) { + var account = node.randomAccount(); + var transaction = node.lisk.transaction.createTransaction(account.address, 1, node.gAccount.password); postTransaction(transaction, function (err, res) { node.expect(res.body).to.have.property('success').to.be.ok; @@ -85,8 +88,8 @@ describe('POST /peer/transactions', function () { node.onNewBlock(function (err) { postTransaction(transaction, function (err, res) { - node.expect(res.body).to.have.property('success').to.be.ok; - node.expect(res.body).to.have.property('transactionId').to.equal(transaction.id); + node.expect(res.body).to.have.property('success').to.be.not.ok; + node.expect(res.body).to.have.property('message').to.match(/Transaction is already confirmed: [0-9]+/); done(); }); }); From 599fce503c93f1b6de5fb4843c462a34ef1f433f Mon Sep 17 00:00:00 2001 From: Oliver Beddows Date: Tue, 8 Nov 2016 23:44:57 +0100 Subject: [PATCH 143/272] Adding ip address to headers. --- test/node.js | 2 ++ 1 file changed, 2 insertions(+) diff --git a/test/node.js b/test/node.js index fbca5f87bc8..d16283347e9 100644 --- a/test/node.js +++ b/test/node.js @@ -194,6 +194,7 @@ node.addPeers = function (numOfPeers, cb) { height: 1, nethash: node.config.nethash, os: os, + ip: '0.0.0.0', port: port, version: version } @@ -312,6 +313,7 @@ function abstractRequest (options, done) { request.set('Accept', 'application/json'); request.set('version', node.version); request.set('nethash', node.config.nethash); + request.set('ip', '0.0.0.0'); request.set('port', node.config.port); request.expect('Content-Type', /json/); From b931817379a1ef731da4fbadc1e18171c17b583e Mon Sep 17 00:00:00 2001 From: Oliver Beddows Date: Wed, 9 Nov 2016 13:17:44 +0100 Subject: [PATCH 144/272] Fixes #253. Adding missing check on transaction. --- modules/transactions.js | 3 +++ 1 file changed, 3 insertions(+) diff --git a/modules/transactions.js b/modules/transactions.js index 1fbacb5b398..54da89d1c61 100644 --- a/modules/transactions.js +++ b/modules/transactions.js @@ -299,6 +299,9 @@ Transactions.prototype.processUnconfirmedTransaction = function (transaction, br Transactions.prototype.applyUnconfirmedList = function (ids, cb) { async.eachSeries(ids, function (id, cb) { var transaction = self.getUnconfirmedTransaction(id); + if (!transaction) { + return setImmediate(cb); + } modules.accounts.setAccountAndGet({publicKey: transaction.senderPublicKey}, function (err, sender) { if (err) { self.removeUnconfirmedTransaction(id); From 38758e9c0f2c073305a6f2f3063a644064131f9f Mon Sep 17 00:00:00 2001 From: Oliver Beddows Date: Wed, 9 Nov 2016 13:18:42 +0100 Subject: [PATCH 145/272] Renaming callback for clarity. --- modules/transactions.js | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/modules/transactions.js b/modules/transactions.js index 54da89d1c61..ad66211a152 100644 --- a/modules/transactions.js +++ b/modules/transactions.js @@ -297,21 +297,21 @@ Transactions.prototype.processUnconfirmedTransaction = function (transaction, br }; Transactions.prototype.applyUnconfirmedList = function (ids, cb) { - async.eachSeries(ids, function (id, cb) { + async.eachSeries(ids, function (id, eachSeriesCb) { var transaction = self.getUnconfirmedTransaction(id); if (!transaction) { - return setImmediate(cb); + return setImmediate(eachSeriesCb); } modules.accounts.setAccountAndGet({publicKey: transaction.senderPublicKey}, function (err, sender) { if (err) { self.removeUnconfirmedTransaction(id); - return setImmediate(cb, err); + return setImmediate(eachSeriesCb, err); } self.applyUnconfirmed(transaction, sender, function (err) { if (err) { self.removeUnconfirmedTransaction(id); } - return setImmediate(cb, err); + return setImmediate(eachSeriesCb, err); }); }); }, cb); From 0d20439bcea66d0639e59876702baefc7a50607a Mon Sep 17 00:00:00 2001 From: Oliver Beddows Date: Wed, 9 Nov 2016 15:52:36 +0100 Subject: [PATCH 146/272] Checking sender balance in Transaction.prototype.verify. --- logic/transaction.js | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/logic/transaction.js b/logic/transaction.js index a20172013ce..3d445b30cb9 100644 --- a/logic/transaction.js +++ b/logic/transaction.js @@ -424,6 +424,14 @@ Transaction.prototype.verify = function (trs, sender, requester, cb) { return setImmediate(cb, 'Invalid transaction amount'); } + // Check sender balance + var amount = bignum(trs.amount.toString()).plus(trs.fee.toString()); + var senderBalance = this.checkBalance(amount, 'balance', trs, sender); + + if (senderBalance.exceeded) { + return setImmediate(cb, senderBalance.error); + } + // Check timestamp if (slots.getSlotNumber(trs.timestamp) > slots.getSlotNumber()) { return setImmediate(cb, 'Invalid transaction timestamp'); From ff004f14368cacc96d7a14b11dc49feaaad8e256 Mon Sep 17 00:00:00 2001 From: Oliver Beddows Date: Wed, 9 Nov 2016 15:53:13 +0100 Subject: [PATCH 147/272] Improving comments. --- logic/transaction.js | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/logic/transaction.js b/logic/transaction.js index 3d445b30cb9..798c44a1681 100644 --- a/logic/transaction.js +++ b/logic/transaction.js @@ -424,7 +424,7 @@ Transaction.prototype.verify = function (trs, sender, requester, cb) { return setImmediate(cb, 'Invalid transaction amount'); } - // Check sender balance + // Check confirmed sender balance var amount = bignum(trs.amount.toString()).plus(trs.fee.toString()); var senderBalance = this.checkBalance(amount, 'balance', trs, sender); @@ -517,7 +517,7 @@ Transaction.prototype.apply = function (trs, block, sender, cb) { return setImmediate(cb, 'Transaction is not ready'); } - // Check sender balance + // Check confirmed sender balance var amount = bignum(trs.amount.toString()).plus(trs.fee.toString()); var senderBalance = this.checkBalance(amount, 'balance', trs, sender); @@ -610,7 +610,7 @@ Transaction.prototype.applyUnconfirmed = function (trs, sender, requester, cb) { return setImmediate(cb, 'Requester does not have a second signature'); } - // Check sender balance + // Check unconfirmed sender balance var amount = bignum(trs.amount.toString()).plus(trs.fee.toString()); var senderBalance = this.checkBalance(amount, 'u_balance', trs, sender); From e4b5bb9dee7673c0ba39bb98253fb89a644a6f77 Mon Sep 17 00:00:00 2001 From: Oliver Beddows Date: Wed, 9 Nov 2016 17:02:39 +0100 Subject: [PATCH 148/272] Extending object. --- logic/peer.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/logic/peer.js b/logic/peer.js index a5d87c928d5..c9edb78e47e 100644 --- a/logic/peer.js +++ b/logic/peer.js @@ -88,7 +88,7 @@ Peer.prototype.headers = function (headers) { }; Peer.prototype.extend = function (object) { - return this.headers(extend({}, this.properties, object)); + return this.headers(extend({}, this.object(), object)); }; Peer.prototype.object = function () { From 31d9ec03b6b21813bf0bbb01219b369bcf5f8183 Mon Sep 17 00:00:00 2001 From: Oliver Beddows Date: Wed, 9 Nov 2016 17:03:38 +0100 Subject: [PATCH 149/272] Converting broadhash to buffer when pushed to sweeper. --- logic/peer.js | 4 ---- logic/peerSweeper.js | 3 +++ 2 files changed, 3 insertions(+), 4 deletions(-) diff --git a/logic/peer.js b/logic/peer.js index c9edb78e47e..9812c3ad0f1 100644 --- a/logic/peer.js +++ b/logic/peer.js @@ -98,10 +98,6 @@ Peer.prototype.object = function () { object[property] = this[property]; }.bind(this)); - if (object.broadhash != null) { - object.broadhash = new Buffer(object.broadhash, 'hex'); - } - this.nullable.forEach(function (property) { if (object[property] == null) { object[property] = null; diff --git a/logic/peerSweeper.js b/logic/peerSweeper.js index ff611460f85..1bcc8c80abb 100644 --- a/logic/peerSweeper.js +++ b/logic/peerSweeper.js @@ -22,6 +22,9 @@ PeerSweeper.prototype.push = function (action, peer) { } else { throw 'Missing push action'; } + if (peer.broadhash != null) { + peer.broadhash = new Buffer(peer.broadhash, 'hex'); + } this.peers.push(peer); }; From 9a8cb60034083509d1aea6fdccc6a97b97c0c5fa Mon Sep 17 00:00:00 2001 From: Oliver Beddows Date: Wed, 9 Nov 2016 19:07:03 +0100 Subject: [PATCH 150/272] Moving logs to log level. --- modules/loader.js | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/modules/loader.js b/modules/loader.js index a19d0a93163..80510f014a7 100644 --- a/modules/loader.js +++ b/modules/loader.js @@ -658,7 +658,7 @@ Loader.prototype.onPeersReady = function () { async.retry(retries, __private.sync, cb); }, function (err) { if (err) { - library.logger.warn('Sync timer', err); + library.logger.log('Sync timer', err); } return setImmediate(seriesCb); @@ -671,7 +671,7 @@ Loader.prototype.onPeersReady = function () { if (__private.loaded) { async.retry(retries, __private.loadUnconfirmedTransactions, function (err) { if (err) { - library.logger.warn('Unconfirmed transactions timer', err); + library.logger.log('Unconfirmed transactions timer', err); } return setImmediate(seriesCb); @@ -684,7 +684,7 @@ Loader.prototype.onPeersReady = function () { if (__private.loaded) { async.retry(retries, __private.loadSignatures, function (err) { if (err) { - library.logger.warn('Signatures timer', err); + library.logger.log('Signatures timer', err); } return setImmediate(seriesCb); From b789298a5fa8268af9cc9d20e22c5b20fc955510 Mon Sep 17 00:00:00 2001 From: Oliver Beddows Date: Wed, 9 Nov 2016 19:09:22 +0100 Subject: [PATCH 151/272] Moving logs to debug level. --- modules/blocks.js | 4 ++-- modules/loader.js | 4 ++-- modules/transport.js | 12 ++++++------ 3 files changed, 10 insertions(+), 10 deletions(-) diff --git a/modules/blocks.js b/modules/blocks.js index 938f1e01b8d..2caa80e5bc8 100644 --- a/modules/blocks.js +++ b/modules/blocks.js @@ -845,8 +845,8 @@ Blocks.prototype.loadBlocksFromPeer = function (peer, cb) { } else { var id = (block ? block.id : 'null'); - library.logger.error(['Block', id].join(' '), err.toString()); - if (block) { library.logger.error('Block', block); } + library.logger.debug(['Block', id].join(' '), err.toString()); + if (block) { library.logger.debug('Block', block); } library.logger.warn(['Block', id, 'is not valid, ban 60 min'].join(' '), peer.string); modules.peers.state(peer.ip, peer.port, 0, 3600); diff --git a/modules/loader.js b/modules/loader.js index 80510f014a7..03ba3554a62 100644 --- a/modules/loader.js +++ b/modules/loader.js @@ -166,8 +166,8 @@ __private.loadUnconfirmedTransactions = function (cb) { try { transaction = library.logic.transaction.objectNormalize(transaction); } catch (e) { - library.logger.error(['Transaction', id].join(' '), e.toString()); - if (transaction) { library.logger.error('Transaction', transaction); } + library.logger.debug(['Transaction', id].join(' '), e.toString()); + if (transaction) { library.logger.debug('Transaction', transaction); } library.logger.warn(['Transaction', id, 'is not valid, ban 60 min'].join(' '), peer.string); modules.peers.state(peer.ip, peer.port, 0, 3600); diff --git a/modules/transport.js b/modules/transport.js index 3118324ce3d..843cd2af342 100644 --- a/modules/transport.js +++ b/modules/transport.js @@ -147,8 +147,8 @@ __private.attachApi = function () { try { block = library.logic.block.objectNormalize(block); } catch (e) { - library.logger.error(['Block', id].join(' '), e.toString()); - if (block) { library.logger.error('Block', block); } + library.logger.debug(['Block', id].join(' '), e.toString()); + if (block) { library.logger.debug('Block', block); } if (req.peer) { // Ban peer for 60 minutes @@ -208,8 +208,8 @@ __private.attachApi = function () { try { transaction = library.logic.transaction.objectNormalize(transaction); } catch (e) { - library.logger.error(['Transaction', id].join(' '), e.toString()); - if (transaction) { library.logger.error('Transaction', transaction); } + library.logger.debug(['Transaction', id].join(' '), e.toString()); + if (transaction) { library.logger.debug('Transaction', transaction); } if (req.peer) { // Ban peer for 60 minutes @@ -224,8 +224,8 @@ __private.attachApi = function () { modules.transactions.receiveTransactions([transaction], cb); }, function (err) { if (err) { - library.logger.error(['Transaction', id].join(' '), err.toString()); - if (transaction) { library.logger.error('Transaction', transaction); } + library.logger.debug(['Transaction', id].join(' '), err.toString()); + if (transaction) { library.logger.debug('Transaction', transaction); } res.status(200).json({success: false, message: err.toString()}); } else { From 2f01e2868a7bad910aaa4a2967e0183a69b2fd7d Mon Sep 17 00:00:00 2001 From: Oliver Beddows Date: Thu, 10 Nov 2016 18:21:41 +0100 Subject: [PATCH 152/272] Renaming variable. --- modules/multisignatures.js | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/modules/multisignatures.js b/modules/multisignatures.js index 628fde07c0e..cc47f21b7bd 100644 --- a/modules/multisignatures.js +++ b/modules/multisignatures.js @@ -134,7 +134,7 @@ shared.pending = function (req, cb) { return transaction.senderPublicKey === req.body.publicKey; }); - var pendings = []; + var pending = []; async.eachSeries(transactions, function (transaction, cb) { var signed = false; @@ -193,7 +193,7 @@ shared.pending = function (req, cb) { var lifetime = sender.u_multilifetime || sender.multilifetime; var signatures = sender.u_multisignatures || []; - pendings.push({ + pending.push({ max: signatures.length, min: min, lifetime: lifetime, @@ -205,7 +205,7 @@ shared.pending = function (req, cb) { return setImmediate(cb); }); }, function () { - return setImmediate(cb, null, {transactions: pendings}); + return setImmediate(cb, null, {transactions: pending}); }); }); }; From 7e3ae69ad925efd60b7dbbe76160bfb32fa51f51 Mon Sep 17 00:00:00 2001 From: Oliver Beddows Date: Thu, 10 Nov 2016 21:47:02 +0100 Subject: [PATCH 153/272] Changing error messages. --- logic/dapp.js | 2 +- logic/signature.js | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/logic/dapp.js b/logic/dapp.js index ab569a7677d..f1df6b43dc0 100644 --- a/logic/dapp.js +++ b/logic/dapp.js @@ -210,7 +210,7 @@ DApp.prototype.applyUnconfirmed = function (trs, sender, cb) { } else if (dapp.link === trs.asset.dapp.link) { return setImmediate(cb, 'Application link already exists: ' + dapp.link); } else { - return setImmediate(cb, 'Unknown error'); + return setImmediate(cb, 'Application already exists'); } } else { return setImmediate(cb, null, trs); diff --git a/logic/signature.js b/logic/signature.js index a2e8baffb88..5c72b623c0c 100644 --- a/logic/signature.js +++ b/logic/signature.js @@ -91,7 +91,7 @@ Signature.prototype.undo = function (trs, block, sender, cb) { Signature.prototype.applyUnconfirmed = function (trs, sender, cb) { if (sender.u_secondSignature || sender.secondSignature) { - return setImmediate(cb, 'Failed second signature: ' + trs.id); + return setImmediate(cb, 'Second signature already enabled'); } modules.accounts.setAccountAndGet({address: sender.address, u_secondSignature: 1}, cb); From bf9c6d3caedfca3f169a4bd38ccddd2ea0da3973 Mon Sep 17 00:00:00 2001 From: Oliver Beddows Date: Thu, 10 Nov 2016 21:47:49 +0100 Subject: [PATCH 154/272] Logging stack trace on error. --- logic/dapp.js | 1 + 1 file changed, 1 insertion(+) diff --git a/logic/dapp.js b/logic/dapp.js index f1df6b43dc0..60b7a8b9548 100644 --- a/logic/dapp.js +++ b/logic/dapp.js @@ -216,6 +216,7 @@ DApp.prototype.applyUnconfirmed = function (trs, sender, cb) { return setImmediate(cb, null, trs); } }).catch(function (err) { + library.logger.error(err.stack); return setImmediate(cb, 'DApp#applyUnconfirmed error'); }); }; From 18a796f3d8d53be8375349ce0a389f0674951a63 Mon Sep 17 00:00:00 2001 From: Oliver Beddows Date: Sat, 12 Nov 2016 12:57:19 +0100 Subject: [PATCH 155/272] Implementing new transaction pool #306. --- helpers/constants.js | 2 + logic/transactionPool.js | 429 +++++++++++++++++++++++++++++++++++++++ modules/blocks.js | 4 +- modules/delegates.js | 7 +- modules/loader.js | 8 +- modules/transactions.js | 237 +++++++-------------- modules/transport.js | 8 +- schema/loader.js | 9 +- 8 files changed, 526 insertions(+), 178 deletions(-) create mode 100644 logic/transactionPool.js diff --git a/helpers/constants.js b/helpers/constants.js index d0aaac5e87f..0501769f7bb 100644 --- a/helpers/constants.js +++ b/helpers/constants.js @@ -24,8 +24,10 @@ module.exports = { maxConfirmations : 77 * 100, maxPayloadLength: 1024 * 1024, maxRequests: 10000 * 12, + maxSharedTxs: 100, maxSignaturesLength: 196 * 256, maxTxsPerBlock: 25, + maxTxsPerQueue: 10000, minBroadhashEfficiency: 10, numberLength: 100000000, requestLength: 104, diff --git a/logic/transactionPool.js b/logic/transactionPool.js new file mode 100644 index 00000000000..7369aabe3b4 --- /dev/null +++ b/logic/transactionPool.js @@ -0,0 +1,429 @@ +'use strict'; + +var async = require('async'); +var constants = require('../helpers/constants.js'); +var transactionTypes = require('../helpers/transactionTypes.js'); + +// Private fields +var modules, library, self, __private = {}; + +// Constructor +function TransactionPool (scope) { + library = scope; + self = this; + + self.unconfirmed = { transactions: [], index: {} }; + self.queued = { transactions: [], index: {} }; + self.multisignature = { transactions: [], index: {} }; + self.poolInterval = 30000; + + // Transaction pool timer + setInterval(function () { + async.series([ + self.expireTransactions + ], function (err) { + if (err) { + library.logger.log('Transaction pool timer', err); + } + }); + }, self.poolInterval); +} + +// Public methods +TransactionPool.prototype.bind = function (scope) { + modules = scope; +}; + +TransactionPool.prototype.transactionInPool = function (id) { + return [ + self.unconfirmed.index[id], + self.queued.index[id], + self.multisignature.index[id] + ].filter(Boolean).length > 0; +}; + +TransactionPool.prototype.getUnconfirmedTransaction = function (id) { + var index = self.unconfirmed.index[id]; + return self.unconfirmed.transactions[index]; +}; + +TransactionPool.prototype.getQueuedTransaction = function (id) { + var index = self.queued.index[id]; + return self.queued.transactions[index]; +}; + +TransactionPool.prototype.getMultisignatureTransaction = function (id) { + var index = self.multisignature.index[id]; + return self.multisignature.transactions[index]; +}; + +TransactionPool.prototype.getUnconfirmedTransactionList = function (reverse, limit) { + return __private.getTransactionList(self.unconfirmed.transactions, reverse, limit); +}; + +TransactionPool.prototype.getQueuedTransactionList = function (reverse, limit) { + return __private.getTransactionList(self.queued.transactions, reverse, limit); +}; + +TransactionPool.prototype.getMultisignatureTransactionList = function (reverse, ready, limit) { + if (ready) { + return __private.getTransactionList(self.multisignature.transactions, reverse).filter(function (transaction) { + return transaction.ready; + }); + } else { + return __private.getTransactionList(self.multisignature.transactions, reverse, limit); + } +}; + +TransactionPool.prototype.getMergedTransactionList = function (reverse, limit) { + var minLimit = (constants.maxTxsPerBlock + 2); + + if (limit <= minLimit || limit > constants.maxSharedTxs) { + limit = minLimit; + } + + var unconfirmed = modules.transactions.getUnconfirmedTransactionList(false, constants.maxTxsPerBlock); + limit -= unconfirmed.length; + + var multisignatures = modules.transactions.getMultisignatureTransactionList(false, constants.maxTxsPerBlock); + limit -= multisignatures.length; + + var queued = modules.transactions.getQueuedTransactionList(false, limit); + limit -= queued.length; + + return unconfirmed.concat(multisignatures).concat(queued); +}; + +TransactionPool.prototype.addUnconfirmedTransaction = function (transaction) { + if (transaction.type === transactionTypes.MULTI || Array.isArray(transaction.signatures)) { + self.removeMultisignatureTransaction(transaction.id); + } else { + self.removeQueuedTransaction(transaction.id); + } + + if (self.unconfirmed.index[transaction.id] === undefined) { + if (!transaction.receivedAt) { + transaction.receivedAt = new Date(); + } + + self.unconfirmed.transactions.push(transaction); + var index = self.unconfirmed.transactions.indexOf(transaction); + self.unconfirmed.index[transaction.id] = index; + } +}; + +TransactionPool.prototype.removeUnconfirmedTransaction = function (id) { + var index = self.unconfirmed.index[id]; + + if (index !== undefined) { + self.unconfirmed.transactions[index] = false; + delete self.unconfirmed.index[id]; + } + + self.removeQueuedTransaction(id); + self.removeMultisignatureTransaction(id); +}; + +TransactionPool.prototype.countUnconfirmed = function () { + return Object.keys(self.unconfirmed.index).length; +}; + +TransactionPool.prototype.addQueuedTransaction = function (transaction) { + if (self.queued.index[transaction.id] === undefined) { + if (!transaction.receivedAt) { + transaction.receivedAt = new Date(); + } + + self.queued.transactions.push(transaction); + var index = self.queued.transactions.indexOf(transaction); + self.queued.index[transaction.id] = index; + } +}; + +TransactionPool.prototype.removeQueuedTransaction = function (id) { + var index = self.queued.index[id]; + + if (index !== undefined) { + self.queued.transactions[index] = false; + delete self.queued.index[id]; + } +}; + +TransactionPool.prototype.countQueued = function () { + return Object.keys(self.queued.index).length; +}; + +TransactionPool.prototype.addMultisignatureTransaction = function (transaction) { + if (self.multisignature.index[transaction.id] === undefined) { + if (!transaction.receivedAt) { + transaction.receivedAt = new Date(); + } + + self.multisignature.transactions.push(transaction); + var index = self.multisignature.transactions.indexOf(transaction); + self.multisignature.index[transaction.id] = index; + } +}; + +TransactionPool.prototype.removeMultisignatureTransaction = function (id) { + var index = self.multisignature.index[id]; + + if (index !== undefined) { + self.multisignature.transactions[index] = false; + delete self.multisignature.index[id]; + } +}; + +TransactionPool.prototype.countMultisignature= function () { + return Object.keys(self.multisignature.index).length; +}; + +TransactionPool.prototype.receiveTransactions = function (transactions, broadcast, cb) { + async.eachSeries(transactions, function (transaction, cb) { + self.processUnconfirmedTransaction(transaction, broadcast, cb); + }, function (err) { + return setImmediate(cb, err, transactions); + }); +}; + +TransactionPool.prototype.processUnconfirmedTransaction = function (transaction, broadcast, cb) { + if (self.transactionInPool(transaction.id)) { + return setImmediate(cb, 'Transaction is already processed: ' + transaction.id); + } + + __private.processVerifyTransaction(transaction, function (err) { + if (err) { + return setImmediate(cb, err); + } else { + delete transaction.receivedAt; + + if (transaction.type === transactionTypes.MULTI || Array.isArray(transaction.signatures)) { + if (self.countMultisignature() >= constants.maxTxsPerQueue) { + return setImmediate(cb, 'Transaction pool is full'); + } else { + self.addMultisignatureTransaction(transaction); + } + } else { + if (self.countQueued() >= constants.maxTxsPerQueue) { + return setImmediate(cb, 'Transaction pool is full'); + } else { + self.addQueuedTransaction(transaction); + } + } + + library.bus.message('unconfirmedTransaction', transaction, broadcast); + return setImmediate(cb); + } + }); +}; + +TransactionPool.prototype.applyUnconfirmedList = function (cb) { + return __private.applyUnconfirmedList(self.getUnconfirmedTransactionList(true), cb); +}; + +TransactionPool.prototype.applyUnconfirmedIds = function (ids, cb) { + return __private.applyUnconfirmedList(ids, cb); +}; + +TransactionPool.prototype.undoUnconfirmedList = function (cb) { + var ids = []; + + async.eachSeries(self.getUnconfirmedTransactionList(false), function (transaction, eachSeriesCb) { + if (transaction) { + ids.push(transaction.id); + modules.transactions.undoUnconfirmed(transaction, function (err) { + if (err) { + library.logger.error('Failed to undo unconfirmed transaction: ' + transaction.id, err); + self.removeUnconfirmedTransaction(transaction.id); + } + return setImmediate(eachSeriesCb); + }); + } else { + return setImmediate(eachSeriesCb); + } + }, function (err) { + return setImmediate(cb, err, ids); + }); +}; + +TransactionPool.prototype.expireTransactions = function (cb) { + var ids = []; + + async.waterfall([ + function (seriesCb) { + __private.expireTransactions(self.getUnconfirmedTransactionList(true), ids, seriesCb); + }, + function (res, seriesCb) { + __private.expireTransactions(self.getQueuedTransactionList(true), ids, seriesCb); + }, + function (res, seriesCb) { + __private.expireTransactions(self.getMultisignatureTransactionList(true), ids, seriesCb); + } + ], function (err, ids) { + return setImmediate(cb, err, ids); + }); +}; + +TransactionPool.prototype.fillPool = function (cb) { + if (modules.loader.syncing()) { return setImmediate(cb); } + + var unconfirmedCount = self.countUnconfirmed(); + library.logger.debug('Transaction pool size: ' + unconfirmedCount); + + if (unconfirmedCount >= constants.maxTxsPerBlock) { + return setImmediate(cb); + } else { + var spare = 0, spareMulti; + var multisignatures = []; + var multisignaturesLimit = 5; + var transactions = []; + + spare = (constants.maxTxsPerBlock - unconfirmedCount); + spareMulti = (spare >= multisignaturesLimit) ? multisignaturesLimit : 0; + multisignatures = self.getMultisignatureTransactionList(true, true, multisignaturesLimit).slice(0, spareMulti); + spare = Math.abs(spare - multisignatures.length); + transactions = self.getQueuedTransactionList(true, constants.maxTxsPerBlock).slice(0, spare); + transactions = multisignatures.concat(transactions); + + transactions.forEach(function (transaction) { + self.addUnconfirmedTransaction(transaction); + }); + + return __private.applyUnconfirmedList(transactions, cb); + } +}; + +// Private +__private.getTransactionList = function (transactions, reverse, limit) { + var a = []; + + for (var i = 0; i < transactions.length; i++) { + var transaction = transactions[i]; + + if (transaction !== false) { + a.push(transaction); + } + } + + a = reverse ? a.reverse() : a; + + if (limit) { + a.splice(limit); + } + + return a; +}; + +__private.processVerifyTransaction = function (transaction, cb) { + if (!transaction) { + return setImmediate(cb, 'Missing transaction'); + } + + async.waterfall([ + function setAccountAndGet (waterCb) { + modules.accounts.setAccountAndGet({publicKey: transaction.senderPublicKey}, waterCb); + }, + function getRequester (sender, waterCb) { + var multisignatures = Array.isArray(sender.multisignatures) && sender.multisignatures.length; + + if (multisignatures) { + transaction.signatures = transaction.signatures || []; + } + + if (sender && transaction.requesterPublicKey && multisignatures) { + modules.accounts.getAccount({publicKey: transaction.requesterPublicKey}, function (err, requester) { + if (!requester) { + return setImmediate(waterCb, 'Requester not found'); + } else { + return setImmediate(waterCb, null, sender, requester); + } + }); + } else { + return setImmediate(waterCb, null, sender, null); + } + }, + function processTransaction (sender, requester, waterCb) { + library.logic.transaction.process(transaction, sender, requester, function (err) { + if (err) { + return setImmediate(waterCb, err); + } else { + return setImmediate(waterCb, null, sender); + } + }); + }, + function verifyTransaction (sender, waterCb) { + library.logic.transaction.verify(transaction, sender, function (err) { + if (err) { + return setImmediate(waterCb, err); + } else { + return setImmediate(waterCb, null, sender); + } + }); + } + ], function (err, sender) { + return setImmediate(cb, err, sender); + }); +}; + +__private.applyUnconfirmedList = function (transactions, cb) { + async.eachSeries(transactions, function (transaction, eachSeriesCb) { + if (typeof transaction === 'string') { + transaction = self.getUnconfirmedTransaction(transaction); + } + if (!transaction) { + return setImmediate(eachSeriesCb); + } + __private.processVerifyTransaction(transaction, function (err, sender) { + if (err) { + library.logger.error('Failed to process / verify unconfirmed transaction: ' + transaction.id, err); + self.removeUnconfirmedTransaction(transaction.id); + return setImmediate(eachSeriesCb); + } + modules.transactions.applyUnconfirmed(transaction, sender, function (err) { + if (err) { + library.logger.error('Failed to apply unconfirmed transaction: ' + transaction.id, err); + self.removeUnconfirmedTransaction(transaction.id); + } + return setImmediate(eachSeriesCb); + }); + }); + }, cb); +}; + +__private.transactionTimeOut = function (transaction) { + if (transaction.type === transactionTypes.MULTI) { + return (transaction.asset.multisignature.lifetime * 3600); + } else if (Array.isArray(transaction.signatures)) { + return (constants.unconfirmedTransactionTimeOut * 8); + } else { + return (constants.unconfirmedTransactionTimeOut); + } +}; + +__private.expireTransactions = function (transactions, parentIds, cb) { + var ids = []; + + async.eachSeries(transactions, function (transaction, eachSeriesCb) { + if (!transaction) { + return setImmediate(eachSeriesCb); + } + + var timeNow = new Date(); + var timeOut = __private.transactionTimeOut(transaction); + var seconds = Math.floor((timeNow.getTime() - transaction.receivedAt.getTime()) / 1000); + + if (seconds > timeOut) { + ids.push(transaction.id); + self.removeUnconfirmedTransaction(transaction.id); + library.logger.info('Expired transaction: ' + transaction.id + ' received at: ' + transaction.receivedAt.toUTCString()); + return setImmediate(eachSeriesCb); + } else { + return setImmediate(eachSeriesCb); + } + }, function (err) { + return setImmediate(cb, err, ids.concat(parentIds)); + }); +}; + +// Export +module.exports = TransactionPool; diff --git a/modules/blocks.js b/modules/blocks.js index 2caa80e5bc8..0028a966bb9 100644 --- a/modules/blocks.js +++ b/modules/blocks.js @@ -554,9 +554,9 @@ __private.applyBlock = function (block, broadcast, cb, saveBlock) { }, // Push back unconfirmed transactions list (minus the one that were on the block if applied correctly). // TODO: See undoUnconfirmedList discussion above. - applyUnconfirmedList: function (seriesCb) { + applyUnconfirmedIds: function (seriesCb) { // DATABASE write - modules.transactions.applyUnconfirmedList(unconfirmedTransactions, function (err) { + modules.transactions.applyUnconfirmedIds(unconfirmedTransactionIds, function (err) { return setImmediate(seriesCb, err); }); }, diff --git a/modules/delegates.js b/modules/delegates.js index bcd8d867f38..4916d8d8a4c 100644 --- a/modules/delegates.js +++ b/modules/delegates.js @@ -555,8 +555,11 @@ Delegates.prototype.onBlockchainReady = function () { library.logger.error('Failed to load delegates', err); } - __private.forge(function () { - setTimeout(nextForge, 1000); + async.series([ + modules.transactions.fillPool, + __private.forge + ], function (err) { + return setTimeout(nextForge, 1000); }); }); }; diff --git a/modules/loader.js b/modules/loader.js index 03ba3554a62..dbc5d931c11 100644 --- a/modules/loader.js +++ b/modules/loader.js @@ -445,8 +445,6 @@ __private.loadBlocksFromNetwork = function (cb) { }; __private.sync = function (cb) { - var transactions = modules.transactions.getUnconfirmedTransactionList(true); - library.logger.info('Starting sync'); __private.isActive = true; @@ -463,9 +461,9 @@ __private.sync = function (cb) { updateSystem: function (seriesCb) { return modules.system.update(seriesCb); }, - receiveTransactions: function (seriesCb) { - library.logger.debug('Receiving unconfirmed transactions after sync'); - return modules.transactions.receiveTransactions(transactions, seriesCb); + applyUnconfirmedList: function (seriesCb) { + library.logger.debug('Applying unconfirmed transactions after sync'); + return modules.transactions.applyUnconfirmedList(seriesCb); } }, function (err) { __private.isActive = false; diff --git a/modules/transactions.js b/modules/transactions.js index ad66211a152..661428f1aff 100644 --- a/modules/transactions.js +++ b/modules/transactions.js @@ -12,14 +12,14 @@ var sandboxHelper = require('../helpers/sandbox.js'); var schema = require('../schema/transactions.js'); var slots = require('../helpers/slots.js'); var sql = require('../sql/transactions.js'); +var TransactionPool = require('../logic/transactionPool.js'); var transactionTypes = require('../helpers/transactionTypes.js'); +var Transfer = require('../logic/transfer.js'); // Private fields var modules, library, self, __private = {}, shared = {}; __private.assetTypes = {}; -__private.unconfirmedTransactions = []; -__private.unconfirmedTransactionsIdIndex = {}; // Constructor function Transactions (cb, scope) { @@ -28,8 +28,8 @@ function Transactions (cb, scope) { self = this; __private.attachApi(); + __private.transactionPool = new TransactionPool(library); - var Transfer = require('../logic/transfer.js'); __private.assetTypes[transactionTypes.SEND] = library.logic.transaction.attachAssetType( transactionTypes.SEND, new Transfer() ); @@ -183,185 +183,100 @@ __private.getById = function (id, cb) { }); }; -__private.addUnconfirmedTransaction = function (transaction, sender, cb) { - self.applyUnconfirmed(transaction, sender, function (err) { +__private.getPooledTransaction = function (method, req, cb) { + library.schema.validate(req.body, schema.getPooledTransaction, function (err) { if (err) { - self.removeUnconfirmedTransaction(transaction.id); - return setImmediate(cb, err); - } else { - transaction.receivedAt = new Date(); - __private.unconfirmedTransactions.push(transaction); - var index = __private.unconfirmedTransactions.length - 1; - __private.unconfirmedTransactionsIdIndex[transaction.id] = index; - - return setImmediate(cb); + return setImmediate(cb, err[0].message); } - }); -}; - -// Public methods -Transactions.prototype.getUnconfirmedTransaction = function (id) { - var index = __private.unconfirmedTransactionsIdIndex[id]; - return __private.unconfirmedTransactions[index]; -}; -Transactions.prototype.getUnconfirmedTransactionList = function (reverse, limit) { - var a = []; + var transaction = self[method](req.body.id); - for (var i = 0; i < __private.unconfirmedTransactions.length; i++) { - if (__private.unconfirmedTransactions[i] !== false) { - a.push(__private.unconfirmedTransactions[i]); + if (!transaction) { + return setImmediate(cb, 'Transaction not found'); } - } - a = reverse ? a.reverse() : a; - - if (limit) { - a.splice(limit); - } - - return a; -}; - -Transactions.prototype.removeUnconfirmedTransaction = function (id) { - var index = __private.unconfirmedTransactionsIdIndex[id]; - delete __private.unconfirmedTransactionsIdIndex[id]; - __private.unconfirmedTransactions[index] = false; + return setImmediate(cb, null, {transaction: transaction}); + }); }; -Transactions.prototype.processUnconfirmedTransaction = function (transaction, broadcast, cb) { - // Check transaction - if (!transaction) { - return setImmediate(cb, 'Missing transaction'); - } - - // Ignore transaction when syncing - if (modules.loader.syncing()) { - return setImmediate(cb); - } - - // Check transaction indexes - if (__private.unconfirmedTransactionsIdIndex[transaction.id] !== undefined) { - return setImmediate(cb, 'Transaction is already processed: ' + transaction.id); - } +__private.getPooledTransactions = function (method, req, cb) { + library.schema.validate(req.body, schema.getPooledTransactions, function (err) { + if (err) { + return setImmediate(cb, err[0].message); + } - modules.accounts.setAccountAndGet({publicKey: transaction.senderPublicKey}, function (err, sender) { - function done (err) { - if (err) { - return setImmediate(cb, err); - } + var transactions = self[method](true); + var i, toSend = []; - __private.addUnconfirmedTransaction(transaction, sender, function (err) { - if (err) { - return setImmediate(cb, err); + if (req.body.senderPublicKey || req.body.address) { + for (i = 0; i < transactions.length; i++) { + if (transactions[i].senderPublicKey === req.body.senderPublicKey || transactions[i].recipientId === req.body.address) { + toSend.push(transactions[i]); } - - library.bus.message('unconfirmedTransaction', transaction, broadcast); - - return setImmediate(cb); - }); + } + } else { + for (i = 0; i < transactions.length; i++) { + toSend.push(transactions[i]); + } } - if (err) { - return done(err); - } + return setImmediate(cb, null, {transactions: toSend, count: transactions.length}); + }); +}; + return __private.transactionPool.addUnconfirmedTransaction(transaction, sender, cb); +}; - if (transaction.requesterPublicKey && sender && Array.isArray(sender.multisignatures) && sender.multisignatures.length) { - modules.accounts.getAccount({publicKey: transaction.requesterPublicKey}, function (err, requester) { - if (err) { - return done(err); - } +// Public methods +Transactions.prototype.transactionInPool = function (id) { + return __private.transactionPool.transactionInPool(id); +}; - if (!requester) { - return done('Requester not found'); - } +Transactions.prototype.getUnconfirmedTransaction = function (id) { + return __private.transactionPool.getUnconfirmedTransaction(id); +}; - library.logic.transaction.process(transaction, sender, requester, function (err, transaction) { - if (err) { - return done(err); - } +Transactions.prototype.getQueuedTransaction = function (id) { + return __private.transactionPool.getQueuedTransaction(id); +}; - library.logic.transaction.verify(transaction, sender, done); - }); - }); - } else { - library.logic.transaction.process(transaction, sender, function (err, transaction) { - if (err) { - return done(err); - } +Transactions.prototype.getMultisignatureTransaction = function (id) { + return __private.transactionPool.getMultisignatureTransaction(id); +}; - library.logic.transaction.verify(transaction, sender, done); - }); - } - }); +Transactions.prototype.getUnconfirmedTransactionList = function (reverse, limit) { + return __private.transactionPool.getUnconfirmedTransactionList(reverse, limit); }; -Transactions.prototype.applyUnconfirmedList = function (ids, cb) { - async.eachSeries(ids, function (id, eachSeriesCb) { - var transaction = self.getUnconfirmedTransaction(id); - if (!transaction) { - return setImmediate(eachSeriesCb); - } - modules.accounts.setAccountAndGet({publicKey: transaction.senderPublicKey}, function (err, sender) { - if (err) { - self.removeUnconfirmedTransaction(id); - return setImmediate(eachSeriesCb, err); - } - self.applyUnconfirmed(transaction, sender, function (err) { - if (err) { - self.removeUnconfirmedTransaction(id); - } - return setImmediate(eachSeriesCb, err); - }); - }); - }, cb); +Transactions.prototype.getQueuedTransactionList = function (reverse, limit) { + return __private.transactionPool.getQueuedTransactionList(reverse, limit); }; -Transactions.prototype.undoUnconfirmedList = function (cb) { - var ids = []; +Transactions.prototype.getMultisignatureTransactionList = function (reverse, limit) { + return __private.transactionPool.getMultisignatureTransactionList(reverse, limit); +}; - async.eachSeries(__private.unconfirmedTransactions, function (transaction, cb) { - if (transaction !== false) { - ids.push(transaction.id); - self.undoUnconfirmed(transaction, cb); - } else { - return setImmediate(cb); - } - }, function (err) { - return setImmediate(cb, err, ids); - }); +Transactions.prototype.getMergedTransactionList = function (reverse, limit) { + return __private.transactionPool.getMergedTransactionList(reverse, limit); }; -Transactions.prototype.expireUnconfirmedList = function (cb) { - var standardTimeOut = Number(constants.unconfirmedTransactionTimeOut); - var ids = []; +Transactions.prototype.removeUnconfirmedTransaction = function (id) { + return __private.transactionPool.removeUnconfirmedTransaction(id); +}; - async.eachSeries(__private.unconfirmedTransactions, function (transaction, cb) { - if (transaction === false) { - return setImmediate(cb); - } +Transactions.prototype.processUnconfirmedTransaction = function (transaction, broadcast, cb) { + return __private.transactionPool.processUnconfirmedTransaction(transaction, broadcast, cb); +}; - var timeNow = new Date(); - var timeOut = (transaction.type === 4) ? (transaction.asset.multisignature.lifetime * 3600) : standardTimeOut; - var seconds = Math.floor((timeNow.getTime() - transaction.receivedAt.getTime()) / 1000); +Transactions.prototype.applyUnconfirmedList = function (cb) { + return __private.transactionPool.applyUnconfirmedList(cb); +}; - if (seconds > timeOut) { - self.undoUnconfirmed(transaction, function (err) { - if (err) { - return setImmediate(cb, err); - } else { - ids.push(transaction.id); - self.removeUnconfirmedTransaction(transaction.id); - library.logger.info('Expired unconfirmed transaction: ' + transaction.id + ' received at: ' + transaction.receivedAt.toUTCString()); - return setImmediate(cb); - } - }); - } else { - return setImmediate(cb); - } - }, function (err) { - return setImmediate(cb, err, ids); - }); +Transactions.prototype.applyUnconfirmedIds = function (ids, cb) { + return __private.transactionPool.applyUnconfirmedIds(ids, cb); +}; + +Transactions.prototype.undoUnconfirmedList = function (cb) { + return __private.transactionPool.undoUnconfirmedList(cb); }; Transactions.prototype.apply = function (transaction, block, sender, cb) { @@ -417,6 +332,10 @@ Transactions.prototype.receiveTransactions = function (transactions, cb) { }); }; +Transactions.prototype.fillPool = function (cb) { + return __private.transactionPool.fillPool(cb); +}; + Transactions.prototype.sandboxApi = function (call, args, cb) { sandboxHelper.callMethod(shared, call, args, cb); }; @@ -425,21 +344,13 @@ Transactions.prototype.sandboxApi = function (call, args, cb) { Transactions.prototype.onBind = function (scope) { modules = scope; + __private.transactionPool.bind(modules); __private.assetTypes[transactionTypes.SEND].bind({ modules: modules, library: library }); }; Transactions.prototype.onPeersReady = function () { - setImmediate(function nextUnconfirmedExpiry () { - self.expireUnconfirmedList(function (err, ids) { - if (err) { - library.logger.error('Unconfirmed transactions timer', err); - } - - setTimeout(nextUnconfirmedExpiry, 30000); - }); - }); }; // Shared diff --git a/modules/transport.js b/modules/transport.js index 843cd2af342..59fd1ea74bf 100644 --- a/modules/transport.js +++ b/modules/transport.js @@ -180,10 +180,10 @@ __private.attachApi = function () { }); router.get('/signatures', function (req, res) { - var unconfirmedList = modules.transactions.getUnconfirmedTransactionList(); + var transactions = modules.transactions.getMultisignatureTransactionList(true, constants.maxSharedTxs); var signatures = []; - async.eachSeries(unconfirmedList, function (trs, cb) { + async.eachSeries(transactions, function (trs, cb) { if (trs.signatures && trs.signatures.length) { signatures.push({ transaction: trs.id, @@ -198,7 +198,9 @@ __private.attachApi = function () { }); router.get('/transactions', function (req, res) { - res.status(200).json({success: true, transactions: modules.transactions.getUnconfirmedTransactionList()}); + var transactions = modules.transactions.getMergedTransactionList(true, constants.maxSharedTxs); + + res.status(200).json({success: true, transactions: transactions}); }); router.post('/transactions', function (req, res) { diff --git a/schema/loader.js b/schema/loader.js index 62a08ec6f60..5e0b0c2ad2b 100644 --- a/schema/loader.js +++ b/schema/loader.js @@ -7,7 +7,8 @@ module.exports = { properties: { signatures: { type: 'array', - uniqueItems: true + uniqueItems: true, + maxItems: 100 } }, required: ['signatures'] @@ -18,7 +19,8 @@ module.exports = { properties: { transactions: { type: 'array', - uniqueItems: true + uniqueItems: true, + maxItems: 100 } }, required: ['transactions'] @@ -30,7 +32,8 @@ module.exports = { properties: { peers: { type: 'array', - uniqueItems: true + uniqueItems: true, + maxItems: 100 } }, required: ['peers'] From 29fdd30aea7b4c4e216f33cfdc970a1268fa1b67 Mon Sep 17 00:00:00 2001 From: Oliver Beddows Date: Sat, 12 Nov 2016 12:58:55 +0100 Subject: [PATCH 156/272] Implementing queued broadcasts #306. --- logic/broadcaster.js | 157 +++++++++++++++++++++++++++++++++++++++++++ modules/transport.js | 49 +++----------- 2 files changed, 166 insertions(+), 40 deletions(-) create mode 100644 logic/broadcaster.js diff --git a/logic/broadcaster.js b/logic/broadcaster.js new file mode 100644 index 00000000000..3ceecf96d71 --- /dev/null +++ b/logic/broadcaster.js @@ -0,0 +1,157 @@ +'use strict'; + +var async = require('async'); +var constants = require('../helpers/constants.js'); +var extend = require('extend'); + +// Private fields +var modules, library, self, __private = {}; + +// Constructor +function Broadcaster (scope) { + library = scope; + self = this; + + self.queue = []; + self.peerLimit = 100; + self.broadcastLimit = 20; + self.releaseLimit = constants.maxTxsPerBlock; + self.broadcastInterval = 5000; + + // Optionally ignore broadhash efficiency + if (!library.config.forging.force) { + self.efficiency = 100; + } else { + self.efficiency = undefined; + } + + // Broadcaster timer + setInterval(function () { + async.series([ + __private.releaseQueue + ], function (err) { + if (err) { + library.logger.log('Broadcaster timer', err); + } + }); + }, self.broadcastInterval); +} + +// Public methods +Broadcaster.prototype.bind = function (scope) { + modules = scope; +}; + +Broadcaster.prototype.getPeers = function (params, cb) { + params.limit = params.limit || self.peerLimit; + params.broadhash = params.broadhash || null; + + modules.peers.list(params, function (err, peers, efficiency) { + if (err) { + return setImmediate(cb, err); + } + + if (self.efficiency !== undefined) { + self.efficiency = efficiency; + } + + return setImmediate(cb, null, peers); + }); +}; + +Broadcaster.prototype.enqueue = function (params, options) { + return self.queue.push({params: params, options: options}); +}; + +Broadcaster.prototype.broadcast = function (params, options, cb) { + params.limit = params.limit || self.peerLimit; + params.broadhash = params.broadhash || null; + + async.waterfall([ + function getPeers (waterCb) { + if (!params.peers) { + return self.getPeers(params, waterCb); + } else { + return setImmediate(waterCb, null, params.peers); + } + }, + function getFromPeer (peers, waterCb) { + library.logger.debug('Begin broadcast', options); + + async.eachLimit(peers, self.broadcastLimit, function (peer, eachLimitCb) { + peer = modules.peers.accept(peer); + + modules.transport.getFromPeer(peer, options, function (err) { + if (err) { + library.logger.debug('Failed to broadcast to peer: ' + peer.string, err); + } + return setImmediate(eachLimitCb); + }); + }, function (err) { + library.logger.debug('End broadcast'); + return setImmediate(waterCb, err, peers); + }); + } + ], function (err, peers) { + if (cb) { + return setImmediate(cb, err, {body: null, peer: peers}); + } + }); +}; + +// Private +__private.cleanQueue = function (cb) { + library.logger.debug('Broadcasts before cleaning: ' + self.queue.length); + + self.queue = self.queue.filter(function (broadcast) { + if (!broadcast.options || !broadcast.options.data) { + return false; + } else if (broadcast.options.data.transaction) { + var transaction = broadcast.options.data.transaction; + + if (transaction !== undefined) { + return modules.transactions.transactionInPool(transaction.id); + } else { + return false; + } + } else { + return true; + } + }); + + library.logger.debug('Broadcasts after cleaning: ' + self.queue.length); + + return setImmediate(cb); +}; + +__private.releaseQueue = function (cb) { + var broadcasts; + + library.logger.debug('Releasing enqueued broadcasts'); + + async.waterfall([ + function cleanQueue (waterCb) { + return __private.cleanQueue(waterCb); + }, + function getPeers (waterCb) { + return self.getPeers({}, waterCb); + }, + function broadcast (peers, waterCb) { + broadcasts = self.queue.splice(0, self.releaseLimit); + + async.eachSeries(broadcasts, function (broadcast, eachSeriesCb) { + self.broadcast(extend({peers: peers}, broadcast.params), broadcast.options, eachSeriesCb); + }, waterCb); + } + ], function (err) { + if (err) { + library.logger.debug('Failed to release broadcast queue', err); + } else { + library.logger.debug('Broadcasts released: ' + broadcasts.length); + } + return setImmediate(cb); + }); +}; + +// Export +module.exports = Broadcaster; diff --git a/modules/transport.js b/modules/transport.js index 59fd1ea74bf..61bb2124813 100644 --- a/modules/transport.js +++ b/modules/transport.js @@ -2,6 +2,7 @@ var _ = require('lodash'); var async = require('async'); +var Broadcaster = require('../logic/broadcaster.js'); var bignum = require('../helpers/bignum.js'); var crypto = require('crypto'); var extend = require('extend'); @@ -26,11 +27,7 @@ function Transport (cb, scope) { self = this; __private.attachApi(); - - // Optionally ignore broadhash efficiency - if (!library.config.forging.force) { - __private.efficiency = 100; - } + __private.broadcaster = new Broadcaster(library); setImmediate(cb, null, self); } @@ -80,7 +77,6 @@ __private.attachApi = function () { return next(); }); - }); router.get('/list', function (req, res) { @@ -360,34 +356,7 @@ Transport.prototype.headers = function (headers) { }; Transport.prototype.efficiency = function () { - return __private.efficiency === undefined ? 100 : __private.efficiency; -}; - -Transport.prototype.broadcast = function (config, options, cb) { - library.logger.debug('Begin broadcast', options); - - config.limit = config.limit || 1; - config.broadhash = config.broadhash || null; - config.height = config.height || null; - - modules.peers.list(config, function (err, peers, efficiency) { - if (!err) { - if (__private.efficiency !== undefined) { - __private.efficiency = efficiency; - } - - async.eachLimit(peers, 20, function (peer, cb) { - return self.getFromPeer(peer, options, cb); - }, function (err) { - library.logger.debug('End broadcast'); - if (cb) { - return setImmediate(cb, null, {body: null, peer: peers}); - } - }); - } else if (cb) { - return setImmediate(cb, err); - } - }); + return __private.broadcaster.efficiency; }; Transport.prototype.getFromRandomPeer = function (config, options, cb) { @@ -490,6 +459,7 @@ Transport.prototype.onBind = function (scope) { modules = scope; __private.headers = modules.system.headers(); + __private.broadcaster.bind(modules); }; Transport.prototype.onBlockchainReady = function () { @@ -498,14 +468,14 @@ Transport.prototype.onBlockchainReady = function () { Transport.prototype.onSignature = function (signature, broadcast) { if (broadcast) { - self.broadcast({limit: 100}, {api: '/signatures', data: {signature: signature}, method: 'POST'}); + __private.broadcaster.enqueue({}, {api: '/signatures', data: {signature: signature}, method: 'POST'}); library.network.io.sockets.emit('signature/change', signature); } }; Transport.prototype.onUnconfirmedTransaction = function (transaction, broadcast) { if (broadcast) { - self.broadcast({limit: 100}, {api: '/transactions', data: {transaction: transaction}, method: 'POST'}); + __private.broadcaster.enqueue({}, {api: '/transactions', data: {transaction: transaction}, method: 'POST'}); library.network.io.sockets.emit('transactions/change', transaction); } }; @@ -513,10 +483,9 @@ Transport.prototype.onUnconfirmedTransaction = function (transaction, broadcast) Transport.prototype.onNewBlock = function (block, broadcast) { if (broadcast) { var broadhash = modules.system.getBroadhash(); - var height = modules.system.getHeight(); modules.system.update(function () { - self.broadcast({limit: 100, broadhash: broadhash, height: height}, {api: '/blocks', data: {block: block}, method: 'POST'}); + __private.broadcaster.broadcast({broadhash: broadhash}, {api: '/blocks', data: {block: block}, method: 'POST'}); library.network.io.sockets.emit('blocks/change', block); }); } @@ -524,7 +493,7 @@ Transport.prototype.onNewBlock = function (block, broadcast) { Transport.prototype.onMessage = function (msg, broadcast) { if (broadcast) { - self.broadcast({limit: 100, dappid: msg.dappid}, {api: '/dapp/message', data: msg, method: 'POST'}); + __private.broadcaster.broadcast({dappid: msg.dappid}, {api: '/dapp/message', data: msg, method: 'POST'}); } }; @@ -538,7 +507,7 @@ shared.message = function (msg, cb) { msg.timestamp = (new Date()).getTime(); msg.hash = __private.hashsum(msg.body, msg.timestamp); - self.broadcast({limit: 100, dappid: msg.dappid}, {api: '/dapp/message', data: msg, method: 'POST'}); + __private.broadcaster.enqueue({dappid: msg.dappid}, {api: '/dapp/message', data: msg, method: 'POST'}); return setImmediate(cb, null, {}); }; From 2ee299715fd20b71704d7b6beae42a2f128ad549 Mon Sep 17 00:00:00 2001 From: Oliver Beddows Date: Sat, 12 Nov 2016 13:06:33 +0100 Subject: [PATCH 157/272] Revising transaction validations #306. - Moving from applyUnconfirmed to verify. - Removing duplicated validations from applyUnconfirmed. --- logic/dapp.js | 46 ++++++++++++------------ logic/delegate.js | 38 +++++--------------- logic/multisignature.js | 8 ++--- logic/transaction.js | 80 +++++++++++++++-------------------------- logic/transfer.js | 5 ++- logic/vote.js | 57 ++++++++++++++++++----------- modules/delegates.js | 2 +- 7 files changed, 104 insertions(+), 132 deletions(-) diff --git a/logic/dapp.js b/logic/dapp.js index 60b7a8b9548..0a85ff5a012 100644 --- a/logic/dapp.js +++ b/logic/dapp.js @@ -131,7 +131,28 @@ DApp.prototype.verify = function (trs, sender, cb) { } } - return setImmediate(cb); + library.db.query(sql.getExisting, { + name: trs.asset.dapp.name, + link: trs.asset.dapp.link || null, + transactionId: trs.id + }).then(function (rows) { + var dapp = rows[0]; + + if (dapp) { + if (dapp.name === trs.asset.dapp.name) { + return setImmediate(cb, 'Application name already exists: ' + dapp.name); + } else if (dapp.link === trs.asset.dapp.link) { + return setImmediate(cb, 'Application link already exists: ' + dapp.link); + } else { + return setImmediate(cb, 'Application already exists'); + } + } else { + return setImmediate(cb, null, trs); + } + }).catch(function (err) { + library.logger.error(err.stack); + return setImmediate(cb, 'DApp#verify error'); + }); }; DApp.prototype.process = function (trs, sender, cb) { @@ -197,28 +218,7 @@ DApp.prototype.applyUnconfirmed = function (trs, sender, cb) { __private.unconfirmedNames[trs.asset.dapp.name] = true; __private.unconfirmedLinks[trs.asset.dapp.link] = true; - library.db.query(sql.getExisting, { - name: trs.asset.dapp.name, - link: trs.asset.dapp.link || null, - transactionId: trs.id - }).then(function (rows) { - var dapp = rows[0]; - - if (dapp) { - if (dapp.name === trs.asset.dapp.name) { - return setImmediate(cb, 'Application name already exists: ' + dapp.name); - } else if (dapp.link === trs.asset.dapp.link) { - return setImmediate(cb, 'Application link already exists: ' + dapp.link); - } else { - return setImmediate(cb, 'Application already exists'); - } - } else { - return setImmediate(cb, null, trs); - } - }).catch(function (err) { - library.logger.error(err.stack); - return setImmediate(cb, 'DApp#applyUnconfirmed error'); - }); + return setImmediate(cb); }; DApp.prototype.undoUnconfirmed = function (trs, sender, cb) { diff --git a/logic/delegate.js b/logic/delegate.js index 1bb001037ef..c37674b1ab6 100644 --- a/logic/delegate.js +++ b/logic/delegate.js @@ -147,38 +147,18 @@ Delegate.prototype.undo = function (trs, block, sender, cb) { }; Delegate.prototype.applyUnconfirmed = function (trs, sender, cb) { - if (sender.u_isDelegate) { - return setImmediate(cb, 'Account is already a delegate'); - } - - function done () { - var data = { - address: sender.address, - u_isDelegate: 1, - isDelegate: 0 - }; - - if (trs.asset.delegate.username) { - data.username = null; - data.u_username = trs.asset.delegate.username; - } + var data = { + address: sender.address, + u_isDelegate: 1, + isDelegate: 0 + }; - modules.accounts.setAccountAndGet(data, cb); + if (trs.asset.delegate.username) { + data.username = null; + data.u_username = trs.asset.delegate.username; } - modules.accounts.getAccount({ - u_username: trs.asset.delegate.username - }, function (err, account) { - if (err) { - return setImmediate(cb, err); - } - - if (account) { - return setImmediate(cb, 'Username already exists'); - } - - done(); - }); + modules.accounts.setAccountAndGet(data, cb); }; Delegate.prototype.undoUnconfirmed = function (trs, sender, cb) { diff --git a/logic/multisignature.js b/logic/multisignature.js index 9faf7d3a82f..50de91feba0 100644 --- a/logic/multisignature.js +++ b/logic/multisignature.js @@ -60,6 +60,10 @@ Multisignature.prototype.verify = function (trs, sender, cb) { return setImmediate(cb, 'Invalid multisignature lifetime. Must be between 1 and 72'); } + if (Array.isArray(sender.multisignatures) && sender.multisignatures.length) { + return setImmediate(cb, 'Account already has multisignatures enabled'); + } + if (this.ready(trs, sender)) { try { for (var s = 0; s < trs.asset.multisignature.keysgroup.length; s++) { @@ -195,10 +199,6 @@ Multisignature.prototype.applyUnconfirmed = function (trs, sender, cb) { return setImmediate(cb, 'Signature on this account is pending confirmation'); } - if (Array.isArray(sender.multisignatures) && sender.multisignatures.length) { - return setImmediate(cb, 'Account already has multisignatures enabled'); - } - __private.unconfirmedSignatures[sender.address] = true; this.scope.account.merge(sender.address, { diff --git a/logic/transaction.js b/logic/transaction.js index 798c44a1681..2ae8bae5eea 100644 --- a/logic/transaction.js +++ b/logic/transaction.js @@ -239,6 +239,11 @@ Transaction.prototype.process = function (trs, sender, requester, cb) { // return setImmediate(cb, 'Transaction is not ready: ' + trs.id); // } + // Check sender + if (!sender) { + return setImmediate(cb, 'Missing sender'); + } + // Get transaction id var txId; @@ -256,26 +261,9 @@ Transaction.prototype.process = function (trs, sender, requester, cb) { trs.id = txId; } - // Check sender - if (!sender) { - return setImmediate(cb, 'Missing sender'); - } - // Equalize sender address trs.senderId = sender.address; - // Check requester public key - if (trs.requesterPublicKey) { - if (sender.multisignatures.indexOf(trs.requesterPublicKey) < 0) { - return setImmediate(cb, 'Invalid requester public key'); - } - } - - // Verify signature - if (!this.verifySignature(trs, (trs.requesterPublicKey || trs.senderPublicKey), trs.signature)) { - return setImmediate(cb, 'Failed to verify signature'); - } - // Call process on transaction type __private.types[trs.type].process.call(this, trs, sender, function (err, trs) { if (err) { @@ -294,14 +282,34 @@ Transaction.prototype.verify = function (trs, sender, requester, cb) { cb = requester; } + // Check sender + if (!sender) { + return setImmediate(cb, 'Missing sender'); + } + // Check transaction type if (!__private.types[trs.type]) { return setImmediate(cb, 'Unknown transaction type ' + trs.type); } - // Check sender - if (!sender) { - return setImmediate(cb, 'Missing sender'); + // Check for missing sender second signature + if (!trs.requesterPublicKey && sender.secondSignature && !trs.signSignature && trs.blockId !== genesisblock.block.id) { + return setImmediate(cb, 'Missing sender second signature'); + } + + // If second signature provided, check if sender has one enabled + if (!trs.requesterPublicKey && !sender.secondSignature && (trs.signSignature && trs.signSignature.length > 0)) { + return setImmediate(cb, 'Sender does not have a second signature'); + } + + // Check for missing requester second signature + if (trs.requesterPublicKey && requester.secondSignature && !trs.signSignature) { + return setImmediate(cb, 'Missing requester second signature'); + } + + // If second signature provided, check if requester has one enabled + if (trs.requesterPublicKey && !requester.secondSignature && (trs.signSignature && trs.signSignature.length > 0)) { + return setImmediate(cb, 'Requester does not have a second signature'); } // Check sender public key @@ -509,10 +517,6 @@ Transaction.prototype.verifyBytes = function (bytes, publicKey, signature) { }; Transaction.prototype.apply = function (trs, block, sender, cb) { - if (!__private.types[trs.type]) { - return setImmediate(cb, 'Unknown transaction type ' + trs.type); - } - if (!this.ready(trs, sender)) { return setImmediate(cb, 'Transaction is not ready'); } @@ -553,10 +557,6 @@ Transaction.prototype.apply = function (trs, block, sender, cb) { }; Transaction.prototype.undo = function (trs, block, sender, cb) { - if (!__private.types[trs.type]) { - return setImmediate(cb, 'Unknown transaction type ' + trs.type); - } - var amount = bignum(trs.amount.toString()); amount = amount.plus(trs.fee.toString()).toNumber(); @@ -590,26 +590,6 @@ Transaction.prototype.applyUnconfirmed = function (trs, sender, requester, cb) { cb = requester; } - if (!__private.types[trs.type]) { - return setImmediate(cb, 'Unknown transaction type ' + trs.type); - } - - if (!trs.requesterPublicKey && sender.secondSignature && !trs.signSignature && trs.blockId !== genesisblock.block.id) { - return setImmediate(cb, 'Missing sender second signature'); - } - - if (!trs.requesterPublicKey && !sender.secondSignature && (trs.signSignature && trs.signSignature.length > 0)) { - return setImmediate(cb, 'Sender does not have a second signature'); - } - - if (trs.requesterPublicKey && requester.secondSignature && !trs.signSignature) { - return setImmediate(cb, 'Missing requester second signature'); - } - - if (trs.requesterPublicKey && !requester.secondSignature && (trs.signSignature && trs.signSignature.length > 0)) { - return setImmediate(cb, 'Requester does not have a second signature'); - } - // Check unconfirmed sender balance var amount = bignum(trs.amount.toString()).plus(trs.fee.toString()); var senderBalance = this.checkBalance(amount, 'u_balance', trs, sender); @@ -638,10 +618,6 @@ Transaction.prototype.applyUnconfirmed = function (trs, sender, requester, cb) { }; Transaction.prototype.undoUnconfirmed = function (trs, sender, cb) { - if (!__private.types[trs.type]) { - return setImmediate(cb, 'Unknown transaction type ' + trs.type); - } - var amount = bignum(trs.amount.toString()); amount = amount.plus(trs.fee.toString()).toNumber(); diff --git a/logic/transfer.js b/logic/transfer.js index 3ecb9a359b6..4b9d3e55560 100644 --- a/logic/transfer.js +++ b/logic/transfer.js @@ -26,9 +26,8 @@ Transfer.prototype.calculateFee = function (trs, sender) { }; Transfer.prototype.verify = function (trs, sender, cb) { - var isAddress = /^[0-9]{1,21}[L|l]$/g; - if (!trs.recipientId || !isAddress.test(trs.recipientId)) { - return setImmediate(cb, 'Invalid recipient'); + if (!trs.recipientId) { + return setImmediate(cb, 'Missing recipient'); } if (trs.amount <= 0) { diff --git a/logic/vote.js b/logic/vote.js index 2f1a5a1996d..9ed2c9e4df7 100644 --- a/logic/vote.js +++ b/logic/vote.js @@ -1,14 +1,17 @@ 'use strict'; +var async = require('async'); var constants = require('../helpers/constants.js'); var exceptions = require('../helpers/exceptions.js'); var Diff = require('../helpers/diff.js'); // Private fields -var modules, library; +var modules, library, self; // Constructor -function Vote () {} +function Vote () { + self = this; +} // Public methods Vote.prototype.bind = function (scope) { @@ -48,13 +51,30 @@ Vote.prototype.verify = function (trs, sender, cb) { return setImmediate(cb, 'Voting limit exceeded. Maximum is 33 votes per transaction'); } - modules.delegates.checkDelegates(trs.senderPublicKey, trs.asset.votes, function (err) { + self.checkConfirmedDelegates(trs, cb); +}; + +Vote.prototype.checkConfirmedDelegates = function (trs, cb) { + modules.delegates.checkConfirmedDelegates(trs.senderPublicKey, trs.asset.votes, function (err) { if (err && exceptions.votes.indexOf(trs.id) > -1) { library.logger.debug(err); library.logger.debug(JSON.stringify(trs)); err = null; } - return setImmediate(cb, err, trs); + + return setImmediate(cb, err); + }); +}; + +Vote.prototype.checkUnconfirmedDelegates = function (trs, cb) { + modules.delegates.checkUnconfirmedDelegates(trs.senderPublicKey, trs.asset.votes, function (err) { + if (err && exceptions.votes.indexOf(trs.id) > -1) { + library.logger.debug(err); + library.logger.debug(JSON.stringify(trs)); + err = null; + } + + return setImmediate(cb, err); }); }; @@ -99,23 +119,20 @@ Vote.prototype.undo = function (trs, block, sender, cb) { }; Vote.prototype.applyUnconfirmed = function (trs, sender, cb) { - modules.delegates.checkUnconfirmedDelegates(trs.senderPublicKey, trs.asset.votes, function (err) { - if (err) { - if (exceptions.votes.indexOf(trs.id) > -1) { - library.logger.debug(err); - library.logger.debug(JSON.stringify(trs)); - err = null; - } else { - return setImmediate(cb, err); - } + var parent = this; + + async.series([ + function (seriesCb) { + self.checkUnconfirmedDelegates(trs, seriesCb); + }, + function (seriesCb) { + parent.scope.account.merge(sender.address, { + u_delegates: trs.asset.votes + }, function (err) { + return setImmediate(seriesCb, err); + }); } - - this.scope.account.merge(sender.address, { - u_delegates: trs.asset.votes - }, function (err) { - return setImmediate(cb, err); - }); - }.bind(this)); + ], cb); }; Vote.prototype.undoUnconfirmed = function (trs, sender, cb) { diff --git a/modules/delegates.js b/modules/delegates.js index 4916d8d8a4c..8ad907ffe71 100644 --- a/modules/delegates.js +++ b/modules/delegates.js @@ -485,7 +485,7 @@ Delegates.prototype.getDelegates = function (query, cb) { }); }; -Delegates.prototype.checkDelegates = function (publicKey, votes, cb) { +Delegates.prototype.checkConfirmedDelegates = function (publicKey, votes, cb) { return __private.checkDelegates(publicKey, votes, 'confirmed', cb); }; From 16b7002a30ec75109633114630830cf959844dc8 Mon Sep 17 00:00:00 2001 From: Oliver Beddows Date: Sat, 12 Nov 2016 13:07:35 +0100 Subject: [PATCH 158/272] Changing scope of requires. --- modules/accounts.js | 2 +- modules/dapps.js | 6 +++--- modules/delegates.js | 2 +- modules/multisignatures.js | 2 +- modules/signatures.js | 2 +- 5 files changed, 7 insertions(+), 7 deletions(-) diff --git a/modules/accounts.js b/modules/accounts.js index 8c49e4c5e84..8faaa468db7 100644 --- a/modules/accounts.js +++ b/modules/accounts.js @@ -10,6 +10,7 @@ var schema = require('../schema/accounts.js'); var sandboxHelper = require('../helpers/sandbox.js'); var slots = require('../helpers/slots.js'); var transactionTypes = require('../helpers/transactionTypes.js'); +var Vote = require('../logic/vote.js'); // Private fields var modules, library, self, __private = {}, shared = {}; @@ -24,7 +25,6 @@ function Accounts (cb, scope) { __private.attachApi(); - var Vote = require('../logic/vote.js'); __private.assetTypes[transactionTypes.VOTE] = library.logic.transaction.attachAssetType( transactionTypes.VOTE, new Vote() ); diff --git a/modules/dapps.js b/modules/dapps.js index 1410747afe3..68d103653a1 100644 --- a/modules/dapps.js +++ b/modules/dapps.js @@ -4,14 +4,17 @@ var _ = require('lodash'); var async = require('async'); var constants = require('../helpers/constants.js'); var crypto = require('crypto'); +var DApp = require('../logic/dapp.js'); var dappCategories = require('../helpers/dappCategories.js'); var dappTypes = require('../helpers/dappTypes.js'); var DecompressZip = require('decompress-zip'); var extend = require('extend'); var fs = require('fs'); var ip = require('ip'); +var InTransfer = require('../logic/inTransfer.js'); var npm = require('npm'); var OrderBy = require('../helpers/orderBy.js'); +var OutTransfer = require('../logic/outTransfer.js'); var path = require('path'); var popsicle = require('popsicle'); var rmdir = require('rimraf'); @@ -43,17 +46,14 @@ function DApps (cb, scope) { __private.attachApi(); - var DApp = require('../logic/dapp.js'); __private.assetTypes[transactionTypes.DAPP] = library.logic.transaction.attachAssetType( transactionTypes.DAPP, new DApp() ); - var InTransfer = require('../logic/inTransfer.js'); __private.assetTypes[transactionTypes.IN_TRANSFER] = library.logic.transaction.attachAssetType( transactionTypes.IN_TRANSFER, new InTransfer() ); - var OutTransfer = require('../logic/outTransfer.js'); __private.assetTypes[transactionTypes.OUT_TRANSFER] = library.logic.transaction.attachAssetType( transactionTypes.OUT_TRANSFER, new OutTransfer() ); diff --git a/modules/delegates.js b/modules/delegates.js index 8ad907ffe71..4bd0fb55827 100644 --- a/modules/delegates.js +++ b/modules/delegates.js @@ -7,6 +7,7 @@ var BlockReward = require('../logic/blockReward.js'); var checkIpInList = require('../helpers/checkIpInList.js'); var constants = require('../helpers/constants.js'); var crypto = require('crypto'); +var Delegate = require('../logic/delegate.js'); var extend = require('extend'); var MilestoneBlocks = require('../helpers/milestoneBlocks.js'); var OrderBy = require('../helpers/orderBy.js'); @@ -32,7 +33,6 @@ function Delegates (cb, scope) { __private.attachApi(); - var Delegate = require('../logic/delegate.js'); __private.assetTypes[transactionTypes.DELEGATE] = library.logic.transaction.attachAssetType( transactionTypes.DELEGATE, new Delegate() ); diff --git a/modules/multisignatures.js b/modules/multisignatures.js index cc47f21b7bd..cf1719b4d7a 100644 --- a/modules/multisignatures.js +++ b/modules/multisignatures.js @@ -4,6 +4,7 @@ var async = require('async'); var crypto = require('crypto'); var extend = require('extend'); var genesisblock = null; +var Multisignature = require('../logic/multisignature.js'); var Router = require('../helpers/router.js'); var sandboxHelper = require('../helpers/sandbox.js'); var schema = require('../schema/multisignatures.js'); @@ -24,7 +25,6 @@ function Multisignatures (cb, scope) { __private.attachApi(); - var Multisignature = require('../logic/multisignature.js'); __private.assetTypes[transactionTypes.MULTI] = library.logic.transaction.attachAssetType( transactionTypes.MULTI, new Multisignature() ); diff --git a/modules/signatures.js b/modules/signatures.js index f2f90bc8aae..4cf69a16a4c 100644 --- a/modules/signatures.js +++ b/modules/signatures.js @@ -7,6 +7,7 @@ var MilestoneBlocks = require('../helpers/milestoneBlocks.js'); var Router = require('../helpers/router.js'); var sandboxHelper = require('../helpers/sandbox.js'); var schema = require('../schema/signatures.js'); +var Signature = require('../logic/signature.js'); var slots = require('../helpers/slots.js'); var transactionTypes = require('../helpers/transactionTypes.js'); @@ -22,7 +23,6 @@ function Signatures (cb, scope) { __private.attachApi(); - var Signature = require('../logic/signature.js'); __private.assetTypes[transactionTypes.SIGNATURE] = library.logic.transaction.attachAssetType( transactionTypes.SIGNATURE, new Signature() ); From 01a44a743386171f198bfa5ffa419fc5a6b88540 Mon Sep 17 00:00:00 2001 From: Oliver Beddows Date: Sat, 12 Nov 2016 13:13:59 +0100 Subject: [PATCH 159/272] Rewriting shared multisignature functions. --- modules/multisignatures.js | 464 +++++++++++++++++++++---------------- sql/multisignatures.js | 2 +- 2 files changed, 263 insertions(+), 203 deletions(-) diff --git a/modules/multisignatures.js b/modules/multisignatures.js index cf1719b4d7a..d1e28ad37d2 100644 --- a/modules/multisignatures.js +++ b/modules/multisignatures.js @@ -75,147 +75,162 @@ Multisignatures.prototype.onBind = function (scope) { }; shared.getAccounts = function (req, cb) { - library.schema.validate(req.body, schema.getAccounts, function (err) { - if (err) { - return setImmediate(cb, err[0].message); - } - - library.db.one(sql.getAccounts, { publicKey: req.body.publicKey }).then(function (row) { - var addresses = Array.isArray(row.accountId) ? row.accountId : []; + var scope = {}; + async.series({ + validateSchema: function (seriesCb) { + library.schema.validate(req.body, schema.getAccounts, function (err) { + if (err) { + return setImmediate(seriesCb, err[0].message); + } else { + return setImmediate(seriesCb); + } + }); + }, + getAccountIds: function (seriesCb) { + library.db.one(sql.getAccountIds, { publicKey: req.body.publicKey }).then(function (row) { + scope.accountIds = Array.isArray(row.accountIds) ? row.accountIds : []; + return setImmediate(seriesCb); + }).catch(function (err) { + library.logger.error(err.stack); + return setImmediate(seriesCb, 'Multisignature#getAccountIds error'); + }); + }, + getAccounts: function (seriesCb) { modules.accounts.getAccounts({ - address: { $in: addresses }, + address: { $in: scope.accountIds }, sort: 'balance' - }, ['address', 'balance', 'multisignatures', 'multilifetime', 'multimin'], function (err, rows) { + }, ['address', 'balance', 'multisignatures', 'multilifetime', 'multimin'], function (err, accounts) { if (err) { - return setImmediate(cb, err); + return setImmediate(seriesCb, err); + } else { + scope.accounts = accounts; + return setImmediate(seriesCb); } + }); + }, + buildAccounts: function (seriesCb) { + async.eachSeries(scope.accounts, function (account, eachSeriesCb) { + var addresses = []; - async.eachSeries(rows, function (account, cb) { - var addresses = []; - for (var i = 0; i < account.multisignatures.length; i++) { - addresses.push(modules.accounts.generateAddressByPublicKey(account.multisignatures[i])); - } - - modules.accounts.getAccounts({ - address: { $in: addresses } - }, ['address', 'publicKey', 'balance'], function (err, multisigaccounts) { - if (err) { - return setImmediate(cb, err); - } + for (var i = 0; i < account.multisignatures.length; i++) { + addresses.push(modules.accounts.generateAddressByPublicKey(account.multisignatures[i])); + } - account.multisigaccounts = multisigaccounts; - return setImmediate(cb); - }); - }, function (err) { + modules.accounts.getAccounts({ + address: { $in: addresses } + }, ['address', 'publicKey', 'balance'], function (err, multisigaccounts) { if (err) { - return setImmediate(cb, err); + return setImmediate(eachSeriesCb, err); } - return setImmediate(cb, null, {accounts: rows}); + account.multisigaccounts = multisigaccounts; + return setImmediate(eachSeriesCb); }); - }); - }).catch(function (err) { - library.logger.error(err.stack); - return setImmediate(cb, 'Multisignature#getAccounts error'); - }); + }, seriesCb); + } + }, function (err) { + if (err) { + return setImmediate(cb, err); + } else { + return setImmediate(cb, null, {accounts: scope.accounts}); + } }); }; // Shared shared.pending = function (req, cb) { - library.schema.validate(req.body, schema.pending, function (err) { - if (err) { - return setImmediate(cb, err[0].message); - } + var scope = { pending: [] }; - var transactions = modules.transactions.getUnconfirmedTransactionList(); - transactions = transactions.filter(function (transaction) { - return transaction.senderPublicKey === req.body.publicKey; - }); + async.series({ + validateSchema: function (seriesCb) { + library.schema.validate(req.body, schema.pending, function (err) { + if (err) { + return setImmediate(seriesCb, err[0].message); + } else { + return setImmediate(seriesCb); + } + }); + }, + getTransactionList: function (seriesCb) { + scope.transactions = modules.transactions.getMultisignatureTransactionList(false, false); + scope.transactions = scope.transactions.filter(function (transaction) { + return transaction.senderPublicKey === req.body.publicKey; + }); - var pending = []; - async.eachSeries(transactions, function (transaction, cb) { - var signed = false; + return setImmediate(seriesCb); + }, + buildTransactions: function (seriesCb) { + async.eachSeries(scope.transactions, function (transaction, eachSeriesCb) { + var signed = false; - if (transaction.signatures && transaction.signatures.length > 0) { - var verify = false; + if (transaction.signatures && transaction.signatures.length > 0) { + var verify = false; - for (var i in transaction.signatures) { - var signature = transaction.signatures[i]; + for (var i in transaction.signatures) { + var signature = transaction.signatures[i]; - try { - verify = library.logic.transaction.verifySignature(transaction, req.body.publicKey, transaction.signatures[i]); - } catch (e) { - library.logger.error(e.stack); - verify = false; + try { + verify = library.logic.transaction.verifySignature(transaction, req.body.publicKey, transaction.signatures[i]); + } catch (e) { + library.logger.error(e.stack); + verify = false; + } + + if (verify) { + break; + } } if (verify) { - break; + signed = true; } } - if (verify) { + if (!signed && transaction.senderPublicKey === req.body.publicKey) { signed = true; } - } - - if (!signed && transaction.senderPublicKey === req.body.publicKey) { - signed = true; - } - modules.accounts.getAccount({ - publicKey: transaction.senderPublicKey - }, function (err, sender) { - if (err) { - return setImmediate(cb, err); - } - - if (!sender) { - return setImmediate(cb, 'Sender not found'); - } - - var hasUnconfirmed = ( - sender.publicKey === req.body.publicKey && Array.isArray(sender.u_multisignatures) && sender.u_multisignatures.length > 0 - ); - - var belongsToUnconfirmed = ( - Array.isArray(sender.u_multisignatures) && sender.u_multisignatures.indexOf(req.body.publicKey) >= 0 - ); + modules.accounts.getAccount({ + publicKey: transaction.senderPublicKey + }, function (err, sender) { + if (err) { + return setImmediate(cb, err); + } - var belongsToConfirmed = ( - Array.isArray(sender.multisignatures) && sender.multisignatures.indexOf(req.body.publicKey) >= 0 - ); + if (!sender) { + return setImmediate(cb, 'Sender not found'); + } - if (hasUnconfirmed || belongsToUnconfirmed || belongsToConfirmed) { var min = sender.u_multimin || sender.multimin; var lifetime = sender.u_multilifetime || sender.multilifetime; var signatures = sender.u_multisignatures || []; - pending.push({ + scope.pending.push({ max: signatures.length, min: min, lifetime: lifetime, signed: signed, transaction: transaction }); - } - return setImmediate(cb); + return setImmediate(eachSeriesCb); + }); + }, function (err) { + return setImmediate(seriesCb, err); }); - }, function () { - return setImmediate(cb, null, {transactions: pending}); - }); + } + }, function (err) { + return setImmediate(cb, err, {transactions: scope.pending}); }); }; Multisignatures.prototype.processSignature = function (tx, cb) { - var transaction = modules.transactions.getUnconfirmedTransaction(tx.transaction); + var transaction = modules.transactions.getMultisignatureTransaction(tx.transaction); function done (cb) { library.balancesSequence.add(function (cb) { - var transaction = modules.transactions.getUnconfirmedTransaction(tx.transaction); + var transaction = modules.transactions.getMultisignatureTransaction(tx.transaction); if (!transaction) { return setImmediate(cb, 'Transaction not found'); @@ -303,157 +318,202 @@ Multisignatures.prototype.processSignature = function (tx, cb) { }; shared.sign = function (req, cb) { - library.schema.validate(req.body, schema.sign, function (err) { - if (err) { - return setImmediate(cb, err[0].message); - } + var scope = {}; - var transaction = modules.transactions.getUnconfirmedTransaction(req.body.transactionId); + function checkGroupPermisions (cb) { + var permissionDenied = ( + scope.transaction.asset.multisignature.keysgroup.indexOf('+' + scope.keypair.publicKey.toString('hex')) === -1 + ); - if (!transaction) { - return setImmediate(cb, 'Transaction not found'); + if (permissionDenied) { + return setImmediate(cb, 'Permission to sign transaction denied'); } - var hash = crypto.createHash('sha256').update(req.body.secret, 'utf8').digest(); - var keypair = library.ed.makeKeypair(hash); + var alreadySigned = ( + Array.isArray(scope.transaction.signatures) && + scope.transaction.signatures.indexOf(scope.signature.toString('hex')) !== -1 + ); - if (req.body.publicKey) { - if (keypair.publicKey.toString('hex') !== req.body.publicKey) { - return setImmediate(cb, 'Invalid passphrase'); - } + if (alreadySigned) { + return setImmediate(cb, 'Transaction already signed'); } - var sign = library.logic.transaction.multisign(keypair, transaction); + return setImmediate(cb); + } - function done (cb) { - library.balancesSequence.add(function (cb) { - var transaction = modules.transactions.getUnconfirmedTransaction(req.body.transactionId); + function checkTransactionPermissions (cb) { + var permissionDenied = true; - if (!transaction) { - return setImmediate(cb, 'Transaction not found'); - } + if (!scope.transaction.requesterPublicKey) { + permissionDenied = ( + (scope.sender.multisignatures.indexOf(scope.keypair.publicKey.toString('hex')) === -1) + ); + } else { + permissionDenied = ( + (scope.sender.publicKey !== scope.keypair.publicKey.toString('hex') || (scope.transaction.senderPublicKey !== scope.keypair.publicKey.toString('hex'))) + ); + } - transaction.signatures = transaction.signatures || []; - transaction.signatures.push(sign); + if (permissionDenied) { + return setImmediate(cb, 'Permission to sign transaction denied'); + } - library.bus.message('signature', { - signature: sign, - transaction: transaction.id - }, true); + var alreadySigned = (scope.transaction.signatures && scope.transaction.signatures.indexOf(scope.signature) !== -1); - return setImmediate(cb); - }, function (err) { - if (err) { - return setImmediate(cb, err); - } - - return setImmediate(cb, null, {transactionId: transaction.id}); - }); + if (alreadySigned) { + return setImmediate(cb, 'Transaction already signed'); } - if (transaction.type === transactionTypes.MULTI) { - if (transaction.asset.multisignature.keysgroup.indexOf('+' + keypair.publicKey.toString('hex')) === -1 || (transaction.signatures && transaction.signatures.indexOf(sign.toString('hex')) !== -1)) { - return setImmediate(cb, 'Permission to sign transaction denied'); - } + return setImmediate(cb); + } - library.network.io.sockets.emit('multisignatures/signature/change', transaction); - return done(cb); - } else { - modules.accounts.getAccount({ - address: transaction.senderId - }, function (err, account) { - if (err) { - return setImmediate(cb, err); + library.balancesSequence.add(function (cb) { + async.series({ + validateSchema: function (seriesCb) { + library.schema.validate(req.body, schema.sign, function (err) { + if (err) { + return setImmediate(seriesCb, err[0].message); + } else { + return setImmediate(seriesCb); + } + }); + }, + signTransaction: function (seriesCb) { + scope.transaction = modules.transactions.getMultisignatureTransaction(req.body.transactionId); + + if (!scope.transaction) { + return setImmediate(seriesCb, 'Transaction not found'); } - if (!account) { - return setImmediate(cb, 'Sender not found'); + scope.hash = crypto.createHash('sha256').update(req.body.secret, 'utf8').digest(); + scope.keypair = library.ed.makeKeypair(scope.hash); + + if (req.body.publicKey) { + if (scope.keypair.publicKey.toString('hex') !== req.body.publicKey) { + return setImmediate(seriesCb, 'Invalid passphrase'); + } } - if (!transaction.requesterPublicKey) { - if (account.multisignatures.indexOf(keypair.publicKey.toString('hex')) < 0) { - return setImmediate(cb, 'Permission to sign transaction denied'); + scope.signature = library.logic.transaction.multisign(scope.keypair, scope.transaction); + return setImmediate(seriesCb); + }, + getAccount: function (seriesCb) { + modules.accounts.getAccount({ + address: scope.transaction.senderId + }, function (err, sender) { + if (err) { + return setImmediate(seriesCb, err); + } else if (!sender) { + return setImmediate(seriesCb, 'Sender not found'); + } else { + scope.sender = sender; + return setImmediate(seriesCb); } + }); + }, + checkPermissions: function (seriesCb) { + if (scope.transaction.type === transactionTypes.MULTI) { + return checkGroupPermisions(seriesCb); } else { - if (account.publicKey !== keypair.publicKey.toString('hex') || transaction.senderPublicKey !== keypair.publicKey.toString('hex')) { - return setImmediate(cb, 'Permission to sign transaction denied'); - } + return checkTransactionPermissions(seriesCb); } + } + }, function (err) { + if (err) { + return setImmediate(cb, err); + } - if (transaction.signatures && transaction.signatures.indexOf(sign) !== -1) { - return setImmediate(cb, 'Permission to sign transaction denied'); - } + var transaction = modules.transactions.getMultisignatureTransaction(req.body.transactionId); - library.network.io.sockets.emit('multisignatures/signature/change', transaction); - return done(cb); - }); - } - }); -}; + if (!transaction) { + return setImmediate(cb, 'Transaction not found'); + } -shared.addMultisignature = function (req, cb) { - library.schema.validate(req.body, schema.addMultisignature, function (err) { - if (err) { - return setImmediate(cb, err[0].message); - } + transaction.signatures = transaction.signatures || []; + transaction.signatures.push(scope.signature); + transaction.ready = Multisignature.prototype.ready(transaction, scope.sender); - var hash = crypto.createHash('sha256').update(req.body.secret, 'utf8').digest(); - var keypair = library.ed.makeKeypair(hash); + library.bus.message('signature', { + signature: scope.signature, + transaction: transaction.id + }, true); - if (req.body.publicKey) { - if (keypair.publicKey.toString('hex') !== req.body.publicKey) { - return setImmediate(cb, 'Invalid passphrase'); - } - } + library.network.io.sockets.emit('multisignatures/signature/change', transaction); + return setImmediate(cb, null, {transactionId: transaction.id}); + }); + }, cb); +}; - library.balancesSequence.add(function (cb) { - modules.accounts.setAccountAndGet({publicKey: keypair.publicKey.toString('hex')}, function (err, account) { - if (err) { - return setImmediate(cb, err); - } +shared.addMultisignature = function (req, cb) { + var scope = {}; - if (!account || !account.publicKey) { - return setImmediate(cb, 'Account not found'); + library.balancesSequence.add(function (cb) { + async.series({ + validateSchema: function (seriesCb) { + library.schema.validate(req.body, schema.addMultisignature, function (err) { + if (err) { + return setImmediate(seriesCb, err[0].message); + } else { + return setImmediate(seriesCb); + } + }); + }, + addMultisignature: function (seriesCb) { + scope.hash = crypto.createHash('sha256').update(req.body.secret, 'utf8').digest(); + scope.keypair = library.ed.makeKeypair(scope.hash); + + if (req.body.publicKey) { + if (scope.keypair.publicKey.toString('hex') !== req.body.publicKey) { + return setImmediate(seriesCb, 'Invalid passphrase'); + } } - if (account.secondSignature && !req.body.secondSecret) { - return setImmediate(cb, 'Invalid second passphrase'); - } + modules.accounts.setAccountAndGet({publicKey: scope.keypair.publicKey.toString('hex')}, function (err, account) { + if (err) { + return setImmediate(seriesCb, err); + } - var secondKeypair = null; + if (!account || !account.publicKey) { + return setImmediate(seriesCb, 'Account not found'); + } - if (account.secondSignature) { - var secondHash = crypto.createHash('sha256').update(req.body.secondSecret, 'utf8').digest(); - secondKeypair = library.ed.makeKeypair(secondHash); - } + if (account.secondSignature && !req.body.secondSecret) { + return setImmediate(seriesCb, 'Invalid second passphrase'); + } - var transaction; - - try { - transaction = library.logic.transaction.create({ - type: transactionTypes.MULTI, - sender: account, - keypair: keypair, - secondKeypair: secondKeypair, - min: req.body.min, - keysgroup: req.body.keysgroup, - lifetime: req.body.lifetime - }); - } catch (e) { - return setImmediate(cb, e.toString()); - } + scope.secondKeypair = null; - modules.transactions.receiveTransactions([transaction], cb); - }); - }, function (err, transaction) { + if (account.secondSignature) { + scope.secondHash = crypto.createHash('sha256').update(req.body.secondSecret, 'utf8').digest(); + scope.secondKeypair = library.ed.makeKeypair(scope.secondHash); + } + + try { + scope.transaction = library.logic.transaction.create({ + type: transactionTypes.MULTI, + sender: account, + keypair: scope.keypair, + secondKeypair: scope.secondKeypair, + min: req.body.min, + keysgroup: req.body.keysgroup, + lifetime: req.body.lifetime + }); + } catch (e) { + return setImmediate(seriesCb, e.toString()); + } + + modules.transactions.receiveTransactions([scope.transaction], seriesCb); + }); + } + }, function (err) { if (err) { return setImmediate(cb, err); + } else { + library.network.io.sockets.emit('multisignatures/change', scope.transaction); + return setImmediate(cb, null, {transactionId: scope.transaction.id}); } - - library.network.io.sockets.emit('multisignatures/change', transaction); - return setImmediate(cb, null, {transactionId: transaction[0].id}); }); - }); + }, cb); }; // Export diff --git a/sql/multisignatures.js b/sql/multisignatures.js index 85834ddddd4..832c0cffd68 100644 --- a/sql/multisignatures.js +++ b/sql/multisignatures.js @@ -1,7 +1,7 @@ 'use strict'; var MultisignaturesSql = { - getAccounts: 'SELECT ARRAY_AGG("accountId") AS "accountId" FROM mem_accounts2multisignatures WHERE "dependentId" = ${publicKey}' + getAccountIds: 'SELECT ARRAY_AGG("accountId") AS "accountIds" FROM mem_accounts2multisignatures WHERE "dependentId" = ${publicKey}' }; module.exports = MultisignaturesSql; From b978d26170089eb0352e7506a3d6b6b6386914eb Mon Sep 17 00:00:00 2001 From: Oliver Beddows Date: Sat, 12 Nov 2016 13:44:05 +0100 Subject: [PATCH 160/272] Reordering functions properly. --- modules/multisignatures.js | 186 ++++++++++++++++++------------------- 1 file changed, 93 insertions(+), 93 deletions(-) diff --git a/modules/multisignatures.js b/modules/multisignatures.js index d1e28ad37d2..d6073207c73 100644 --- a/modules/multisignatures.js +++ b/modules/multisignatures.js @@ -61,6 +61,98 @@ __private.attachApi = function () { }; // Public methods +Multisignatures.prototype.processSignature = function (tx, cb) { + var transaction = modules.transactions.getMultisignatureTransaction(tx.transaction); + + function done (cb) { + library.balancesSequence.add(function (cb) { + var transaction = modules.transactions.getMultisignatureTransaction(tx.transaction); + + if (!transaction) { + return setImmediate(cb, 'Transaction not found'); + } + + transaction.signatures = transaction.signatures || []; + transaction.signatures.push(tx.signature); + library.bus.message('signature', transaction, true); + + return setImmediate(cb); + }, cb); + } + + if (!transaction) { + return setImmediate(cb, 'Transaction not found'); + } + + if (transaction.type === transactionTypes.MULTI) { + transaction.signatures = transaction.signatures || []; + + if (transaction.asset.multisignature.signatures || transaction.signatures.indexOf(tx.signature) !== -1) { + return setImmediate(cb, 'Permission to sign transaction denied'); + } + + // Find public key + var verify = false; + + try { + for (var i = 0; i < transaction.asset.multisignature.keysgroup.length && !verify; i++) { + var key = transaction.asset.multisignature.keysgroup[i].substring(1); + verify = library.logic.transaction.verifySignature(transaction, key, tx.signature); + } + } catch (e) { + library.logger.error(e.stack); + return setImmediate(cb, 'Failed to verify signature'); + } + + if (!verify) { + return setImmediate(cb, 'Failed to verify signature'); + } + + return done(cb); + } else { + modules.accounts.getAccount({ + address: transaction.senderId + }, function (err, account) { + if (err) { + return setImmediate(cb, 'Multisignature account not found'); + } + + var verify = false; + var multisignatures = account.multisignatures; + + if (transaction.requesterPublicKey) { + multisignatures.push(transaction.senderPublicKey); + } + + if (!account) { + return setImmediate(cb, 'Account not found'); + } + + transaction.signatures = transaction.signatures || []; + + if (transaction.signatures.indexOf(tx.signature) >= 0) { + return setImmediate(cb, 'Signature already exists'); + } + + try { + for (var i = 0; i < multisignatures.length && !verify; i++) { + verify = library.logic.transaction.verifySignature(transaction, multisignatures[i], tx.signature); + } + } catch (e) { + library.logger.error(e.stack); + return setImmediate(cb, 'Failed to verify signature'); + } + + if (!verify) { + return setImmediate(cb, 'Failed to verify signature'); + } + + library.network.io.sockets.emit('multisignatures/signature/change', transaction); + return done(cb); + }); + } +}; + Multisignatures.prototype.sandboxApi = function (call, args, cb) { sandboxHelper.callMethod(shared, call, args, cb); }; @@ -74,6 +166,7 @@ Multisignatures.prototype.onBind = function (scope) { }); }; +// Shared shared.getAccounts = function (req, cb) { var scope = {}; @@ -138,7 +231,6 @@ shared.getAccounts = function (req, cb) { }); }; -// Shared shared.pending = function (req, cb) { var scope = { pending: [] }; @@ -225,98 +317,6 @@ shared.pending = function (req, cb) { }); }; -Multisignatures.prototype.processSignature = function (tx, cb) { - var transaction = modules.transactions.getMultisignatureTransaction(tx.transaction); - - function done (cb) { - library.balancesSequence.add(function (cb) { - var transaction = modules.transactions.getMultisignatureTransaction(tx.transaction); - - if (!transaction) { - return setImmediate(cb, 'Transaction not found'); - } - - transaction.signatures = transaction.signatures || []; - transaction.signatures.push(tx.signature); - library.bus.message('signature', transaction, true); - - return setImmediate(cb); - }, cb); - } - - if (!transaction) { - return setImmediate(cb, 'Transaction not found'); - } - - if (transaction.type === transactionTypes.MULTI) { - transaction.signatures = transaction.signatures || []; - - if (transaction.asset.multisignature.signatures || transaction.signatures.indexOf(tx.signature) !== -1) { - return setImmediate(cb, 'Permission to sign transaction denied'); - } - - // Find public key - var verify = false; - - try { - for (var i = 0; i < transaction.asset.multisignature.keysgroup.length && !verify; i++) { - var key = transaction.asset.multisignature.keysgroup[i].substring(1); - verify = library.logic.transaction.verifySignature(transaction, key, tx.signature); - } - } catch (e) { - library.logger.error(e.stack); - return setImmediate(cb, 'Failed to verify signature'); - } - - if (!verify) { - return setImmediate(cb, 'Failed to verify signature'); - } - - return done(cb); - } else { - modules.accounts.getAccount({ - address: transaction.senderId - }, function (err, account) { - if (err) { - return setImmediate(cb, 'Multisignature account not found'); - } - - var verify = false; - var multisignatures = account.multisignatures; - - if (transaction.requesterPublicKey) { - multisignatures.push(transaction.senderPublicKey); - } - - if (!account) { - return setImmediate(cb, 'Account not found'); - } - - transaction.signatures = transaction.signatures || []; - - if (transaction.signatures.indexOf(tx.signature) >= 0) { - return setImmediate(cb, 'Signature already exists'); - } - - try { - for (var i = 0; i < multisignatures.length && !verify; i++) { - verify = library.logic.transaction.verifySignature(transaction, multisignatures[i], tx.signature); - } - } catch (e) { - library.logger.error(e.stack); - return setImmediate(cb, 'Failed to verify signature'); - } - - if (!verify) { - return setImmediate(cb, 'Failed to verify signature'); - } - - library.network.io.sockets.emit('multisignatures/signature/change', transaction); - return done(cb); - }); - } -}; - shared.sign = function (req, cb) { var scope = {}; From 5983c23baf320f36f0b4c0a7485e3525bd3ff252 Mon Sep 17 00:00:00 2001 From: Oliver Beddows Date: Sat, 12 Nov 2016 13:14:24 +0100 Subject: [PATCH 161/272] Fixing and improving test coverage #306. --- test/api/dapps.js | 2 +- test/api/delegates.js | 6 +- test/api/multisignatures.js | 88 ++++++++++++++++-------- test/api/peer.transactions.delegates.js | 10 +-- test/api/peer.transactions.js | 16 ++++- test/api/peer.transactions.signatures.js | 6 +- test/api/peer.transactions.votes.js | 61 +++++----------- 7 files changed, 107 insertions(+), 82 deletions(-) diff --git a/test/api/dapps.js b/test/api/dapps.js index 20b41e4bc42..ddabecd91ff 100644 --- a/test/api/dapps.js +++ b/test/api/dapps.js @@ -752,7 +752,7 @@ describe('PUT /api/dapps/withdrawal', function () { }); }); - it('using same params twice within current block should fail', function (done) { + it('using same valid params twice should fail', function (done) { putWithdrawal(validParams, function (err, res) { node.expect(res.body).to.have.property('success').to.be.ok; node.expect(res.body).to.have.property('transactionId').to.not.be.empty; diff --git a/test/api/delegates.js b/test/api/delegates.js index 41eb1241f97..71281fb0833 100644 --- a/test/api/delegates.js +++ b/test/api/delegates.js @@ -51,7 +51,7 @@ describe('PUT /api/accounts/delegates without funds', function () { delegates: ['-' + node.eAccount.publicKey] }, function (err, res) { node.expect(res.body).to.have.property('success').to.be.not.ok; - node.expect(res.body).to.have.property('error').to.match(/Failed to remove vote/); + node.expect(res.body).to.have.property('error').to.match(/Account does not have enough LSK: [0-9]+L balance: 0/); done(); }); }); @@ -89,9 +89,7 @@ describe('PUT /api/accounts/delegates with funds', function () { node.expect(res.body).to.have.property('success').to.be.ok; node.expect(res.body).to.have.property('transactionId'); node.expect(res.body.transactionId).to.be.not.empty; - node.onNewBlock(function (err) { - done(); - }); + done(); }); }); diff --git a/test/api/multisignatures.js b/test/api/multisignatures.js index cb6a5da5287..fa788c32465 100644 --- a/test/api/multisignatures.js +++ b/test/api/multisignatures.js @@ -343,17 +343,31 @@ describe('GET /api/multisignatures/pending', function () { var flag = 0; for (var i = 0; i < res.body.transactions.length; i++) { - if (res.body.transactions[i].transaction.senderPublicKey === multisigAccount.publicKey) { - flag += 1; - node.expect(res.body.transactions[i].transaction).to.have.property('type').to.equal(node.txTypes.MULTI); - node.expect(res.body.transactions[i].transaction).to.have.property('amount').to.equal(0); - node.expect(res.body.transactions[i].transaction).to.have.property('asset').that.is.an('object'); - node.expect(res.body.transactions[i].transaction).to.have.property('fee').to.equal(node.fees.multisignatureRegistrationFee * (Keys.length + 1)); - node.expect(res.body.transactions[i].transaction).to.have.property('id').to.equal(multiSigTx.txId); - node.expect(res.body.transactions[i].transaction).to.have.property('senderPublicKey').to.equal(multisigAccount.publicKey); - node.expect(res.body.transactions[i]).to.have.property('lifetime').to.equal(multiSigTx.lifetime); - node.expect(res.body.transactions[i]).to.have.property('min').to.equal(multiSigTx.min); - } + flag += 1; + + var pending = res.body.transactions[i]; + + node.expect(pending).to.have.property('max').that.is.equal(0); + node.expect(pending).to.have.property('min').that.is.equal(0); + node.expect(pending).to.have.property('lifetime').that.is.equal(0); + node.expect(pending).to.have.property('signed').that.is.true; + + node.expect(pending.transaction).to.have.property('type').that.is.equal(node.txTypes.MULTI); + node.expect(pending.transaction).to.have.property('amount').that.is.equal(0); + node.expect(pending.transaction).to.have.property('senderPublicKey').that.is.equal(multisigAccount.publicKey); + node.expect(pending.transaction).to.have.property('requesterPublicKey').that.is.null; + node.expect(pending.transaction).to.have.property('timestamp').that.is.a('number'); + node.expect(pending.transaction).to.have.property('asset').that.is.an('object'); + node.expect(pending.transaction.asset).to.have.property('multisignature').that.is.an('object'); + node.expect(pending.transaction.asset.multisignature).to.have.property('min').that.is.a('number'); + node.expect(pending.transaction.asset.multisignature).to.have.property('keysgroup').that.is.an('array'); + node.expect(pending.transaction.asset.multisignature).to.have.property('lifetime').that.is.a('number'); + node.expect(pending.transaction).to.have.property('recipientId').that.is.null; + node.expect(pending.transaction).to.have.property('signature').that.is.a('string'); + node.expect(pending.transaction).to.have.property('id').that.is.equal(multiSigTx.txId); + node.expect(pending.transaction).to.have.property('fee').that.is.equal(node.fees.multisignatureRegistrationFee * (Keys.length + 1)); + node.expect(pending.transaction).to.have.property('senderId').that.is.eql(multisigAccount.address); + node.expect(pending.transaction).to.have.property('receivedAt').that.is.a('string'); } node.expect(flag).to.equal(1); @@ -362,7 +376,7 @@ describe('GET /api/multisignatures/pending', function () { }); }); -describe('PUT /api/api/transactions/', function () { +describe('PUT /api/transactions', function () { it('when group transaction is pending should be ok', function (done) { sendLISKFromMultisigAccount(100000000, node.gAccount.address, function (err, transactionId) { @@ -382,6 +396,10 @@ describe('POST /api/multisignatures/sign (group)', function () { var validParams; + var passphrases = accounts.map(function (account) { + return account.password; + }); + beforeEach(function (done) { validParams = { secret: accounts[0].password, @@ -418,10 +436,6 @@ describe('POST /api/multisignatures/sign (group)', function () { }); it('using one less than total signatures should not confirm transaction', function (done) { - var passphrases = accounts.map(function (account) { - return account.password; - }); - confirmTransaction(multiSigTx.txId, passphrases.slice(0, (passphrases.length - 1)), function () { node.onNewBlock(function (err) { node.get('/api/transactions/get?id=' + multiSigTx.txId, function (err, res) { @@ -432,11 +446,15 @@ describe('POST /api/multisignatures/sign (group)', function () { }); }); - it('using one more signature should confirm transaction', function (done) { - var passphrases = accounts.map(function (account) { - return account.password; + it('using same signature again should fail', function (done) { + node.post('/api/multisignatures/sign', validParams, function (err, res) { + node.expect(res.body).to.have.property('success').to.be.not.ok; + node.expect(res.body).to.have.property('error').to.equal('Transaction already signed'); + done(); }); + }); + it('using one more signature should confirm transaction', function (done) { confirmTransaction(multiSigTx.txId, passphrases.slice(-1), function () { node.onNewBlock(function (err) { node.get('/api/transactions/get?id=' + multiSigTx.txId, function (err, res) { @@ -452,18 +470,30 @@ describe('POST /api/multisignatures/sign (group)', function () { describe('POST /api/multisignatures/sign (transaction)', function () { + var validParams; + + var passphrases = accounts.map(function (account) { + return account.password; + }); + before(function (done) { sendLISKFromMultisigAccount(100000000, node.gAccount.address, function (err, transactionId) { multiSigTx.txId = transactionId; - done(); + node.onNewBlock(function (err) { + done(); + }); }); }); - it('using one less than minimum signatures should not confirm transaction', function (done) { - var passphrases = accounts.map(function (account) { - return account.password; - }); + beforeEach(function (done) { + validParams = { + secret: accounts[0].password, + transactionId: multiSigTx.txId + }; + done(); + }); + it('using one less than minimum signatures should not confirm transaction', function (done) { confirmTransaction(multiSigTx.txId, passphrases.slice(0, (multiSigTx.min - 1)), function () { node.onNewBlock(function (err) { node.get('/api/transactions/get?id=' + multiSigTx.txId, function (err, res) { @@ -474,11 +504,15 @@ describe('POST /api/multisignatures/sign (transaction)', function () { }); }); - it('using one more signature should confirm transaction', function (done) { - var passphrases = accounts.map(function (account) { - return account.password; + it('using same signature again should fail', function (done) { + node.post('/api/multisignatures/sign', validParams, function (err, res) { + node.expect(res.body).to.have.property('success').to.be.not.ok; + node.expect(res.body).to.have.property('error').to.equal('Transaction already signed'); + done(); }); + }); + it('using one more signature should confirm transaction', function (done) { confirmTransaction(multiSigTx.txId, passphrases.slice(-1), function () { node.onNewBlock(function (err) { node.get('/api/transactions/get?id=' + multiSigTx.txId, function (err, res) { diff --git a/test/api/peer.transactions.delegates.js b/test/api/peer.transactions.delegates.js index 4af08544110..917d3cd9692 100644 --- a/test/api/peer.transactions.delegates.js +++ b/test/api/peer.transactions.delegates.js @@ -117,7 +117,7 @@ describe('POST /peer/transactions', function () { }); }); - describe('twice within the same block', function () { + describe('twice for the same account', function () { before(function (done) { sendLISK({ @@ -137,9 +137,11 @@ describe('POST /peer/transactions', function () { postTransaction(transaction, function (err, res) { node.expect(res.body).to.have.property('success').to.be.ok; - postTransaction(transaction2, function (err, res) { - node.expect(res.body).to.have.property('success').to.be.not.ok; - done(); + node.onNewBlock(function () { + postTransaction(transaction2, function (err, res) { + node.expect(res.body).to.have.property('success').to.be.not.ok; + done(); + }); }); }); }); diff --git a/test/api/peer.transactions.js b/test/api/peer.transactions.js index 87b6f64c06c..cd341ba7752 100644 --- a/test/api/peer.transactions.js +++ b/test/api/peer.transactions.js @@ -128,7 +128,17 @@ describe('POST /peer/transactions', function () { postTransaction(transaction, function (err, res) { node.expect(res.body).to.have.property('success').to.be.not.ok; - node.expect(res.body).to.have.property('message'); + node.expect(res.body).to.have.property('message').to.eql('Missing recipient'); + done(); + }); + }); + + it('using transaction with invalid recipientId should fail', function (done) { + var transaction = node.lisk.transaction.createTransaction('0123456789001234567890L', 1, node.gAccount.password); + + postTransaction(transaction, function (err, res) { + node.expect(res.body).to.have.property('success').to.be.not.ok; + node.expect(res.body).to.have.property('message').to.eql('Invalid transaction body'); done(); }); }); @@ -306,6 +316,10 @@ describe('POST /peer/transactions', function () { describe('when transaction is valid', function () { + beforeEach(function (done) { + node.onNewBlock(done); + }); + it('should be ok for passphrase one', function (done) { var transaction = node.lisk.transaction.createTransaction(node.gAccount.address, 100000000, collision.passphrases[0]); diff --git a/test/api/peer.transactions.signatures.js b/test/api/peer.transactions.signatures.js index 4d3a4dc66ac..fb19c4d8c46 100644 --- a/test/api/peer.transactions.signatures.js +++ b/test/api/peer.transactions.signatures.js @@ -108,17 +108,19 @@ describe('POST /peer/transactions', function () { postTransaction(transaction, function (err, res) { node.expect(res.body).to.have.property('success').to.be.not.ok; + node.expect(res.body).to.have.property('message').to.equal('Missing sender second signature'); done(); }); }); - it('using fake second passphrase should fail', function (done) { - var transaction = node.lisk.transaction.createTransaction('1L', 1, account.password, account2.secondPassword); + it('using fake second signature should fail', function (done) { + var transaction = node.lisk.transaction.createTransaction('1L', 1, account.password, account.secondPassword); transaction.signSignature = crypto.randomBytes(64).toString('hex'); transaction.id = node.lisk.crypto.getId(transaction); postTransaction(transaction, function (err, res) { node.expect(res.body).to.have.property('success').to.be.not.ok; + node.expect(res.body).to.have.property('message').to.equal('Failed to verify second signature'); done(); }); }); diff --git a/test/api/peer.transactions.votes.js b/test/api/peer.transactions.votes.js index 52122d96ad2..00c9225615b 100644 --- a/test/api/peer.transactions.votes.js +++ b/test/api/peer.transactions.votes.js @@ -91,7 +91,7 @@ describe('POST /peer/transactions', function () { }, done); }); - before(function (done) { + beforeEach(function (done) { getDelegates(function (err, res) { delegates = res.body.delegates.map(function (delegate) { return delegate.publicKey; @@ -103,7 +103,7 @@ describe('POST /peer/transactions', function () { }); }); - before(function (done) { + beforeEach(function (done) { getVotes(account.address, function (err, res) { votedDelegates = res.body.delegates.map(function (delegate) { return delegate.publicKey; @@ -144,36 +144,6 @@ describe('POST /peer/transactions', function () { }); }); - it('voting for a delegate and then removing again within same block should fail', function (done) { - node.onNewBlock(function (err) { - var transaction = node.lisk.vote.createVote(account.password, ['+' + delegate]); - postVote(transaction, function (err, res) { - node.expect(res.body).to.have.property('success').to.be.ok; - - var transaction2 = node.lisk.vote.createVote(account.password, ['-' + delegate]); - postVote(transaction2, function (err, res) { - node.expect(res.body).to.have.property('success').to.be.not.ok; - done(); - }); - }); - }); - }); - - it('removing votes from a delegate and then voting again within same block should fail', function (done) { - node.onNewBlock(function (err) { - var transaction = node.lisk.vote.createVote(account.password, ['-' + delegate]); - postVote(transaction, function (err, res) { - node.expect(res.body).to.have.property('success').to.be.ok; - - var transaction2 = node.lisk.vote.createVote(account.password, ['+' + delegate]); - postVote(transaction2, function (err, res) { - node.expect(res.body).to.have.property('success').to.be.not.ok; - done(); - }); - }); - }); - }); - it('voting twice for a delegate should fail', function (done) { async.series([ function (seriesCb) { @@ -181,7 +151,7 @@ describe('POST /peer/transactions', function () { var transaction = node.lisk.vote.createVote(account.password, ['+' + delegate]); postVote(transaction, function (err, res) { node.expect(res.body).to.have.property('success').to.be.ok; - done(); + seriesCb(); }); }); }, @@ -281,15 +251,17 @@ describe('POST /peer/transactions', function () { }); it('removing votes from 101 delegates separately should be ok', function (done) { - postVotes({ - delegates: delegates, - passphrase: account.password, - action: '-', - voteCb: function (err, res) { - node.expect(res.body).to.have.property('success').to.be.ok; - node.expect(res.body).to.have.property('transactionId').that.is.a('string'); - } - }, done); + node.onNewBlock(function (err) { + postVotes({ + delegates: delegates, + passphrase: account.password, + action: '-', + voteCb: function (err, res) { + node.expect(res.body).to.have.property('success').to.be.ok; + node.expect(res.body).to.have.property('transactionId').that.is.a('string'); + } + }, done); + }); }); }); @@ -329,7 +301,7 @@ describe('POST /peer/transactions after registering a new delegate', function () }); }); - it('exceeding maximum of 101 votes within same block should fail', function (done) { + it('exceeding maximum of 101 votes should fail', function (done) { async.series([ function (seriesCb) { var slicedDelegates = delegates.slice(0, 76); @@ -344,6 +316,9 @@ describe('POST /peer/transactions after registering a new delegate', function () } }, seriesCb); }, + function (seriesCb) { + return node.onNewBlock(seriesCb); + }, function (seriesCb) { var slicedDelegates = delegates.slice(-25); node.expect(slicedDelegates).to.have.lengthOf(25); From 6370bfc4f6d81d94bed7e1767bbcdbf485731a53 Mon Sep 17 00:00:00 2001 From: Oliver Beddows Date: Sat, 12 Nov 2016 19:23:33 +0100 Subject: [PATCH 162/272] Adding waitForBlocks test helper. --- test/node.js | 17 ++++++++++++++--- 1 file changed, 14 insertions(+), 3 deletions(-) diff --git a/test/node.js b/test/node.js index d16283347e9..8a4d4f0e4bc 100644 --- a/test/node.js +++ b/test/node.js @@ -128,13 +128,24 @@ node.onNewBlock = function (cb) { if (err) { return cb(err); } else { - node.waitForNewBlock(height, cb); + node.waitForNewBlock(height, 2, cb); + } + }); +}; + +// Waits for (n) blocks to be created +node.waitForBlocks = function (blocksToWait, cb) { + node.getHeight(function (err, height) { + if (err) { + return cb(err); + } else { + node.waitForNewBlock(height, blocksToWait, cb); } }); }; // Waits for a new block to be created -node.waitForNewBlock = function (height, cb) { +node.waitForNewBlock = function (height, blocksToWait, cb) { var actualHeight = height; var counter = 1; @@ -149,7 +160,7 @@ node.waitForNewBlock = function (height, cb) { return cb(['Received bad response code', res.status, res.url].join(' ')); } - if (height + 2 === res.body.height) { + if (height + blocksToWait === res.body.height) { height = res.body.height; } From 887897c7bca69d858a49e4d0ff4689959f33af67 Mon Sep 17 00:00:00 2001 From: Oliver Beddows Date: Sat, 12 Nov 2016 19:22:48 +0100 Subject: [PATCH 163/272] Adding new API endpoints #306. - GET /api/transactions/multisignatures/get - GET /api/transactions/multisignatures - GET /api/transactions/queued/get - GET /api/transactions/queued --- modules/transactions.js | 55 +++++++++++++++------------------------- schema/transactions.js | 8 +++--- test/api/transactions.js | 51 +++++++++++++++++++++++++++++++++++++ 3 files changed, 75 insertions(+), 39 deletions(-) diff --git a/modules/transactions.js b/modules/transactions.js index 661428f1aff..757fda19cec 100644 --- a/modules/transactions.js +++ b/modules/transactions.js @@ -49,6 +49,10 @@ __private.attachApi = function () { router.map(shared, { 'get /': 'getTransactions', 'get /get': 'getTransaction', + 'get /queued/get': 'getQueuedTransaction', + 'get /queued': 'getQueuedTransactions', + 'get /multisignatures/get': 'getMultisignatureTransaction', + 'get /multisignatures': 'getMultisignatureTransactions', 'get /unconfirmed/get': 'getUnconfirmedTransaction', 'get /unconfirmed': 'getUnconfirmedTransactions', 'put /': 'addTransactions' @@ -223,8 +227,6 @@ __private.getPooledTransactions = function (method, req, cb) { return setImmediate(cb, null, {transactions: toSend, count: transactions.length}); }); }; - return __private.transactionPool.addUnconfirmedTransaction(transaction, sender, cb); -}; // Public methods Transactions.prototype.transactionInPool = function (id) { @@ -385,45 +387,28 @@ shared.getTransaction = function (req, cb) { }); }; -shared.getUnconfirmedTransaction = function (req, cb) { - library.schema.validate(req.body, schema.getUnconfirmedTransaction, function (err) { - if (err) { - return setImmediate(cb, err[0].message); - } - - var unconfirmedTransaction = self.getUnconfirmedTransaction(req.body.id); - - if (!unconfirmedTransaction) { - return setImmediate(cb, 'Transaction not found'); - } +shared.getQueuedTransaction = function (req, cb) { + return __private.getPooledTransaction('getQueuedTransaction', req, cb); +}; - return setImmediate(cb, null, {transaction: unconfirmedTransaction}); - }); +shared.getQueuedTransactions = function (req, cb) { + return __private.getPooledTransactions('getQueuedTransactionList', req, cb); }; -shared.getUnconfirmedTransactions = function (req, cb) { - library.schema.validate(req.body, schema.getUnconfirmedTransactions, function (err) { - if (err) { - return setImmediate(cb, err[0].message); - } +shared.getMultisignatureTransaction = function (req, cb) { + return __private.getPooledTransaction('getMultisignatureTransaction', req, cb); +}; - var transactions = self.getUnconfirmedTransactionList(true); - var i, toSend = []; +shared.getMultisignatureTransactions = function (req, cb) { + return __private.getPooledTransactions('getMultisignatureTransactionList', req, cb); +}; - if (req.body.senderPublicKey || req.body.address) { - for (i = 0; i < transactions.length; i++) { - if (transactions[i].senderPublicKey === req.body.senderPublicKey || transactions[i].recipientId === req.body.address) { - toSend.push(transactions[i]); - } - } - } else { - for (i = 0; i < transactions.length; i++) { - toSend.push(transactions[i]); - } - } +shared.getUnconfirmedTransaction = function (req, cb) { + return __private.getPooledTransaction('getUnconfirmedTransaction', req, cb); +}; - return setImmediate(cb, null, {transactions: toSend}); - }); +shared.getUnconfirmedTransactions = function (req, cb) { + return __private.getPooledTransactions('getUnconfirmedTransactionList', req, cb); }; shared.addTransactions = function (req, cb) { diff --git a/schema/transactions.js b/schema/transactions.js index e8eed073f0a..514d7501fa1 100644 --- a/schema/transactions.js +++ b/schema/transactions.js @@ -81,8 +81,8 @@ module.exports = { }, required: ['id'] }, - getUnconfirmedTransaction: { - id: 'transactions.getUnconfirmedTransaction', + getPooledTransaction: { + id: 'transactions.getPooledTransaction', type: 'object', properties: { id: { @@ -94,8 +94,8 @@ module.exports = { }, required: ['id'] }, - getUnconfirmedTransactions: { - id: 'transactions.getUnconfirmedTransactions', + getPooledTransactions: { + id: 'transactions.getPooledTransactions', type: 'object', properties: { senderPublicKey: { diff --git a/test/api/transactions.js b/test/api/transactions.js index 95bf0f83253..b91b738d2fe 100644 --- a/test/api/transactions.js +++ b/test/api/transactions.js @@ -247,6 +247,56 @@ describe('GET /api/transactions/get?id=', function () { }); }); +describe('GET /api/transactions/queued/get?id=', function () { + + it('using unknown id should be ok', function (done) { + var params = 'id=' + '1234'; + + node.get('/api/transactions/queued/get?' + params, function (err, res) { + node.expect(res.body).to.have.property('success').to.be.false; + node.expect(res.body).to.have.property('error').that.is.equal('Transaction not found'); + done(); + }); + }); +}); + +describe('GET /api/transactions/queued', function () { + + it('should be ok', function (done) { + node.get('/api/transactions/queued', function (err, res) { + node.expect(res.body).to.have.property('success').to.be.ok; + node.expect(res.body).to.have.property('transactions').that.is.an('array'); + node.expect(res.body).to.have.property('count').that.is.an('number'); + done(); + }); + }); +}); + +describe('GET /api/transactions/multisignatures/get?id=', function () { + + it('using unknown id should be ok', function (done) { + var params = 'id=' + '1234'; + + node.get('/api/transactions/multisignatures/get?' + params, function (err, res) { + node.expect(res.body).to.have.property('success').to.be.false; + node.expect(res.body).to.have.property('error').that.is.equal('Transaction not found'); + done(); + }); + }); +}); + +describe('GET /api/transactions/multisignatures', function () { + + it('should be ok', function (done) { + node.get('/api/transactions/multisignatures', function (err, res) { + node.expect(res.body).to.have.property('success').to.be.ok; + node.expect(res.body).to.have.property('transactions').that.is.an('array'); + node.expect(res.body).to.have.property('count').that.is.an('number'); + done(); + }); + }); +}); + describe('GET /api/transactions/unconfirmed/get?id=', function () { it('using valid id should be ok', function (done) { @@ -271,6 +321,7 @@ describe('GET /api/transactions/unconfirmed', function () { node.get('/api/transactions/unconfirmed', function (err, res) { node.expect(res.body).to.have.property('success').to.be.ok; node.expect(res.body).to.have.property('transactions').that.is.an('array'); + node.expect(res.body).to.have.property('count').that.is.an('number'); done(); }); }); From 763edd28c0a959ed2ecaaa002ad61533106d610b Mon Sep 17 00:00:00 2001 From: Oliver Beddows Date: Sat, 12 Nov 2016 20:42:10 +0100 Subject: [PATCH 164/272] Returning error message. --- modules/accounts.js | 2 +- modules/blocks.js | 2 +- modules/dapps.js | 4 ++-- modules/delegates.js | 2 +- modules/loader.js | 2 +- modules/multisignatures.js | 2 +- modules/peers.js | 2 +- modules/signatures.js | 2 +- modules/transactions.js | 2 +- modules/transport.js | 2 +- 10 files changed, 11 insertions(+), 11 deletions(-) diff --git a/modules/accounts.js b/modules/accounts.js index 8faaa468db7..918d98f1937 100644 --- a/modules/accounts.js +++ b/modules/accounts.js @@ -100,7 +100,7 @@ __private.attachApi = function () { library.network.app.use('/api/accounts', router); library.network.app.use(function (err, req, res, next) { if (!err) { return next(); } - library.logger.error('API error ' + req.url, err); + library.logger.error('API error ' + req.url, err.message); res.status(500).send({success: false, error: 'API error: ' + err.message}); }); }; diff --git a/modules/blocks.js b/modules/blocks.js index 0028a966bb9..a226a69e252 100644 --- a/modules/blocks.js +++ b/modules/blocks.js @@ -118,7 +118,7 @@ __private.attachApi = function () { library.network.app.use('/api/blocks', router); library.network.app.use(function (err, req, res, next) { if (!err) { return next(); } - library.logger.error('API error ' + req.url, err); + library.logger.error('API error ' + req.url, err.message); res.status(500).send({success: false, error: 'API error: ' + err.message}); }); }; diff --git a/modules/dapps.js b/modules/dapps.js index 68d103653a1..8d9c770b779 100644 --- a/modules/dapps.js +++ b/modules/dapps.js @@ -526,7 +526,7 @@ __private.attachApi = function () { library.network.app.use('/api/dapps', router); library.network.app.use(function (err, req, res, next) { if (!err) { return next(); } - library.logger.error('API error ' + req.url, err); + library.logger.error('API error ' + req.url, err.message); res.status(500).send({success: false, error: 'API error: ' + err.message}); }); }; @@ -925,7 +925,7 @@ __private.createRoutes = function (dapp, cb) { library.network.app.use('/api/dapps/' + dapp.transactionId + '/api/', __private.routes[dapp.transactionId]); library.network.app.use(function (err, req, res, next) { if (!err) { return next(); } - library.logger.error('API error ' + req.url, err); + library.logger.error('API error ' + req.url, err.message); res.status(500).send({success: false, error: 'API error: ' + err.message}); }); diff --git a/modules/delegates.js b/modules/delegates.js index 4bd0fb55827..7079c544ea2 100644 --- a/modules/delegates.js +++ b/modules/delegates.js @@ -181,7 +181,7 @@ __private.attachApi = function () { library.network.app.use('/api/delegates', router); library.network.app.use(function (err, req, res, next) { if (!err) { return next(); } - library.logger.error('API error ' + req.url, err); + library.logger.error('API error ' + req.url, err.message); res.status(500).send({success: false, error: 'API error: ' + err.message}); }); }; diff --git a/modules/loader.js b/modules/loader.js index dbc5d931c11..96ac9f9d5ac 100644 --- a/modules/loader.js +++ b/modules/loader.js @@ -59,7 +59,7 @@ __private.attachApi = function () { library.network.app.use('/api/loader', router); library.network.app.use(function (err, req, res, next) { if (!err) { return next(); } - library.logger.error('API error ' + req.url, err); + library.logger.error('API error ' + req.url, err.message); res.status(500).send({success: false, error: 'API error: ' + err.message}); }); }; diff --git a/modules/multisignatures.js b/modules/multisignatures.js index d6073207c73..47f78215185 100644 --- a/modules/multisignatures.js +++ b/modules/multisignatures.js @@ -55,7 +55,7 @@ __private.attachApi = function () { library.network.app.use('/api/multisignatures', router); library.network.app.use(function (err, req, res, next) { if (!err) { return next(); } - library.logger.error('API error ' + req.url, err); + library.logger.error('API error ' + req.url, err.message); res.status(500).send({success: false, error: 'API error: ' + err.message}); }); }; diff --git a/modules/peers.js b/modules/peers.js index af55c8b3663..faa1795d026 100644 --- a/modules/peers.js +++ b/modules/peers.js @@ -55,7 +55,7 @@ __private.attachApi = function () { library.network.app.use('/api/peers', router); library.network.app.use(function (err, req, res, next) { if (!err) { return next(); } - library.logger.error('API error ' + req.url, err); + library.logger.error('API error ' + req.url, err.message); res.status(500).send({success: false, error: 'API error: ' + err.message}); }); }; diff --git a/modules/signatures.js b/modules/signatures.js index 4cf69a16a4c..b60407b1683 100644 --- a/modules/signatures.js +++ b/modules/signatures.js @@ -51,7 +51,7 @@ __private.attachApi = function () { library.network.app.use('/api/signatures', router); library.network.app.use(function (err, req, res, next) { if (!err) { return next(); } - library.logger.error('API error ' + req.url, err); + library.logger.error('API error ' + req.url, err.message); res.status(500).send({success: false, error: 'API error: ' + err.message}); }); }; diff --git a/modules/transactions.js b/modules/transactions.js index 757fda19cec..0add0e9e698 100644 --- a/modules/transactions.js +++ b/modules/transactions.js @@ -65,7 +65,7 @@ __private.attachApi = function () { library.network.app.use('/api/transactions', router); library.network.app.use(function (err, req, res, next) { if (!err) { return next(); } - library.logger.error('API error ' + req.url, err); + library.logger.error('API error ' + req.url, err.message); res.status(500).send({success: false, error: 'API error: ' + err.message}); }); }; diff --git a/modules/transport.js b/modules/transport.js index 61bb2124813..d411080d7e2 100644 --- a/modules/transport.js +++ b/modules/transport.js @@ -320,7 +320,7 @@ __private.attachApi = function () { library.network.app.use(function (err, req, res, next) { if (!err) { return next(); } - library.logger.error('API error ' + req.url, err); + library.logger.error('API error ' + req.url, err.message); res.status(500).send({success: false, error: 'API error: ' + err.message}); }); }; From 113d61caf39942964eb2ad25d4f0419ec7d3d12c Mon Sep 17 00:00:00 2001 From: Oliver Beddows Date: Sat, 12 Nov 2016 20:55:12 +0100 Subject: [PATCH 165/272] Testing unit first. --- test/index.js | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/test/index.js b/test/index.js index d59b248f0fc..1f1a58f85fd 100644 --- a/test/index.js +++ b/test/index.js @@ -1,3 +1,6 @@ +require('./unit/helpers/request-limiter.js'); +require('./unit/logic/blockReward.js'); + require('./api/accounts.js'); require('./api/blocks.js'); require('./api/dapps.js'); @@ -12,6 +15,3 @@ require('./api/peer.transactions.votes.js'); require('./api/peers.js'); require('./api/signatures.js'); require('./api/transactions.js'); - -require('./unit/helpers/request-limiter.js'); -require('./unit/logic/blockReward.js'); From c6669701ce157bfbfe782b5b93c63a38c90bc9f2 Mon Sep 17 00:00:00 2001 From: Oliver Beddows Date: Sun, 13 Nov 2016 14:20:03 +0100 Subject: [PATCH 166/272] Renaming variables for clarity. --- modules/blocks.js | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/modules/blocks.js b/modules/blocks.js index a226a69e252..4aa2ec4db7b 100644 --- a/modules/blocks.js +++ b/modules/blocks.js @@ -427,20 +427,20 @@ __private.applyBlock = function (block, broadcast, cb, saveBlock) { // Transactions to rewind in case of error. var appliedTransactions = {}; - // List of currrently unconfirmed transactions. - var unconfirmedTransactions; + // List of unconfirmed transactions ids. + var unconfirmedTransactionIds; async.series({ // Rewind any unconfirmed transactions before applying block. // TODO: It should be possible to remove this call if we can guarantee that only this function is processing transactions atomically. Then speed should be improved further. // TODO: Other possibility, when we rebuild from block chain this action should be moved out of the rebuild function. undoUnconfirmedList: function (seriesCb) { - modules.transactions.undoUnconfirmedList(function (err, transactions) { + modules.transactions.undoUnconfirmedList(function (err, ids) { if (err) { // TODO: Send a numbered signal to be caught by forever to trigger a rebuild. return process.exit(0); } else { - unconfirmedTransactions = transactions; + unconfirmedTransactionIds = ids; return setImmediate(seriesCb); } }); @@ -462,9 +462,9 @@ __private.applyBlock = function (block, broadcast, cb, saveBlock) { appliedTransactions[transaction.id] = transaction; // Remove the transaction from the node queue, if it was present. - var index = unconfirmedTransactions.indexOf(transaction.id); + var index = unconfirmedTransactionIds.indexOf(transaction.id); if (index >= 0) { - unconfirmedTransactions.splice(index, 1); + unconfirmedTransactionIds.splice(index, 1); } return setImmediate(eachSeriesCb); @@ -566,7 +566,7 @@ __private.applyBlock = function (block, broadcast, cb, saveBlock) { // Nullify large objects. // Prevents memory leak during synchronisation. - appliedTransactions = unconfirmedTransactions = block = null; + appliedTransactions = unconfirmedTransactionIds = block = null; // Finish here if snapshotting. if (err === 'Snapshot finished') { From 5bc1a142e0f0bc728e86ed4b2c95975d9da4fca5 Mon Sep 17 00:00:00 2001 From: Oliver Beddows Date: Sun, 13 Nov 2016 14:25:47 +0100 Subject: [PATCH 167/272] Adding maxPeers constant. --- helpers/constants.js | 1 + logic/broadcaster.js | 2 +- modules/peers.js | 3 ++- modules/transport.js | 3 ++- 4 files changed, 6 insertions(+), 3 deletions(-) diff --git a/helpers/constants.js b/helpers/constants.js index 0501769f7bb..a1363af7d9b 100644 --- a/helpers/constants.js +++ b/helpers/constants.js @@ -23,6 +23,7 @@ module.exports = { maxClientConnections: 100, maxConfirmations : 77 * 100, maxPayloadLength: 1024 * 1024, + maxPeers: 100, maxRequests: 10000 * 12, maxSharedTxs: 100, maxSignaturesLength: 196 * 256, diff --git a/logic/broadcaster.js b/logic/broadcaster.js index 3ceecf96d71..b60be59a842 100644 --- a/logic/broadcaster.js +++ b/logic/broadcaster.js @@ -13,7 +13,7 @@ function Broadcaster (scope) { self = this; self.queue = []; - self.peerLimit = 100; + self.peerLimit = constants.maxPeers; self.broadcastLimit = 20; self.releaseLimit = constants.maxTxsPerBlock; self.broadcastInterval = 5000; diff --git a/modules/peers.js b/modules/peers.js index faa1795d026..1a9f334f261 100644 --- a/modules/peers.js +++ b/modules/peers.js @@ -2,6 +2,7 @@ var _ = require('lodash'); var async = require('async'); +var constants = require('constants'); var extend = require('extend'); var fs = require('fs'); var ip = require('ip'); @@ -247,7 +248,7 @@ Peers.prototype.acceptable = function (peers) { }; Peers.prototype.list = function (options, cb) { - options.limit = options.limit || 100; + options.limit = options.limit || constants.maxPeers; options.broadhash = options.broadhash || modules.system.getBroadhash(); options.attempts = ['matched broadhash', 'unmatched broadhash', 'legacy']; options.attempt = 0; diff --git a/modules/transport.js b/modules/transport.js index d411080d7e2..c26521abcd5 100644 --- a/modules/transport.js +++ b/modules/transport.js @@ -4,6 +4,7 @@ var _ = require('lodash'); var async = require('async'); var Broadcaster = require('../logic/broadcaster.js'); var bignum = require('../helpers/bignum.js'); +var constants = require('constants'); var crypto = require('crypto'); var extend = require('extend'); var ip = require('ip'); @@ -80,7 +81,7 @@ __private.attachApi = function () { }); router.get('/list', function (req, res) { - modules.peers.list({limit: 100}, function (err, peers) { + modules.peers.list({limit: constants.maxPeers}, function (err, peers) { return res.status(200).json({peers: !err ? peers : []}); }); }); From 2c4eedd29a0c591df0b0301aff2b198bb8f41adc Mon Sep 17 00:00:00 2001 From: Oliver Beddows Date: Sun, 13 Nov 2016 14:26:01 +0100 Subject: [PATCH 168/272] Removing maxClientConnections constant. --- helpers/constants.js | 1 - 1 file changed, 1 deletion(-) diff --git a/helpers/constants.js b/helpers/constants.js index a1363af7d9b..f7eb994a698 100644 --- a/helpers/constants.js +++ b/helpers/constants.js @@ -20,7 +20,6 @@ module.exports = { fixedPoint : Math.pow(10, 8), maxAddressesLength: 208 * 128, maxAmount: 100000000, - maxClientConnections: 100, maxConfirmations : 77 * 100, maxPayloadLength: 1024 * 1024, maxPeers: 100, From ac22692a92977cd5b335936e96bc835f2c05f94a Mon Sep 17 00:00:00 2001 From: Oliver Beddows Date: Sun, 13 Nov 2016 14:26:39 +0100 Subject: [PATCH 169/272] Renaming __private.loadUnconfirmedTransactions function. --- modules/loader.js | 10 +++++----- schema/loader.js | 4 ++-- 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/modules/loader.js b/modules/loader.js index 96ac9f9d5ac..76f09cbdc34 100644 --- a/modules/loader.js +++ b/modules/loader.js @@ -127,7 +127,7 @@ __private.loadSignatures = function (cb) { }); }; -__private.loadUnconfirmedTransactions = function (cb) { +__private.loadTransactions = function (cb) { async.waterfall([ function (waterCb) { self.getNetwork(function (err, network) { @@ -140,7 +140,7 @@ __private.loadUnconfirmedTransactions = function (cb) { }); }, function (peer, waterCb) { - library.logger.log('Loading unconfirmed transactions from: ' + peer.string); + library.logger.log('Loading transactions from: ' + peer.string); modules.transport.getFromPeer(peer, { api: '/transactions', @@ -150,7 +150,7 @@ __private.loadUnconfirmedTransactions = function (cb) { return setImmediate(waterCb, err); } - library.schema.validate(res.body, schema.loadUnconfirmedTransactions, function (err) { + library.schema.validate(res.body, schema.loadTransactions, function (err) { if (err) { return setImmediate(waterCb, err[0].message); } else { @@ -665,9 +665,9 @@ Loader.prototype.onPeersReady = function () { return setImmediate(seriesCb); } }, - loadUnconfirmedTransactions: function (seriesCb) { + loadTransactions: function (seriesCb) { if (__private.loaded) { - async.retry(retries, __private.loadUnconfirmedTransactions, function (err) { + async.retry(retries, __private.loadTransactions, function (err) { if (err) { library.logger.log('Unconfirmed transactions timer', err); } diff --git a/schema/loader.js b/schema/loader.js index 5e0b0c2ad2b..16058ba0010 100644 --- a/schema/loader.js +++ b/schema/loader.js @@ -13,8 +13,8 @@ module.exports = { }, required: ['signatures'] }, - loadUnconfirmedTransactions: { - id: 'loader.loadUnconfirmedTransactions', + loadTransactions: { + id: 'loader.loadTransactions', type: 'object', properties: { transactions: { From 6146701377f404979027f1c3fe8b194547db99cc Mon Sep 17 00:00:00 2001 From: Oliver Beddows Date: Sun, 13 Nov 2016 14:39:42 +0100 Subject: [PATCH 170/272] Do not always broadcast on receiving transactions #306. --- modules/accounts.js | 4 ++-- modules/dapps.js | 10 +++++----- modules/delegates.js | 4 ++-- modules/loader.js | 2 +- modules/multisignatures.js | 2 +- modules/signatures.js | 4 ++-- modules/transactions.js | 12 ++++-------- modules/transport.js | 2 +- 8 files changed, 18 insertions(+), 22 deletions(-) diff --git a/modules/accounts.js b/modules/accounts.js index 918d98f1937..2d6832a5d0b 100644 --- a/modules/accounts.js +++ b/modules/accounts.js @@ -416,7 +416,7 @@ shared.addDelegates = function (req, cb) { return setImmediate(cb, e.toString()); } - modules.transactions.receiveTransactions([transaction], cb); + modules.transactions.receiveTransactions([transaction], true, cb); }); }); } else { @@ -454,7 +454,7 @@ shared.addDelegates = function (req, cb) { return setImmediate(cb, e.toString()); } - modules.transactions.receiveTransactions([transaction], cb); + modules.transactions.receiveTransactions([transaction], true, cb); }); } }, function (err, transaction) { diff --git a/modules/dapps.js b/modules/dapps.js index 8d9c770b779..a6df7a3171e 100644 --- a/modules/dapps.js +++ b/modules/dapps.js @@ -158,7 +158,7 @@ __private.attachApi = function () { return setImmediate(cb, e.toString()); } - modules.transactions.receiveTransactions([transaction], cb); + modules.transactions.receiveTransactions([transaction], true, cb); }); }, function (err, transaction) { if (err) { @@ -1206,7 +1206,7 @@ __private.addTransactions = function (req, cb) { return setImmediate(cb, e.toString()); } - modules.transactions.receiveTransactions([transaction], cb); + modules.transactions.receiveTransactions([transaction], true, cb); }); }); } else { @@ -1245,7 +1245,7 @@ __private.addTransactions = function (req, cb) { return setImmediate(cb, e.toString()); } - modules.transactions.receiveTransactions([transaction], cb); + modules.transactions.receiveTransactions([transaction], true, cb); }); } }, function (err, transaction) { @@ -1329,7 +1329,7 @@ __private.sendWithdrawal = function (req, cb) { return setImmediate(cb, e.toString()); } - modules.transactions.receiveTransactions([transaction], cb); + modules.transactions.receiveTransactions([transaction], true, cb); }); }); } else { @@ -1370,7 +1370,7 @@ __private.sendWithdrawal = function (req, cb) { return setImmediate(cb, e.toString()); } - modules.transactions.receiveTransactions([transaction], cb); + modules.transactions.receiveTransactions([transaction], true, cb); }); } }, function (err, transaction) { diff --git a/modules/delegates.js b/modules/delegates.js index 7079c544ea2..6e7e444c7ad 100644 --- a/modules/delegates.js +++ b/modules/delegates.js @@ -808,7 +808,7 @@ shared.addDelegate = function (req, cb) { } catch (e) { return setImmediate(cb, e.toString()); } - modules.transactions.receiveTransactions([transaction], cb); + modules.transactions.receiveTransactions([transaction], true, cb); }); }); } else { @@ -845,7 +845,7 @@ shared.addDelegate = function (req, cb) { } catch (e) { return setImmediate(cb, e.toString()); } - modules.transactions.receiveTransactions([transaction], cb); + modules.transactions.receiveTransactions([transaction], true, cb); }); } }, function (err, transaction) { diff --git a/modules/loader.js b/modules/loader.js index 76f09cbdc34..b480394d2e1 100644 --- a/modules/loader.js +++ b/modules/loader.js @@ -182,7 +182,7 @@ __private.loadTransactions = function (cb) { }, function (transactions, waterCb) { library.balancesSequence.add(function (cb) { - modules.transactions.receiveTransactions(transactions, cb); + modules.transactions.receiveTransactions(transactions, false, cb); }, waterCb); } ], function (err) { diff --git a/modules/multisignatures.js b/modules/multisignatures.js index 47f78215185..3abb66dab82 100644 --- a/modules/multisignatures.js +++ b/modules/multisignatures.js @@ -502,7 +502,7 @@ shared.addMultisignature = function (req, cb) { return setImmediate(seriesCb, e.toString()); } - modules.transactions.receiveTransactions([scope.transaction], seriesCb); + modules.transactions.receiveTransactions([scope.transaction], true, seriesCb); }); } }, function (err) { diff --git a/modules/signatures.js b/modules/signatures.js index b60407b1683..ba192745f8d 100644 --- a/modules/signatures.js +++ b/modules/signatures.js @@ -151,7 +151,7 @@ shared.addSignature = function (req, cb) { return setImmediate(cb, e.toString()); } - modules.transactions.receiveTransactions([transaction], cb); + modules.transactions.receiveTransactions([transaction], true, cb); }); }); } else { @@ -182,7 +182,7 @@ shared.addSignature = function (req, cb) { } catch (e) { return setImmediate(cb, e.toString()); } - modules.transactions.receiveTransactions([transaction], cb); + modules.transactions.receiveTransactions([transaction], true, cb); }); } diff --git a/modules/transactions.js b/modules/transactions.js index 0add0e9e698..e384283b12f 100644 --- a/modules/transactions.js +++ b/modules/transactions.js @@ -326,12 +326,8 @@ Transactions.prototype.undoUnconfirmed = function (transaction, cb) { }); }; -Transactions.prototype.receiveTransactions = function (transactions, cb) { - async.eachSeries(transactions, function (transaction, cb) { - self.processUnconfirmedTransaction(transaction, true, cb); - }, function (err) { - return setImmediate(cb, err, transactions); - }); +Transactions.prototype.receiveTransactions = function (transactions, broadcast, cb) { + return __private.transactionPool.receiveTransactions(transactions, broadcast, cb); }; Transactions.prototype.fillPool = function (cb) { @@ -498,7 +494,7 @@ shared.addTransactions = function (req, cb) { return setImmediate(cb, e.toString()); } - modules.transactions.receiveTransactions([transaction], cb); + modules.transactions.receiveTransactions([transaction], true, cb); }); }); } else { @@ -537,7 +533,7 @@ shared.addTransactions = function (req, cb) { return setImmediate(cb, e.toString()); } - modules.transactions.receiveTransactions([transaction], cb); + modules.transactions.receiveTransactions([transaction], true, cb); }); } }); diff --git a/modules/transport.js b/modules/transport.js index c26521abcd5..df3bfa052bc 100644 --- a/modules/transport.js +++ b/modules/transport.js @@ -220,7 +220,7 @@ __private.attachApi = function () { library.balancesSequence.add(function (cb) { library.logger.debug('Received transaction ' + transaction.id + ' from peer ' + req.peer.string); - modules.transactions.receiveTransactions([transaction], cb); + modules.transactions.receiveTransactions([transaction], true, cb); }, function (err) { if (err) { library.logger.debug(['Transaction', id].join(' '), err.toString()); From 9e677e6c368f6fad0e571cf87a3a83abddfa4ba5 Mon Sep 17 00:00:00 2001 From: Oliver Beddows Date: Sun, 13 Nov 2016 14:50:39 +0100 Subject: [PATCH 171/272] Breaking up /api/peer/transactions tests. --- test/api/peer.transactions.collision.js | 85 +++++++++++++++++++ ...nsactions.js => peer.transactions.main.js} | 0 test/index.js | 3 +- 3 files changed, 87 insertions(+), 1 deletion(-) create mode 100644 test/api/peer.transactions.collision.js rename test/api/{peer.transactions.js => peer.transactions.main.js} (100%) diff --git a/test/api/peer.transactions.collision.js b/test/api/peer.transactions.collision.js new file mode 100644 index 00000000000..6bd3d1e8d62 --- /dev/null +++ b/test/api/peer.transactions.collision.js @@ -0,0 +1,85 @@ +'use strict'; /*jslint mocha:true, expr:true */ + +var crypto = require('crypto'); +var node = require('./../node.js'); + +function postTransaction (transaction, done) { + node.post('/peer/transactions', { + transaction: transaction + }, done); +} + +describe('POST /peer/transactions', function () { + + describe('when two passphrases collide into the same address', function () { + + var collision = { + address: '13555181540209512417L', + passphrases: [ + 'merry field slogan sibling convince gold coffee town fold glad mix page', + 'annual youth lift quote off olive uncle town chief poverty extend series' + ] + }; + + before(function (done) { + var transaction = node.lisk.transaction.createTransaction(collision.address, 220000000, node.gAccount.password); + postTransaction(transaction, function (err, res) { + node.expect(res.body).to.have.property('success').to.be.ok; + node.onNewBlock(done); + }); + }); + + describe('when transaction is invalid', function () { + + it('should fail for passphrase two', function (done) { + var transaction = node.lisk.transaction.createTransaction(node.gAccount.address, 100000000, collision.passphrases[1]); + transaction.signature = crypto.randomBytes(64).toString('hex'); + transaction.id = node.lisk.crypto.getId(transaction); + + postTransaction(transaction, function (err, res) { + node.expect(res.body).to.have.property('success').to.be.not.ok; + node.expect(res.body).to.have.property('message').to.equal('Failed to verify signature'); + done(); + }); + }); + + it('should fail for passphrase one', function (done) { + var transaction = node.lisk.transaction.createTransaction(node.gAccount.address, 100000000, collision.passphrases[0]); + transaction.signature = crypto.randomBytes(64).toString('hex'); + transaction.id = node.lisk.crypto.getId(transaction); + + postTransaction(transaction, function (err, res) { + node.expect(res.body).to.have.property('success').to.be.not.ok; + node.expect(res.body).to.have.property('message').to.equal('Failed to verify signature'); + done(); + }); + }); + }); + + describe('when transaction is valid', function () { + + beforeEach(function (done) { + node.onNewBlock(done); + }); + + it('should be ok for passphrase one', function (done) { + var transaction = node.lisk.transaction.createTransaction(node.gAccount.address, 100000000, collision.passphrases[0]); + + postTransaction(transaction, function (err, res) { + node.expect(res.body).to.have.property('success').to.be.ok; + done(); + }); + }); + + it('should fail for passphrase two', function (done) { + var transaction = node.lisk.transaction.createTransaction(node.gAccount.address, 100000000, collision.passphrases[1]); + + postTransaction(transaction, function (err, res) { + node.expect(res.body).to.have.property('success').to.be.not.ok; + node.expect(res.body).to.have.property('message').to.equal('Invalid sender public key: b26dd40ba33e4785e49ddc4f106c0493ed00695817235c778f487aea5866400a expected: ce33db918b059a6e99c402963b42cf51c695068007ef01d8c383bb8a41270263'); + done(); + }); + }); + }); + }); +}); diff --git a/test/api/peer.transactions.js b/test/api/peer.transactions.main.js similarity index 100% rename from test/api/peer.transactions.js rename to test/api/peer.transactions.main.js diff --git a/test/index.js b/test/index.js index 1f1a58f85fd..86fe0fcaa5f 100644 --- a/test/index.js +++ b/test/index.js @@ -8,8 +8,9 @@ require('./api/delegates.js'); require('./api/loader.js'); require('./api/multisignatures.js'); require('./api/peer.js'); +require('./api/peer.transactions.main.js'); +require('./api/peer.transactions.collision.js'); require('./api/peer.transactions.delegates.js'); -require('./api/peer.transactions.js'); require('./api/peer.transactions.signatures.js'); require('./api/peer.transactions.votes.js'); require('./api/peers.js'); From 0ba271aa62b3432bdb9aca7a9a60f5607f72c6c7 Mon Sep 17 00:00:00 2001 From: Oliver Beddows Date: Sun, 13 Nov 2016 14:50:59 +0100 Subject: [PATCH 172/272] Closes #306. Adding transaction stress tests. Closes #259. Closes #309. --- test/api/peer.transactions.stress.js | 54 ++++++++++++++++++++++++++++ 1 file changed, 54 insertions(+) create mode 100644 test/api/peer.transactions.stress.js diff --git a/test/api/peer.transactions.stress.js b/test/api/peer.transactions.stress.js new file mode 100644 index 00000000000..df3893eecd1 --- /dev/null +++ b/test/api/peer.transactions.stress.js @@ -0,0 +1,54 @@ +'use strict'; /*jslint mocha:true, expr:true */ + +var node = require('./../node.js'); + +function postTransaction (transaction, done) { + node.post('/peer/transactions', { + transaction: transaction + }, done); +} + +describe('POST /peer/transactions', function () { + + describe('sending 1000 transfers to random addresses', function () { + + var transactions = []; + var maximum = 1000; + var count = 1; + + before(function (done) { + node.async.doUntil(function (next) { + var transaction = node.lisk.transaction.createTransaction( + node.randomAccount().address, + node.randomNumber(100000000, 1000000000), + node.gAccount.password + ); + + postTransaction(transaction, function (err, res) { + node.expect(res.body).to.have.property('success').to.be.ok; + node.expect(res.body).to.have.property('transactionId').to.equal(transaction.id); + transactions.push(transaction); + count++; + next(); + }); + }, function () { + return (count === maximum); + }, function (err) { + done(err); + }); + }); + + it('should confirm all transactions', function (done) { + var blocksToWait = maximum / node.constants.maxTxsPerBlock + 1; + node.waitForBlocks(blocksToWait, function (err) { + node.async.eachSeries(transactions, function (transaction, eachSeriesCb) { + node.get('/api/transactions/get?id=' + transaction.id, function (err, res) { + node.expect(res.body).to.have.property('success').to.be.ok; + node.expect(res.body).to.have.property('transaction').that.is.an('object'); + return setImmediate(eachSeriesCb); + }); + }, done); + }); + }).timeout(500000); + }); +}); From 00a0d45c8d3c83a739883a23a5b9e778e9eebdfc Mon Sep 17 00:00:00 2001 From: Oliver Beddows Date: Mon, 14 Nov 2016 00:58:00 +0100 Subject: [PATCH 173/272] Increasing poolSize from 20 to 100. --- config.json | 2 +- test/config.json | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/config.json b/config.json index 5483a60767b..91f628d5731 100644 --- a/config.json +++ b/config.json @@ -13,7 +13,7 @@ "database": "lisk_main", "user": null, "password": "password", - "poolSize": 20, + "poolSize": 100, "poolIdleTimeout": 30000, "reapIntervalMillis": 1000, "logEvents": [ diff --git a/test/config.json b/test/config.json index 142988b3c70..b7f1ef39a69 100644 --- a/test/config.json +++ b/test/config.json @@ -13,7 +13,7 @@ "database": "lisk_test", "user": null, "password": "password", - "poolSize": 20, + "poolSize": 100, "poolIdleTimeout": 30000, "reapIntervalMillis": 1000, "logEvents": [ From 9bee9901731941767f5ba2ff7dd91d75547f1220 Mon Sep 17 00:00:00 2001 From: TheGoldenEye Date: Fri, 18 Nov 2016 20:54:37 +0100 Subject: [PATCH 174/272] Prevent "TypeError: Cannot read property 'network' of undefined" (#314). Closes #273. If starting with empty database and a dapp enabled. --- logic/dapp.js | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/logic/dapp.js b/logic/dapp.js index 0a85ff5a012..f8666902248 100644 --- a/logic/dapp.js +++ b/logic/dapp.js @@ -337,7 +337,8 @@ DApp.prototype.dbSave = function (trs) { }; DApp.prototype.afterSave = function (trs, cb) { - library.network.io.sockets.emit('dapps/change', trs); + if (library) + library.network.io.sockets.emit('dapps/change', {}); return setImmediate(cb); }; From ed7cb92763e5e60a6a978a255a18201fcd7594bc Mon Sep 17 00:00:00 2001 From: TheGoldenEye Date: Fri, 18 Nov 2016 21:00:48 +0100 Subject: [PATCH 175/272] Fixes #51. Invalid getGenesis parameter (#315). --- logic/inTransfer.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/logic/inTransfer.js b/logic/inTransfer.js index b47208d1cab..2977cdcde4f 100644 --- a/logic/inTransfer.js +++ b/logic/inTransfer.js @@ -76,7 +76,7 @@ InTransfer.prototype.getBytes = function (trs) { }; InTransfer.prototype.apply = function (trs, block, sender, cb) { - shared.getGenesis({id: trs.asset.inTransfer.dappId}, function (err, res) { + shared.getGenesis({dappid: trs.asset.inTransfer.dappId}, function (err, res) { if (err) { return setImmediate(cb, err); } @@ -93,7 +93,7 @@ InTransfer.prototype.apply = function (trs, block, sender, cb) { }; InTransfer.prototype.undo = function (trs, block, sender, cb) { - shared.getGenesis({id: trs.asset.inTransfer.dappId}, function (err, res) { + shared.getGenesis({dappid: trs.asset.inTransfer.dappId}, function (err, res) { if (err) { return setImmediate(cb, err); } From 2ea735832f6fea9b2e0ddc42bd04c4f04b1c06a7 Mon Sep 17 00:00:00 2001 From: Oliver Beddows Date: Fri, 18 Nov 2016 21:06:51 +0100 Subject: [PATCH 176/272] Standardising code. --- logic/dapp.js | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/logic/dapp.js b/logic/dapp.js index f8666902248..46b41238489 100644 --- a/logic/dapp.js +++ b/logic/dapp.js @@ -337,8 +337,9 @@ DApp.prototype.dbSave = function (trs) { }; DApp.prototype.afterSave = function (trs, cb) { - if (library) + if (library) { library.network.io.sockets.emit('dapps/change', {}); + } return setImmediate(cb); }; From 07f3b529ba82c9dfd7b95f27312426b7ea3ca6e9 Mon Sep 17 00:00:00 2001 From: Oliver Beddows Date: Tue, 15 Nov 2016 14:06:12 +0100 Subject: [PATCH 177/272] Fixing incorrect requirements. --- modules/peers.js | 2 +- modules/transport.js | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/modules/peers.js b/modules/peers.js index 1a9f334f261..921b5d64f8e 100644 --- a/modules/peers.js +++ b/modules/peers.js @@ -2,7 +2,7 @@ var _ = require('lodash'); var async = require('async'); -var constants = require('constants'); +var constants = require('../helpers/constants.js'); var extend = require('extend'); var fs = require('fs'); var ip = require('ip'); diff --git a/modules/transport.js b/modules/transport.js index df3bfa052bc..61f4cadd507 100644 --- a/modules/transport.js +++ b/modules/transport.js @@ -4,7 +4,7 @@ var _ = require('lodash'); var async = require('async'); var Broadcaster = require('../logic/broadcaster.js'); var bignum = require('../helpers/bignum.js'); -var constants = require('constants'); +var constants = require('../helpers/constants.js'); var crypto = require('crypto'); var extend = require('extend'); var ip = require('ip'); From 01cae646452fa9eb699c9bb3463998bdcfeeb9d8 Mon Sep 17 00:00:00 2001 From: Oliver Beddows Date: Tue, 15 Nov 2016 14:52:30 +0100 Subject: [PATCH 178/272] Slicing accepted peers up to maxPeers. --- modules/peers.js | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/modules/peers.js b/modules/peers.js index 921b5d64f8e..8d20e5afd04 100644 --- a/modules/peers.js +++ b/modules/peers.js @@ -244,7 +244,8 @@ Peers.prototype.acceptable = function (peers) { }).uniqWith(function (a, b) { // Removing non-unique peers return (a.ip + a.port) === (b.ip + b.port); - }).value(); + // Slicing peers up to maxPeers + }).slice(0, constants.maxPeers).value(); }; Peers.prototype.list = function (options, cb) { From 79cbda32680258d17d4930ed196d3e450471017c Mon Sep 17 00:00:00 2001 From: Oliver Beddows Date: Tue, 15 Nov 2016 14:55:00 +0100 Subject: [PATCH 179/272] Removing schema validations. Relying on modules.peers.acceptable to remove non-unique entries and limit collection. --- schema/loader.js | 4 +--- schema/peers.js | 4 +--- 2 files changed, 2 insertions(+), 6 deletions(-) diff --git a/schema/loader.js b/schema/loader.js index 16058ba0010..beb05e67d4d 100644 --- a/schema/loader.js +++ b/schema/loader.js @@ -31,9 +31,7 @@ module.exports = { type: 'object', properties: { peers: { - type: 'array', - uniqueItems: true, - maxItems: 100 + type: 'array' } }, required: ['peers'] diff --git a/schema/peers.js b/schema/peers.js index 0fffd1f0a90..7d83101cc9c 100644 --- a/schema/peers.js +++ b/schema/peers.js @@ -7,9 +7,7 @@ module.exports = { type: 'object', properties: { peers: { - type: 'array', - uniqueItems: false, - maxItems: 100 + type: 'array' } }, required: ['peers'] From 45c5713a57411a1933f5f86e71ed0248b612d497 Mon Sep 17 00:00:00 2001 From: Oliver Beddows Date: Tue, 15 Nov 2016 14:55:47 +0100 Subject: [PATCH 180/272] Accepting peers immediately after schema validation. --- modules/loader.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/modules/loader.js b/modules/loader.js index b480394d2e1..c3694113cff 100644 --- a/modules/loader.js +++ b/modules/loader.js @@ -604,7 +604,7 @@ Loader.prototype.getNetwork = function (cb) { }, function (res, waterCb) { library.schema.validate(res.body, schema.getNetwork.peers, function (err) { - var peers = res.body.peers; + var peers = modules.peers.acceptable(res.body.peers); if (err) { return setImmediate(waterCb, err); From 52b51d34d29d0a56fad8c22b552293bc1901a97f Mon Sep 17 00:00:00 2001 From: Oliver Beddows Date: Tue, 15 Nov 2016 15:22:26 +0100 Subject: [PATCH 181/272] Refactoring code. Adding System.prototype.nethashCompatible. --- modules/system.js | 4 ++++ modules/transport.js | 4 ++-- 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/modules/system.js b/modules/system.js index 8e4e6dcba18..90a569ad7e3 100644 --- a/modules/system.js +++ b/modules/system.js @@ -51,6 +51,10 @@ System.prototype.getNethash = function () { return __private.nethash; }; +System.prototype.networkCompatible = function (nethash) { + return __private.nethash === nethash; +}; + System.prototype.getBroadhash = function (cb) { if (typeof cb !== 'function') { return __private.broadhash; diff --git a/modules/transport.js b/modules/transport.js index 61f4cadd507..4c957bcdd0a 100644 --- a/modules/transport.js +++ b/modules/transport.js @@ -63,7 +63,7 @@ __private.attachApi = function () { return res.status(500).send({success: false, error: report.issues}); } - if (headers.nethash !== modules.system.getNethash()) { + if (!modules.system.networkCompatible(headers.nethash)) { // Remove peer __private.removePeer({peer: req.peer, code: 'ENETHASH', req: req}); @@ -419,7 +419,7 @@ Transport.prototype.getFromPeer = function (peer, options, cb) { return setImmediate(cb, ['Invalid response headers', JSON.stringify(headers), req.method, req.url].join(' ')); } - if (headers.nethash !== modules.system.getNethash()) { + if (!modules.system.networkCompatible(headers.nethash)) { // Remove peer __private.removePeer({peer: peer, code: 'ENETHASH', req: req}); From 6190e43b5e0eb10d4b88ebbe40bbf7385c718cf1 Mon Sep 17 00:00:00 2001 From: Oliver Beddows Date: Tue, 15 Nov 2016 16:33:38 +0100 Subject: [PATCH 182/272] Closes #244. Adding minimal client version parameter. Defined as semver ruling for flexible version number matching. Supports release candidate letter matching e.g. ~0.0.0a. Applies to peer request and response headers. --- config.json | 1 + modules/system.js | 31 ++++++++++++++++++++++++++++++ modules/transport.js | 14 ++++++++++++++ package.json | 1 + test/api/peer.js | 26 +++++++++++++++++++++++++ test/api/peer.signatures.js | 26 +++++++++++++++++++++++++ test/api/peer.transactions.main.js | 26 +++++++++++++++++++++++++ test/config.json | 3 ++- 8 files changed, 127 insertions(+), 1 deletion(-) diff --git a/config.json b/config.json index 91f628d5731..06b5d98f6b0 100644 --- a/config.json +++ b/config.json @@ -2,6 +2,7 @@ "port": 8000, "address": "0.0.0.0", "version": "0.4.0", + "minVersion": "~0.4.0", "fileLogLevel": "info", "logFileName": "logs/lisk.log", "consoleLogLevel": "info", diff --git a/modules/system.js b/modules/system.js index 90a569ad7e3..d719c24eb58 100644 --- a/modules/system.js +++ b/modules/system.js @@ -4,6 +4,7 @@ var async = require('async'); var crypto = require('crypto'); var os = require('os'); var sandboxHelper = require('../helpers/sandbox.js'); +var semver = require('semver'); var sql = require('../sql/system.js'); // Private fields @@ -20,6 +21,15 @@ function System (cb, scope) { __private.height = 1; __private.nethash = library.config.nethash; __private.broadhash = library.config.nethash; + __private.minVersion = library.config.minVersion; + __private.rcRegExp = /[a-z]+$/; + + if (__private.rcRegExp.test(__private.minVersion)) { + this.minVersion = __private.minVersion.replace(__private.rcRegExp, ''); + this.minVersionChar = __private.minVersion.charAt(__private.minVersion.length - 1); + } else { + this.minVersion = __private.minVersion; + } setImmediate(cb, null, self); } @@ -55,6 +65,27 @@ System.prototype.networkCompatible = function (nethash) { return __private.nethash === nethash; }; +System.prototype.getMinVersion = function () { + return __private.minVersion; +}; + +System.prototype.versionCompatible = function (version) { + var versionChar; + + if (__private.rcRegExp.test(version)) { + versionChar = version.charAt(version.length - 1); + version = version.replace(__private.rcRegExp, ''); + } + + var semVerMatch = semver.satisfies(version, this.minVersion); + + if (this.minVersionChar && versionChar) { + return semVerMatch && this.minVersionChar === versionChar; + } else { + return semVerMatch; + } +}; + System.prototype.getBroadhash = function (cb) { if (typeof cb !== 'function') { return __private.broadhash; diff --git a/modules/transport.js b/modules/transport.js index 4c957bcdd0a..b555fb6f9da 100644 --- a/modules/transport.js +++ b/modules/transport.js @@ -70,6 +70,13 @@ __private.attachApi = function () { return res.status(200).send({success: false, message: 'Request is made on the wrong network', expected: modules.system.getNethash(), received: headers.nethash}); } + if (!modules.system.versionCompatible(headers.version)) { + // Remove peer + __private.removePeer({peer: req.peer, code: 'EVERSION:' + headers.version, req: req}); + + return res.status(200).send({success: false, message: 'Request is made from incompatible version', expected: modules.system.getMinVersion(), received: headers.version}); + } + if (req.body && req.body.dappid) { req.peer.dappid = req.body.dappid; } @@ -426,6 +433,13 @@ Transport.prototype.getFromPeer = function (peer, options, cb) { return setImmediate(cb, ['Peer is not on the same network', headers.nethash, req.method, req.url].join(' ')); } + if (!modules.system.versionCompatible(headers.version)) { + // Remove peer + __private.removePeer({peer: peer, code: 'EVERSION:' + headers.version, req: req}); + + return setImmediate(cb, ['Peer is using incompatible version', headers.version, req.method, req.url].join(' ')); + } + if (res.body.height) { peer.height = res.body.height; } diff --git a/package.json b/package.json index 0e69d5d2fbd..1388d99d3b4 100644 --- a/package.json +++ b/package.json @@ -38,6 +38,7 @@ "popsicle": "=8.2.0", "randomstring": "=1.1.5", "rimraf": "=2.5.4", + "semver": "=5.3.0", "socket.io": "=1.4.8", "strftime": "=0.9.2", "valid-url": "=1.0.9", diff --git a/test/api/peer.js b/test/api/peer.js index 58ebcd0d525..0039a0e0968 100644 --- a/test/api/peer.js +++ b/test/api/peer.js @@ -19,6 +19,19 @@ describe('GET /peer/list', function () { }); }); + it('using incompatible version in headers should fail', function (done) { + node.get('/peer/list') + .set('version', '0.1.0a') + .end(function (err, res) { + node.debug('> Response:'.grey, JSON.stringify(res.body)); + node.expect(res.body).to.have.property('success').to.be.not.ok; + node.expect(res.body).to.have.property('message').to.eql('Request is made from incompatible version'); + node.expect(res.body).to.have.property('expected').to.eql('~0.0.0a'); + node.expect(res.body).to.have.property('received').to.eql('0.1.0a'); + done(); + }); + }); + it('using valid headers should be ok', function (done) { node.get('/peer/list') .end(function (err, res) { @@ -51,6 +64,19 @@ describe('GET /peer/height', function () { }); }); + it('using incompatible version in headers should fail', function (done) { + node.get('/peer/height') + .set('version', '0.1.0a') + .end(function (err, res) { + node.debug('> Response:'.grey, JSON.stringify(res.body)); + node.expect(res.body).to.have.property('success').to.be.not.ok; + node.expect(res.body).to.have.property('message').to.eql('Request is made from incompatible version'); + node.expect(res.body).to.have.property('expected').to.eql('~0.0.0a'); + node.expect(res.body).to.have.property('received').to.eql('0.1.0a'); + done(); + }); + }); + it('using valid headers should be ok', function (done) { node.get('/peer/height') .end(function (err, res) { diff --git a/test/api/peer.signatures.js b/test/api/peer.signatures.js index 881c8333e62..0b0a4ea9d69 100644 --- a/test/api/peer.signatures.js +++ b/test/api/peer.signatures.js @@ -15,6 +15,19 @@ describe('GET /peer/signatures', function () { }); }); + it('using incompatible version in headers should fail', function (done) { + node.get('/peer/signatures') + .set('version', '0.1.0a') + .end(function (err, res) { + node.debug('> Response:'.grey, JSON.stringify(res.body)); + node.expect(res.body).to.have.property('success').to.be.not.ok; + node.expect(res.body).to.have.property('message').to.eql('Request is made from incompatible version'); + node.expect(res.body).to.have.property('expected').to.eql('~0.0.0a'); + node.expect(res.body).to.have.property('received').to.eql('0.1.0a'); + done(); + }); + }); + it('using valid headers should be ok', function (done) { node.get('/peer/signatures') .end(function (err, res) { @@ -53,6 +66,19 @@ describe('POST /peer/signatures', function () { }); }); + it('using incompatible version in headers should fail', function (done) { + node.post('/peer/signatures') + .set('version', '0.1.0a') + .end(function (err, res) { + node.debug('> Response:'.grey, JSON.stringify(res.body)); + node.expect(res.body).to.have.property('success').to.be.not.ok; + node.expect(res.body).to.have.property('message').to.eql('Request is made from incompatible version'); + node.expect(res.body).to.have.property('expected').to.eql('~0.0.0a'); + node.expect(res.body).to.have.property('received').to.eql('0.1.0a'); + done(); + }); + }); + it('using invalid signature schema should fail', function (done) { delete validParams.signature.transaction; diff --git a/test/api/peer.transactions.main.js b/test/api/peer.transactions.main.js index cd341ba7752..acd2937bc0c 100644 --- a/test/api/peer.transactions.main.js +++ b/test/api/peer.transactions.main.js @@ -28,6 +28,19 @@ describe('GET /peer/transactions', function () { }); }); + it('using incompatible version in headers should fail', function (done) { + node.get('/peer/transactions') + .set('version', '0.1.0a') + .end(function (err, res) { + node.debug('> Response:'.grey, JSON.stringify(res.body)); + node.expect(res.body).to.have.property('success').to.be.not.ok; + node.expect(res.body).to.have.property('message').to.eql('Request is made from incompatible version'); + node.expect(res.body).to.have.property('expected').to.eql('~0.0.0a'); + node.expect(res.body).to.have.property('received').to.eql('0.1.0a'); + done(); + }); + }); + it('using valid headers should be ok', function (done) { node.get('/peer/transactions') .end(function (err, res) { @@ -51,6 +64,19 @@ describe('POST /peer/transactions', function () { }); }); + it('using incompatible version in headers should fail', function (done) { + node.post('/peer/transactions') + .set('version', '0.1.0a') + .end(function (err, res) { + node.debug('> Response:'.grey, JSON.stringify(res.body)); + node.expect(res.body).to.have.property('success').to.be.not.ok; + node.expect(res.body).to.have.property('message').to.eql('Request is made from incompatible version'); + node.expect(res.body).to.have.property('expected').to.eql('~0.0.0a'); + node.expect(res.body).to.have.property('received').to.eql('0.1.0a'); + done(); + }); + }); + it('using valid headers should be ok', function (done) { var account = node.randomAccount(); var transaction = node.lisk.transaction.createTransaction(account.address, 1, node.gAccount.password); diff --git a/test/config.json b/test/config.json index b7f1ef39a69..ac8e3e72880 100644 --- a/test/config.json +++ b/test/config.json @@ -1,7 +1,8 @@ { "port": 4000, "address": "0.0.0.0", - "version": "0.0.0", + "version": "0.0.0a", + "minVersion": "~0.0.0a", "fileLogLevel": "info", "logFileName": "logs/lisk.log", "consoleLogLevel": "debug", From 1fc0f03b1538b7f3fc1f810483428e9869561d43 Mon Sep 17 00:00:00 2001 From: Oliver Beddows Date: Tue, 15 Nov 2016 16:51:23 +0100 Subject: [PATCH 183/272] Changing expectation. --- test/api/peer.transactions.collision.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/api/peer.transactions.collision.js b/test/api/peer.transactions.collision.js index 6bd3d1e8d62..77591376d68 100644 --- a/test/api/peer.transactions.collision.js +++ b/test/api/peer.transactions.collision.js @@ -38,7 +38,7 @@ describe('POST /peer/transactions', function () { postTransaction(transaction, function (err, res) { node.expect(res.body).to.have.property('success').to.be.not.ok; - node.expect(res.body).to.have.property('message').to.equal('Failed to verify signature'); + node.expect(res.body).to.have.property('message').to.equal('Invalid sender public key: b26dd40ba33e4785e49ddc4f106c0493ed00695817235c778f487aea5866400a expected: ce33db918b059a6e99c402963b42cf51c695068007ef01d8c383bb8a41270263'); done(); }); }); From 560acadaab911d7e950869ae055d27550c9b9e7a Mon Sep 17 00:00:00 2001 From: Oliver Beddows Date: Tue, 15 Nov 2016 20:57:49 +0100 Subject: [PATCH 184/272] Improving peer removal conditions. Only remove if not frozen and not already removed. --- modules/peers.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/modules/peers.js b/modules/peers.js index 8d20e5afd04..bad29335f24 100644 --- a/modules/peers.js +++ b/modules/peers.js @@ -333,7 +333,7 @@ Peers.prototype.remove = function (pip, port) { var frozenPeer = _.find(library.config.peers.list, function (peer) { return peer.ip === pip && peer.port === port; }); - if (!frozenPeer) { + if (!frozenPeer && removed.indexOf(pip) === -1) { removed.push(pip); return __private.sweeper.push('remove', { ip: pip, port: port }); } From 129ad3250732d5db6d8bb07d7f8f76cdcbeac1b4 Mon Sep 17 00:00:00 2001 From: Oliver Beddows Date: Tue, 15 Nov 2016 21:08:40 +0100 Subject: [PATCH 185/272] Debugging frozen / already removed peers. --- modules/peers.js | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/modules/peers.js b/modules/peers.js index bad29335f24..b73df9ad92a 100644 --- a/modules/peers.js +++ b/modules/peers.js @@ -312,8 +312,11 @@ Peers.prototype.state = function (pip, port, state, timeoutSeconds) { var frozenPeer = _.find(library.config.peers, function (peer) { return peer.ip === pip && peer.port === port; }); - if (!frozenPeer) { + if (frozenPeer) { + library.logger.debug('Not changing state of frozen peer', [pip, port].join(':')); + } else { var clock; + if (state === 0) { clock = (timeoutSeconds || 1) * 1000; clock = Date.now() + clock; @@ -333,7 +336,11 @@ Peers.prototype.remove = function (pip, port) { var frozenPeer = _.find(library.config.peers.list, function (peer) { return peer.ip === pip && peer.port === port; }); - if (!frozenPeer && removed.indexOf(pip) === -1) { + if (frozenPeer) { + library.logger.debug('Not removing frozen peer', [pip, port].join(':')); + } else if (removed.indexOf(pip) !== -1) { + library.logger.debug('Peer already removed', [pip, port].join(':')); + } else { removed.push(pip); return __private.sweeper.push('remove', { ip: pip, port: port }); } From 1cbf43dfa3e72c7ab11f9df5697d4f88dafb4822 Mon Sep 17 00:00:00 2001 From: Oliver Beddows Date: Tue, 15 Nov 2016 23:29:43 +0100 Subject: [PATCH 186/272] Closes #91. Validating config.json according to schema. --- app.js | 7 +- config.json | 2 +- helpers/config.js | 30 ++++++ schema/config.js | 238 ++++++++++++++++++++++++++++++++++++++++++++++ test/config.json | 2 +- 5 files changed, 271 insertions(+), 8 deletions(-) create mode 100644 helpers/config.js create mode 100644 schema/config.js diff --git a/app.js b/app.js index a698768c21e..3372d84001c 100644 --- a/app.js +++ b/app.js @@ -34,12 +34,7 @@ program .option('-s, --snapshot ', 'verify snapshot') .parse(process.argv); -var appConfig; -if (program.config) { - appConfig = require(path.resolve(process.cwd(), program.config)); -} else { - appConfig = require('./config.json'); -} +var appConfig = require('./helpers/config.js')(program.config); if (program.port) { appConfig.port = program.port; diff --git a/config.json b/config.json index 06b5d98f6b0..7dfe3524dc3 100644 --- a/config.json +++ b/config.json @@ -12,7 +12,7 @@ "host": "localhost", "port": 5432, "database": "lisk_main", - "user": null, + "user": "", "password": "password", "poolSize": 100, "poolIdleTimeout": 30000, diff --git a/helpers/config.js b/helpers/config.js new file mode 100644 index 00000000000..b2b824171f1 --- /dev/null +++ b/helpers/config.js @@ -0,0 +1,30 @@ +'use strict'; + +var fs = require('fs'); +var path = require('path'); +var z_schema = require('./z_schema.js'); +var configSchema = require('../schema/config.js'); + +function Config (configPath) { + var configData = fs.readFileSync(path.resolve(process.cwd(), (configPath || 'config.json')), 'utf8'); + + if (!configData.length) { + console.log('Failed to read config file'); + process.exit(1); + } else { + configData = JSON.parse(configData); + } + + var validator = new z_schema(); + var valid = validator.validate(configData, configSchema.config); + + if (!valid) { + console.log('Failed to validate config data', validator.getLastErrors()); + process.exit(1); + } else { + return configData; + } +} + +// Exports +module.exports = Config; diff --git a/schema/config.js b/schema/config.js new file mode 100644 index 00000000000..03536d0e6f5 --- /dev/null +++ b/schema/config.js @@ -0,0 +1,238 @@ +'use strict'; + +module.exports = { + config: { + id: 'appCon', + type: 'object', + properties: { + port: { + type: 'integer', + minimum: 1, + maximum: 65535 + }, + address: { + type: 'string', + format: 'ip' + }, + version: { + type: 'string', + format: 'version', + minLength: 5, + maxLength: 12 + }, + minVersion: { + type: 'string' + }, + fileLogLevel: { + type: 'string' + }, + logFileName: { + type: 'string' + }, + consoleLogLevel: { + type: 'string' + }, + trustProxy: { + type: 'boolean' + }, + topAccounts: { + type: 'boolean' + }, + db: { + type: 'object', + properties: { + host: { + type: 'string', + }, + port: { + type: 'integer', + minimum: 1, + maximum: 65535 + }, + database: { + type: 'string' + }, + user: { + type: 'string' + }, + password: { + type: 'string' + }, + poolSize: { + type: 'integer' + }, + poolIdleTimeout: { + type: 'integer' + }, + reapIntervalMillis: { + type: 'integer' + }, + logEvents: { + type: 'array' + } + }, + required: ['host', 'port', 'database', 'user', 'password', 'poolSize', 'poolIdleTimeout', 'reapIntervalMillis', 'logEvents'] + }, + api: { + type: 'object', + properties: { + access: { + type: 'object', + properties: { + whiteList: { + type: 'array' + } + }, + required: ['whiteList'] + }, + options: { + type: 'object', + properties: { + limits: { + type: 'object', + properties: { + max: { + type: 'integer' + }, + delayMs: { + type: 'integer' + }, + delayAfter: { + type: 'integer' + }, + windowMs: { + type: 'integer' + } + }, + required: ['max', 'delayMs', 'delayAfter', 'windowMs'] + } + }, + required: ['limits'] + } + }, + required: ['access', 'options'] + }, + peers: { + type: 'object', + properties: { + list: { + type: 'array' + }, + blackList: { + type: 'array' + }, + options: { + properties: { + limits: { + type: 'object', + properties: { + max: { + type: 'integer' + }, + delayMs: { + type: 'integer' + }, + delayAfter: { + type: 'integer' + }, + windowMs: { + type: 'integer' + } + }, + required: ['max', 'delayMs', 'delayAfter', 'windowMs'] + }, + timeout: { + type: 'integer' + } + }, + required: ['limits', 'timeout'] + } + }, + required: ['list', 'blackList', 'options'] + }, + forging: { + type: 'object', + properties: { + force: { + type: 'boolean' + }, + secret: { + type: 'array' + }, + access: { + type: 'object', + properties: { + whiteList: { + type: 'array' + } + }, + required: ['whiteList'] + } + }, + required: ['force', 'secret', 'access'] + }, + loading: { + type: 'object', + properties: { + verifyOnLoading: { + type: 'boolean' + }, + loadPerIteration: { + type: 'integer', + minimum: 1, + maximum: 5000 + } + }, + required: ['verifyOnLoading', 'loadPerIteration'] + }, + ssl: { + type: 'object', + properties: { + enabled: { + type: 'boolean' + }, + options: { + type: 'object', + properties: { + port: { + type: 'integer' + }, + address: { + type: 'string', + format: 'ip', + }, + key: { + type: 'string' + }, + cert: { + type: 'string' + } + }, + required: ['port', 'address', 'key', 'cert'] + } + }, + required: ['enabled', 'options'] + }, + dapp: { + type: 'object', + properties: { + masterrequired: { + type: 'boolean' + }, + masterpassword: { + type: 'string' + }, + autoexec: { + type: 'array' + } + }, + required: ['masterrequired', 'masterpassword', 'autoexec'] + }, + nethash: { + type: 'string', + format: 'hex' + } + }, + required: ['port', 'address', 'version', 'minVersion', 'fileLogLevel', 'logFileName', 'consoleLogLevel', 'trustProxy', 'topAccounts', 'db', 'api', 'peers', 'forging', 'loading', 'ssl', 'dapp', 'nethash'] + } +}; diff --git a/test/config.json b/test/config.json index ac8e3e72880..ebb585e76e9 100644 --- a/test/config.json +++ b/test/config.json @@ -12,7 +12,7 @@ "host": "localhost", "port": 5432, "database": "lisk_test", - "user": null, + "user": "", "password": "password", "poolSize": 100, "poolIdleTimeout": 30000, From 26a1df4a74fecae70df83f257b8fc7c6204cf657 Mon Sep 17 00:00:00 2001 From: Oliver Beddows Date: Wed, 16 Nov 2016 00:02:32 +0100 Subject: [PATCH 187/272] Removing unused RequestSanitizer. --- .jshintrc | 3 +- app.js | 1 - helpers/request-sanitizer.js | 410 ----------------------------------- 3 files changed, 1 insertion(+), 413 deletions(-) delete mode 100644 helpers/request-sanitizer.js diff --git a/.jshintrc b/.jshintrc index 99ddc133a3e..c51273a7cf4 100644 --- a/.jshintrc +++ b/.jshintrc @@ -8,7 +8,6 @@ "quotmark": "single", "strict" : true, "globals": { - "gc": true, - "RequestSanitizer": true + "gc": true } } diff --git a/app.js b/app.js index 3372d84001c..240f93e7bfd 100644 --- a/app.js +++ b/app.js @@ -226,7 +226,6 @@ d.run(function () { var path = require('path'); var bodyParser = require('body-parser'); var methodOverride = require('method-override'); - var requestSanitizer = require('./helpers/request-sanitizer'); var queryParser = require('express-query-int'); scope.network.app.engine('html', require('ejs').renderFile); diff --git a/helpers/request-sanitizer.js b/helpers/request-sanitizer.js deleted file mode 100644 index dc84ec7a1ba..00000000000 --- a/helpers/request-sanitizer.js +++ /dev/null @@ -1,410 +0,0 @@ -'use strict'; - -var extend = require('extend'); -var inherits = require('util').inherits; -var Validator = require('./validator/validator.js'); - -module.exports = RequestSanitizer; - -function RequestSanitizer(options) { - Validator.call(this, options); -} - -inherits(RequestSanitizer, Validator); - -RequestSanitizer.prototype.rules = {}; -extend(RequestSanitizer, Validator); - -RequestSanitizer.options = extend({ - reporter : SanitizeReporter -}, Validator.options); - -RequestSanitizer.addRule('empty', { - validate : function (accept, value, field) { - if (accept !== false) { return; } - - return !field.isEmpty(); - } -}); - -RequestSanitizer.addRule('string', { - filter : function (accept, value, field) { - if (field.isEmpty() && field.rules.empty) { return null; } - - return String(value||''); - } -}); - -RequestSanitizer.addRule('regexp', { - message : 'value should match template', - validate : function (accept, value) { - if (typeof value !== 'string') { return false; } - - return accept.test(value); - } -}); - -RequestSanitizer.addRule('boolean', { - filter : function (accept, value, field) { - if (field.isEmpty() && field.rules.empty) { return null; } - - switch(String(value).toLowerCase()) { - case 'false': - case 'f': - return false; - default: - return !!value; - } - } -}); - -RequestSanitizer.addRule('int', { - filter : function (accept, value , field) { - if (field.isEmpty() && field.rules.empty) { return null; } - - if (isNaN(value) || parseInt(value) !== value || isNaN(parseInt(value, 10))) { - return 0; - } - - return parseInt(value); - } -}); - -RequestSanitizer.addRule('float', { - filter : function (accept, value , field) { - if (field.isEmpty() && field.rules.empty) { return null; } - - value = parseFloat(value); - - return isNaN(value) ? 0 : value; - } -}); - -RequestSanitizer.addRule('object', { - filter : function (accept, value , field) { - if (field.isEmpty() && field.rules.empty) { return null; } - - return Object.prototype.toString.call(value) === '[object Object]' ? value : {}; - } -}); - -RequestSanitizer.addRule('array', { - filter: function (accept, value, field) { - if (field.isEmpty() && field.rules.empty) { return null; } - - if (typeof value === 'string' && (typeof accept === 'string' || accept instanceof RegExp )) { - return value.length ? value.split(accept) : []; - } else if (Array.isArray(value)) { - return value; - } else { - return []; - } - } -}); - -RequestSanitizer.addRule('arrayOf', { - validate : function (accept, value, field) { - if (field.isEmpty() && field.rules.empty) { return null; } - if (! Array.isArray(value)) { return false; } - - var l = value.length; - var i = -1; - var child; - - while (++i < l) { - field.child(i, value[i], accept, value).validate(); - } - } -}); - -RequestSanitizer.addRule('hex', { - filter : function (accept, value, field) { - if (field.isEmpty() && field.rules.empty) { return null; } - - return value; - }, - validate : function (accept, value, field) { - if (field.isEmpty() && field.rules.empty) { return; } - - return /^([A-Fa-f0-9]{2})*$/.test(String(value||'')); - } -}); - -RequestSanitizer.addRule('buffer', { - filter : function (accept, value) { - if (typeof accept !== 'string') { - accept = 'utf8'; - } - - try { - return new Buffer(value||'', accept); - } catch (err) { - return new Buffer(); - } - } -}); - -RequestSanitizer.addRule('variant', { - filter : function (accept, value, field) { - if (field.isEmpty() && field.rules.empty) { return null; } - - return typeof value === 'undefined' ? '' : value; - } -}); - -RequestSanitizer.addRule('required', { - message : 'value is required', - validate : function (accept, value) { - return (typeof value !== 'undefined') === accept; - } -}); - -RequestSanitizer.addRule('default', { - filter : function (accept, value) { - return (typeof value === 'undefined') ? accept : value; - } -}); - -RequestSanitizer.addRule('properties', { - validate : function (accept, value, field) { - if (! field.isObject()) { return false; } - - Object.getOwnPropertyNames(accept).forEach(function (name) { - var childAccept = accept[name]; - if (typeof childAccept === 'string') { - childAccept = convertStringRule(childAccept); - } - var child = field.child(name, value[name], childAccept, value); - child.validate(function (err, report, output) { - if (err) { throw err; } - - value[name] = output; - }); - }); - } -}); - -RequestSanitizer.addRule('minLength', { - message : 'minimum length is ${accept}.', - validate : function (accept, value) { - return value.length >= accept; - } -}); - -RequestSanitizer.addRule('case', { - message : 'case is ${accept}.', - validate : function (accept, value) { - return typeof value === 'string' && ((accept==='lower' && value===value.toLowerCase())||(accept==='upper' && value===value.toUpperCase())); - }, - filter : function (accept, value, field) { - if (field.isEmpty() && field.rules.empty) { return null; } - - if(accept==='lower') { - return String(value||'').toLowerCase(); - } - else if(accept==='upper') { - return String(value||'').toUpperCase(); - } - } -}); - -RequestSanitizer.addRule('maxLength', { - message : 'maximum length is ${accept}.', - validate : function (accept, value) { - return value.length <= accept; - } -}); - -RequestSanitizer.addRule('maxByteLength', { - message : 'maximum size is ${accept.length} bytes', - accept : function (accept) { - if (typeof accept !== 'object') { - accept = { - encoding : 'utf8', - length : accept - }; - } - return accept; - }, - validate : function (accept, value, field) { - if (field.isEmpty() && field.rules.empty) { return; } - - if (typeof value === 'object' && value !== null) { - value = JSON.stringify(value); - } - - - return Buffer.byteLength(value, 'utf8') <= accept.length; - } -}); - -RequestSanitizer.addRule('minByteLength', { - message : 'minimum size is ${accept.length} bytes', - accept : function (accept) { - if (typeof accept !== 'object') { - accept = { - encoding : 'utf8', - length : accept - }; - } - - return accept; - }, - validate : function (accept, value, field) { - if (field.isEmpty() && field.rules.empty) { return; } - - if (typeof value === 'object' && value !== null) { - value = JSON.stringify(value); - } - return Buffer.byteLength(value, 'utf8') >= accept.length; - } -}); - -/** - * Express middleware factory - * @param {Object} options Validator constructor options - * @returns {Function} Express middleware - */ -RequestSanitizer.express = function (options) { - options = extend({}, RequestSanitizer.options, options); - - - return function (req, res, next) { - req.sanitize = sanitize; - - function sanitize(value, properties, callback) { - var values = {}; - if (typeof value === 'string') { - value = req[value] || {}; - } - - Object.getOwnPropertyNames(properties).forEach(function (name) { - values[name] = value.hasOwnProperty(name) ? value[name] : undefined; - if (typeof properties[name] === 'string') { - properties[name] = convertStringRule(properties[name]); - } - }); - - return (new RequestSanitizer(options)).validate(values, {properties:properties}, callback); - } - - next(); - }; -}; - -// Define filter rules as standalone methods -var rules = RequestSanitizer.prototype.rules; -[ - 'string', - 'boolean', - 'int', - 'float', - 'variant', - 'array', - 'object', - 'hex', - 'buffer' -].forEach(function (name) { - var rule = rules[name]; - if (typeof rule.filter !== 'function') { return; } - if (name in RequestSanitizer) { return; } - - RequestSanitizer[name] = function filter(value, extra) { - var rules = {}; - if (typeof extra === 'object') { - extend(rules, extra); - } else if (typeof extra !== 'undefined') { - rules.empty = extra; - } - - rules[name] = true; - - var report = (new RequestSanitizer(RequestSanitizer.options)).validate(value, rules); - if (! report.isValid) { - var error = new Error(report.issues); - error.name = 'ValidationError'; - error.issues = report.issues; - throw error; - } - - return report.value; - }; -}); - -RequestSanitizer.options.reporter = SanitizeReporter; - -function SanitizeReporter(validator) { - this.validator = validator; -} - -SanitizeReporter.prototype.format = function (message, values) { - return String(message).replace(/\$\{([^}]+)}/g, function (match, id) { - return getByPath(values, id.split('.')) || ''; - }); -}; - -SanitizeReporter.prototype.convert = function (issues) { - var self = this; - - var grouped = issues.reduce(function (result, item) { - var path = item.path.join('.'); - if (path in result === false) { result[path] = []; } - - result[path].push(item); - - return result; - }, {}); - - var result = ''; - - Object.getOwnPropertyNames(grouped).forEach(function (path) { - result += 'Property \'' + path + '\':\n'; - - grouped[path].forEach(function (item) { - var rule = self.validator.getRule(item.rule); - - result += '\t- '; - - if (rule.hasOwnProperty('message')) { - result += self.format(rule.message, item) + '\n'; - } else { - result += 'break rule \'' + item.rule + '\'\n'; - } - }); - }); - - return result; -}; - -function getByPath (target, path) { - var segment; - path = path.slice(); - var i = -1; - var l = path.length - 1; - - while (++i < l) { - segment = path[i]; - if (typeof target[segment] !== 'object') { - return null; - } - - target = target[segment]; - } - - return target[path[l]]; -} - -function convertStringRule (rule) { - var result = {}; - - if (rule.charAt(rule.length-1) === '!') { - result.required = true; - rule = rule.slice(0, -1); - } else if (rule.charAt(rule.length-1) === '?') { - result.empty = true; - rule = rule.slice(0, -1); - } - - result[rule] = true; - return result; -} From 13edfca6395b26516bb3a4650291bc6bdd8a8d79 Mon Sep 17 00:00:00 2001 From: Oliver Beddows Date: Tue, 15 Nov 2016 16:33:38 +0100 Subject: [PATCH 188/272] Closes #244. Adding minimal client version parameter. Defined as semver ruling for flexible version number matching. Supports release candidate letter matching e.g. ~0.0.0a. Applies to peer request and response headers. --- modules/peers.js | 3 +++ 1 file changed, 3 insertions(+) diff --git a/modules/peers.js b/modules/peers.js index b73df9ad92a..58bd1441c00 100644 --- a/modules/peers.js +++ b/modules/peers.js @@ -117,6 +117,9 @@ __private.updatePeersList = function (cb) { err.forEach(function (e) { library.logger.error(['Rejecting invalid peer:', peer.ip, e.path, e.message].join(' ')); }); + } else if (!modules.system.versionCompatible(peer.version)) { + library.logger.error(['Rejecting peer', peer.ip, 'with incompatible version', peer.version].join(' ')); + self.remove(peer.ip, peer.port); } else { self.update(peer); } From 4ce8771e50df2dc004858c71915f1e55b89ccd57 Mon Sep 17 00:00:00 2001 From: Oliver Beddows Date: Wed, 16 Nov 2016 17:39:12 +0100 Subject: [PATCH 189/272] Normalising formatting. --- modules/peers.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/modules/peers.js b/modules/peers.js index 58bd1441c00..f53929b5a1a 100644 --- a/modules/peers.js +++ b/modules/peers.js @@ -115,10 +115,10 @@ __private.updatePeersList = function (cb) { library.schema.validate(peer, schema.updatePeersList.peer, function (err) { if (err) { err.forEach(function (e) { - library.logger.error(['Rejecting invalid peer:', peer.ip, e.path, e.message].join(' ')); + library.logger.error(['Rejecting invalid peer', peer.string, e.path, e.message].join(' ')); }); } else if (!modules.system.versionCompatible(peer.version)) { - library.logger.error(['Rejecting peer', peer.ip, 'with incompatible version', peer.version].join(' ')); + library.logger.error(['Rejecting peer', peer.string, 'with incompatible version', peer.version].join(' ')); self.remove(peer.ip, peer.port); } else { self.update(peer); From 598a292d5faec802517c7c92a159b8e65aaa97a0 Mon Sep 17 00:00:00 2001 From: Oliver Beddows Date: Thu, 17 Nov 2016 00:32:23 +0100 Subject: [PATCH 190/272] Exhausting broadcasts after (n) relays. --- logic/broadcaster.js | 16 ++++++++++++++++ modules/transport.js | 8 ++++---- 2 files changed, 20 insertions(+), 4 deletions(-) diff --git a/logic/broadcaster.js b/logic/broadcaster.js index b60be59a842..5beac8b88d7 100644 --- a/logic/broadcaster.js +++ b/logic/broadcaster.js @@ -17,6 +17,7 @@ function Broadcaster (scope) { self.broadcastLimit = 20; self.releaseLimit = constants.maxTxsPerBlock; self.broadcastInterval = 5000; + self.relayLimit = 5; // Optionally ignore broadhash efficiency if (!library.config.forging.force) { @@ -99,6 +100,21 @@ Broadcaster.prototype.broadcast = function (params, options, cb) { }); }; +Broadcaster.prototype.maxRelays = function (object) { + if (!Number.isInteger(object.relays)) { + object.relays = 1; // First broadcast + } else { + object.relays++; // Next broadcast + } + + if (Math.abs(object.relays) > self.relayLimit) { + library.logger.debug('Broadcast relays exhausted', object); + return true; + } else { + return false; + } +}; + // Private __private.cleanQueue = function (cb) { library.logger.debug('Broadcasts before cleaning: ' + self.queue.length); diff --git a/modules/transport.js b/modules/transport.js index b555fb6f9da..b84db09adcc 100644 --- a/modules/transport.js +++ b/modules/transport.js @@ -482,21 +482,21 @@ Transport.prototype.onBlockchainReady = function () { }; Transport.prototype.onSignature = function (signature, broadcast) { - if (broadcast) { + if (broadcast && !__private.broadcaster.maxRelays(signature)) { __private.broadcaster.enqueue({}, {api: '/signatures', data: {signature: signature}, method: 'POST'}); library.network.io.sockets.emit('signature/change', signature); } }; Transport.prototype.onUnconfirmedTransaction = function (transaction, broadcast) { - if (broadcast) { + if (broadcast && !__private.broadcaster.maxRelays(transaction)) { __private.broadcaster.enqueue({}, {api: '/transactions', data: {transaction: transaction}, method: 'POST'}); library.network.io.sockets.emit('transactions/change', transaction); } }; Transport.prototype.onNewBlock = function (block, broadcast) { - if (broadcast) { + if (broadcast && !__private.broadcaster.maxRelays(block)) { var broadhash = modules.system.getBroadhash(); modules.system.update(function () { @@ -507,7 +507,7 @@ Transport.prototype.onNewBlock = function (block, broadcast) { }; Transport.prototype.onMessage = function (msg, broadcast) { - if (broadcast) { + if (broadcast && !__private.broadcaster.maxRelays(msg)) { __private.broadcaster.broadcast({dappid: msg.dappid}, {api: '/dapp/message', data: msg, method: 'POST'}); } }; From 5fd867464223ada5ac00f4cc78bef98c0a270b50 Mon Sep 17 00:00:00 2001 From: Oliver Beddows Date: Thu, 17 Nov 2016 12:16:00 +0100 Subject: [PATCH 191/272] Assigning as undefined. --- logic/transactionPool.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/logic/transactionPool.js b/logic/transactionPool.js index 7369aabe3b4..fe4c682460f 100644 --- a/logic/transactionPool.js +++ b/logic/transactionPool.js @@ -274,9 +274,9 @@ TransactionPool.prototype.fillPool = function (cb) { return setImmediate(cb); } else { var spare = 0, spareMulti; - var multisignatures = []; + var multisignatures; var multisignaturesLimit = 5; - var transactions = []; + var transactions; spare = (constants.maxTxsPerBlock - unconfirmedCount); spareMulti = (spare >= multisignaturesLimit) ? multisignaturesLimit : 0; From 72085b5cb418dfcd994cbb8ac0be3e98c74755f9 Mon Sep 17 00:00:00 2001 From: Oliver Beddows Date: Thu, 17 Nov 2016 12:22:08 +0100 Subject: [PATCH 192/272] Broadcasting to partition of peers. --- logic/broadcaster.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/logic/broadcaster.js b/logic/broadcaster.js index 5beac8b88d7..9e82434c0d0 100644 --- a/logic/broadcaster.js +++ b/logic/broadcaster.js @@ -79,7 +79,7 @@ Broadcaster.prototype.broadcast = function (params, options, cb) { function getFromPeer (peers, waterCb) { library.logger.debug('Begin broadcast', options); - async.eachLimit(peers, self.broadcastLimit, function (peer, eachLimitCb) { + async.eachLimit(peers.slice(0, self.broadcastLimit), self.broadcastLimit, function (peer, eachLimitCb) { peer = modules.peers.accept(peer); modules.transport.getFromPeer(peer, options, function (err) { From 5f30e9d42a8ddfdcf8cd6d88fb016b339e5b5813 Mon Sep 17 00:00:00 2001 From: Oliver Beddows Date: Thu, 17 Nov 2016 15:22:13 +0100 Subject: [PATCH 193/272] Making broadcast settings user configurable. --- config.json | 7 +++++++ logic/broadcaster.js | 19 ++++++++----------- schema/config.js | 30 ++++++++++++++++++++++++++++++ test/config.json | 7 +++++++ 4 files changed, 52 insertions(+), 11 deletions(-) diff --git a/config.json b/config.json index 7dfe3524dc3..3eadfc28d17 100644 --- a/config.json +++ b/config.json @@ -68,6 +68,13 @@ "timeout": 5000 } }, + "broadcasts": { + "broadcastInterval": 5000, + "broadcastLimit": 20, + "parallelLimit": 20, + "releaseLimit": 5, + "relayLimit": 5 + }, "forging": { "force": false, "secret": [], diff --git a/logic/broadcaster.js b/logic/broadcaster.js index 9e82434c0d0..bb5ac0ed56f 100644 --- a/logic/broadcaster.js +++ b/logic/broadcaster.js @@ -13,11 +13,8 @@ function Broadcaster (scope) { self = this; self.queue = []; - self.peerLimit = constants.maxPeers; - self.broadcastLimit = 20; - self.releaseLimit = constants.maxTxsPerBlock; - self.broadcastInterval = 5000; - self.relayLimit = 5; + self.config = library.config.broadcasts; + self.config.peerLimit = constants.maxPeers; // Optionally ignore broadhash efficiency if (!library.config.forging.force) { @@ -35,7 +32,7 @@ function Broadcaster (scope) { library.logger.log('Broadcaster timer', err); } }); - }, self.broadcastInterval); + }, self.config.broadcastInterval); } // Public methods @@ -44,7 +41,7 @@ Broadcaster.prototype.bind = function (scope) { }; Broadcaster.prototype.getPeers = function (params, cb) { - params.limit = params.limit || self.peerLimit; + params.limit = params.limit || self.config.peerLimit; params.broadhash = params.broadhash || null; modules.peers.list(params, function (err, peers, efficiency) { @@ -65,7 +62,7 @@ Broadcaster.prototype.enqueue = function (params, options) { }; Broadcaster.prototype.broadcast = function (params, options, cb) { - params.limit = params.limit || self.peerLimit; + params.limit = params.limit || self.config.peerLimit; params.broadhash = params.broadhash || null; async.waterfall([ @@ -79,7 +76,7 @@ Broadcaster.prototype.broadcast = function (params, options, cb) { function getFromPeer (peers, waterCb) { library.logger.debug('Begin broadcast', options); - async.eachLimit(peers.slice(0, self.broadcastLimit), self.broadcastLimit, function (peer, eachLimitCb) { + async.eachLimit(peers.slice(0, self.config.broadcastLimit), self.config.parallelLimit, function (peer, eachLimitCb) { peer = modules.peers.accept(peer); modules.transport.getFromPeer(peer, options, function (err) { @@ -107,7 +104,7 @@ Broadcaster.prototype.maxRelays = function (object) { object.relays++; // Next broadcast } - if (Math.abs(object.relays) > self.relayLimit) { + if (Math.abs(object.relays) > self.config.relayLimit) { library.logger.debug('Broadcast relays exhausted', object); return true; } else { @@ -153,7 +150,7 @@ __private.releaseQueue = function (cb) { return self.getPeers({}, waterCb); }, function broadcast (peers, waterCb) { - broadcasts = self.queue.splice(0, self.releaseLimit); + broadcasts = self.queue.splice(0, self.config.releaseLimit); async.eachSeries(broadcasts, function (broadcast, eachSeriesCb) { self.broadcast(extend({peers: peers}, broadcast.params), broadcast.options, eachSeriesCb); diff --git a/schema/config.js b/schema/config.js index 03536d0e6f5..db8517f9aa2 100644 --- a/schema/config.js +++ b/schema/config.js @@ -150,6 +150,36 @@ module.exports = { }, required: ['list', 'blackList', 'options'] }, + broadcasts: { + type: 'object', + properties: { + broadcastInterval: { + type: 'integer', + minimum: 1000, + maximum: 60000 + }, + broadcastLimit: { + type: 'integer', + minimum: 1, + maximum: 100 + }, + parallelLimit: { + type: 'integer', + minimum: 1, + maximum: 100 + }, + releaseLimit: { + type: 'integer', + minimum: 1, + maximum: 25 + }, + relayLimit: { + type: 'integer', + minimum: 1, + maximum: 100 + } + } + }, forging: { type: 'object', properties: { diff --git a/test/config.json b/test/config.json index ebb585e76e9..43cf83a20cf 100644 --- a/test/config.json +++ b/test/config.json @@ -47,6 +47,13 @@ "timeout": 5000 } }, + "broadcasts": { + "broadcastInterval": 5000, + "broadcastLimit": 20, + "parallelLimit": 20, + "releaseLimit": 5, + "relayLimit": 5 + }, "forging": { "force": true, "secret": [ From 0db654b1bd3a2d44cf91eb93c6d4e598f6cf30d5 Mon Sep 17 00:00:00 2001 From: Oliver Beddows Date: Thu, 17 Nov 2016 19:08:21 +0100 Subject: [PATCH 194/272] Cleaning broadcast queue of already confirmed transactions. --- logic/broadcaster.js | 34 ++++++++++++++++++++++------------ 1 file changed, 22 insertions(+), 12 deletions(-) diff --git a/logic/broadcaster.js b/logic/broadcaster.js index bb5ac0ed56f..a154ad197d2 100644 --- a/logic/broadcaster.js +++ b/logic/broadcaster.js @@ -116,25 +116,35 @@ Broadcaster.prototype.maxRelays = function (object) { __private.cleanQueue = function (cb) { library.logger.debug('Broadcasts before cleaning: ' + self.queue.length); - self.queue = self.queue.filter(function (broadcast) { + async.filter(self.queue, function (broadcast, filterCb) { if (!broadcast.options || !broadcast.options.data) { - return false; + return setImmediate(filterCb, null, false); } else if (broadcast.options.data.transaction) { var transaction = broadcast.options.data.transaction; - - if (transaction !== undefined) { - return modules.transactions.transactionInPool(transaction.id); - } else { - return false; - } + return __private.cleanTransaction(transaction, filterCb); } else { - return true; + return setImmediate(filterCb, null, true); } - }); + }, function (err, broadcasts) { + self.queue = broadcasts; - library.logger.debug('Broadcasts after cleaning: ' + self.queue.length); + library.logger.debug('Broadcasts after cleaning: ' + self.queue.length); + return setImmediate(cb); + }); +}; - return setImmediate(cb); +__private.cleanTransaction = function (transaction, cb) { + if (transaction !== undefined) { + if (modules.transactions.transactionInPool(transaction.id)) { + return setImmediate(cb, null, true); + } else { + return library.logic.transaction.checkConfirmed(transaction, function (err) { + return setImmediate(cb, null, !err); + }); + } + } else { + return setImmediate(cb, null, false); + } }; __private.releaseQueue = function (cb) { From 4aa9bcc9f4f17e317285b6e05b5cfb9f2b78ab30 Mon Sep 17 00:00:00 2001 From: Oliver Beddows Date: Thu, 17 Nov 2016 22:05:28 +0100 Subject: [PATCH 195/272] Renaming functions. --- logic/broadcaster.js | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/logic/broadcaster.js b/logic/broadcaster.js index a154ad197d2..6d5f4f4d5d8 100644 --- a/logic/broadcaster.js +++ b/logic/broadcaster.js @@ -113,27 +113,27 @@ Broadcaster.prototype.maxRelays = function (object) { }; // Private -__private.cleanQueue = function (cb) { - library.logger.debug('Broadcasts before cleaning: ' + self.queue.length); +__private.filterQueue = function (cb) { + library.logger.debug('Broadcasts before filtering: ' + self.queue.length); async.filter(self.queue, function (broadcast, filterCb) { if (!broadcast.options || !broadcast.options.data) { return setImmediate(filterCb, null, false); } else if (broadcast.options.data.transaction) { var transaction = broadcast.options.data.transaction; - return __private.cleanTransaction(transaction, filterCb); + return __private.filterTransaction(transaction, filterCb); } else { return setImmediate(filterCb, null, true); } }, function (err, broadcasts) { self.queue = broadcasts; - library.logger.debug('Broadcasts after cleaning: ' + self.queue.length); + library.logger.debug('Broadcasts after filtering: ' + self.queue.length); return setImmediate(cb); }); }; -__private.cleanTransaction = function (transaction, cb) { +__private.filterTransaction = function (transaction, cb) { if (transaction !== undefined) { if (modules.transactions.transactionInPool(transaction.id)) { return setImmediate(cb, null, true); @@ -153,8 +153,8 @@ __private.releaseQueue = function (cb) { library.logger.debug('Releasing enqueued broadcasts'); async.waterfall([ - function cleanQueue (waterCb) { - return __private.cleanQueue(waterCb); + function filterQueue (waterCb) { + return __private.filterQueue(waterCb); }, function getPeers (waterCb) { return self.getPeers({}, waterCb); From 8ad481a116d87f5f6dd470235636b28f5985f26c Mon Sep 17 00:00:00 2001 From: Oliver Beddows Date: Thu, 17 Nov 2016 22:10:43 +0100 Subject: [PATCH 196/272] Decrement relays on exhaustion. --- logic/broadcaster.js | 1 + 1 file changed, 1 insertion(+) diff --git a/logic/broadcaster.js b/logic/broadcaster.js index 6d5f4f4d5d8..498c4f62591 100644 --- a/logic/broadcaster.js +++ b/logic/broadcaster.js @@ -106,6 +106,7 @@ Broadcaster.prototype.maxRelays = function (object) { if (Math.abs(object.relays) > self.config.relayLimit) { library.logger.debug('Broadcast relays exhausted', object); + object.relays--; return true; } else { return false; From 980505588a24e6e1c2dbc6eb15ecd5cec8fc4ca4 Mon Sep 17 00:00:00 2001 From: Oliver Beddows Date: Thu, 17 Nov 2016 23:00:36 +0100 Subject: [PATCH 197/272] Reindexing queue to reduce memory usage. --- logic/transactionPool.js | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/logic/transactionPool.js b/logic/transactionPool.js index fe4c682460f..db12e21e4f2 100644 --- a/logic/transactionPool.js +++ b/logic/transactionPool.js @@ -16,6 +16,7 @@ function TransactionPool (scope) { self.queued = { transactions: [], index: {} }; self.multisignature = { transactions: [], index: {} }; self.poolInterval = 30000; + self.processed = 0; // Transaction pool timer setInterval(function () { @@ -186,9 +187,26 @@ TransactionPool.prototype.receiveTransactions = function (transactions, broadcas }); }; +TransactionPool.prototype.reindexQueues = function () { + ['unconfirmed', 'queued', 'multisignature'].forEach(function (queue) { + self[queue].index = {}; + self[queue].transactions = self[queue].transactions.filter(Boolean); + self[queue].transactions.forEach(function (transaction) { + var index = self[queue].transactions.indexOf(transaction); + self[queue].index[transaction.id] = index; + }); + }); +}; + TransactionPool.prototype.processUnconfirmedTransaction = function (transaction, broadcast, cb) { if (self.transactionInPool(transaction.id)) { return setImmediate(cb, 'Transaction is already processed: ' + transaction.id); + } else { + self.processed++; + if (self.processed > 1000) { + self.reindexQueues(); + self.processed = 1; + } } __private.processVerifyTransaction(transaction, function (err) { From 3705e2218d2c1310d94b9743953d7d5966d32032 Mon Sep 17 00:00:00 2001 From: Oliver Beddows Date: Fri, 18 Nov 2016 15:14:33 +0100 Subject: [PATCH 198/272] Inserting seeds with same version as self. --- modules/peers.js | 1 + 1 file changed, 1 insertion(+) diff --git a/modules/peers.js b/modules/peers.js index f53929b5a1a..e84036c7386 100644 --- a/modules/peers.js +++ b/modules/peers.js @@ -370,6 +370,7 @@ Peers.prototype.onBlockchainReady = function () { self.update({ ip: peer.ip, port: peer.port, + version: modules.system.getVersion(), state: 2, broadhash: modules.system.getBroadhash(), height: 1 From d42d27f25ee9765bb5656f6c6f8564c39624fb6d Mon Sep 17 00:00:00 2001 From: Oliver Beddows Date: Fri, 18 Nov 2016 16:00:22 +0100 Subject: [PATCH 199/272] Do not upsert peer when listed as removed. --- modules/peers.js | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/modules/peers.js b/modules/peers.js index e84036c7386..5eb21046d0e 100644 --- a/modules/peers.js +++ b/modules/peers.js @@ -350,8 +350,10 @@ Peers.prototype.remove = function (pip, port) { }; Peers.prototype.update = function (peer) { - peer.state = 2; - return __private.sweeper.push('upsert', self.accept(peer).object()); + if (removed.indexOf(peer.ip) === -1) { + peer.state = 2; + return __private.sweeper.push('upsert', self.accept(peer).object()); + } }; Peers.prototype.sandboxApi = function (call, args, cb) { From d71c9c674036de58e014b8e7130ceadef8188446 Mon Sep 17 00:00:00 2001 From: Oliver Beddows Date: Fri, 18 Nov 2016 18:50:36 +0100 Subject: [PATCH 200/272] Do not remove peer on ETIMEOUT, only ban. --- modules/transport.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/modules/transport.js b/modules/transport.js index b84db09adcc..bcd0a8a760a 100644 --- a/modules/transport.js +++ b/modules/transport.js @@ -452,7 +452,7 @@ Transport.prototype.getFromPeer = function (peer, options, cb) { request.catch(function (err) { if (peer) { - if (err.code === 'EUNAVAILABLE' || err.code === 'ETIMEOUT') { + if (err.code === 'EUNAVAILABLE') { // Remove peer __private.removePeer({peer: peer, code: err.code, req: req}); } else { From 73c78c0cac71e1387e14aa6ce5a3b3e0cda10cca Mon Sep 17 00:00:00 2001 From: Oliver Beddows Date: Sat, 19 Nov 2016 01:04:54 +0100 Subject: [PATCH 201/272] Returning 500 status code upon incorrect network / version. --- modules/transport.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/modules/transport.js b/modules/transport.js index bcd0a8a760a..62d4ae91bac 100644 --- a/modules/transport.js +++ b/modules/transport.js @@ -67,14 +67,14 @@ __private.attachApi = function () { // Remove peer __private.removePeer({peer: req.peer, code: 'ENETHASH', req: req}); - return res.status(200).send({success: false, message: 'Request is made on the wrong network', expected: modules.system.getNethash(), received: headers.nethash}); + return res.status(500).send({success: false, message: 'Request is made on the wrong network', expected: modules.system.getNethash(), received: headers.nethash}); } if (!modules.system.versionCompatible(headers.version)) { // Remove peer __private.removePeer({peer: req.peer, code: 'EVERSION:' + headers.version, req: req}); - return res.status(200).send({success: false, message: 'Request is made from incompatible version', expected: modules.system.getMinVersion(), received: headers.version}); + return res.status(500).send({success: false, message: 'Request is made from incompatible version', expected: modules.system.getMinVersion(), received: headers.version}); } if (req.body && req.body.dappid) { From f8b77f634017974f4f8dfdb6273278e3fe8fba23 Mon Sep 17 00:00:00 2001 From: Oliver Beddows Date: Sat, 19 Nov 2016 16:50:18 +0100 Subject: [PATCH 202/272] Broadcasting to maxPeers onNewBlock, onNewMessage. --- modules/transport.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/modules/transport.js b/modules/transport.js index 62d4ae91bac..571638dc9af 100644 --- a/modules/transport.js +++ b/modules/transport.js @@ -500,7 +500,7 @@ Transport.prototype.onNewBlock = function (block, broadcast) { var broadhash = modules.system.getBroadhash(); modules.system.update(function () { - __private.broadcaster.broadcast({broadhash: broadhash}, {api: '/blocks', data: {block: block}, method: 'POST'}); + __private.broadcaster.broadcast({limit: constants.maxPeers, broadhash: broadhash}, {api: '/blocks', data: {block: block}, method: 'POST'}); library.network.io.sockets.emit('blocks/change', block); }); } @@ -508,7 +508,7 @@ Transport.prototype.onNewBlock = function (block, broadcast) { Transport.prototype.onMessage = function (msg, broadcast) { if (broadcast && !__private.broadcaster.maxRelays(msg)) { - __private.broadcaster.broadcast({dappid: msg.dappid}, {api: '/dapp/message', data: msg, method: 'POST'}); + __private.broadcaster.broadcast({limit: constants.maxPeers, dappid: msg.dappid}, {api: '/dapp/message', data: msg, method: 'POST'}); } }; From 9001a774d08efc4955ee0e923d805c4035d8f813 Mon Sep 17 00:00:00 2001 From: Oliver Beddows Date: Sun, 20 Nov 2016 11:14:48 +0100 Subject: [PATCH 203/272] Broadcasting first transactions on queue immediately. --- logic/broadcaster.js | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/logic/broadcaster.js b/logic/broadcaster.js index 498c4f62591..49a4b759e46 100644 --- a/logic/broadcaster.js +++ b/logic/broadcaster.js @@ -58,6 +58,13 @@ Broadcaster.prototype.getPeers = function (params, cb) { }; Broadcaster.prototype.enqueue = function (params, options) { + if (self.queue.length <= self.config.releaseLimit) { + options.immediate = true; + self.broadcast(params, options); + } else { + options.immediate = false; + } + return self.queue.push({params: params, options: options}); }; @@ -118,7 +125,7 @@ __private.filterQueue = function (cb) { library.logger.debug('Broadcasts before filtering: ' + self.queue.length); async.filter(self.queue, function (broadcast, filterCb) { - if (!broadcast.options || !broadcast.options.data) { + if (broadcast.options.immediate) { return setImmediate(filterCb, null, false); } else if (broadcast.options.data.transaction) { var transaction = broadcast.options.data.transaction; From 2a93ea14133caecf431c535eace316c51e182e36 Mon Sep 17 00:00:00 2001 From: Oliver Beddows Date: Sun, 20 Nov 2016 12:01:38 +0100 Subject: [PATCH 204/272] Marking onNewBlock, onNewMessage broadcasts as immediate. --- modules/transport.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/modules/transport.js b/modules/transport.js index 571638dc9af..589f692e54a 100644 --- a/modules/transport.js +++ b/modules/transport.js @@ -500,7 +500,7 @@ Transport.prototype.onNewBlock = function (block, broadcast) { var broadhash = modules.system.getBroadhash(); modules.system.update(function () { - __private.broadcaster.broadcast({limit: constants.maxPeers, broadhash: broadhash}, {api: '/blocks', data: {block: block}, method: 'POST'}); + __private.broadcaster.broadcast({limit: constants.maxPeers, broadhash: broadhash}, {api: '/blocks', data: {block: block}, method: 'POST', immediate: true}); library.network.io.sockets.emit('blocks/change', block); }); } @@ -508,7 +508,7 @@ Transport.prototype.onNewBlock = function (block, broadcast) { Transport.prototype.onMessage = function (msg, broadcast) { if (broadcast && !__private.broadcaster.maxRelays(msg)) { - __private.broadcaster.broadcast({limit: constants.maxPeers, dappid: msg.dappid}, {api: '/dapp/message', data: msg, method: 'POST'}); + __private.broadcaster.broadcast({limit: constants.maxPeers, dappid: msg.dappid}, {api: '/dapp/message', data: msg, method: 'POST', immediate: true}); } }; From 81bacfc72c7bd7f2218c59a64a4074997115a6c3 Mon Sep 17 00:00:00 2001 From: Oliver Beddows Date: Sat, 19 Nov 2016 17:05:39 +0100 Subject: [PATCH 205/272] Moving rcRegExp variable from __private. --- modules/system.js | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/modules/system.js b/modules/system.js index d719c24eb58..9207bc8d577 100644 --- a/modules/system.js +++ b/modules/system.js @@ -10,6 +10,8 @@ var sql = require('../sql/system.js'); // Private fields var modules, library, self, __private = {}, shared = {}; +var rcRegExp = /[a-z]+$/; + // Constructor function System (cb, scope) { library = scope; @@ -22,10 +24,9 @@ function System (cb, scope) { __private.nethash = library.config.nethash; __private.broadhash = library.config.nethash; __private.minVersion = library.config.minVersion; - __private.rcRegExp = /[a-z]+$/; - if (__private.rcRegExp.test(__private.minVersion)) { - this.minVersion = __private.minVersion.replace(__private.rcRegExp, ''); + if (rcRegExp.test(__private.minVersion)) { + this.minVersion = __private.minVersion.replace(rcRegExp, ''); this.minVersionChar = __private.minVersion.charAt(__private.minVersion.length - 1); } else { this.minVersion = __private.minVersion; @@ -72,9 +73,9 @@ System.prototype.getMinVersion = function () { System.prototype.versionCompatible = function (version) { var versionChar; - if (__private.rcRegExp.test(version)) { + if (rcRegExp.test(version)) { versionChar = version.charAt(version.length - 1); - version = version.replace(__private.rcRegExp, ''); + version = version.replace(rcRegExp, ''); } var semVerMatch = semver.satisfies(version, this.minVersion); From 805e6cc8d7869527cf440ecb039a4d617799d251 Mon Sep 17 00:00:00 2001 From: Oliver Beddows Date: Sat, 19 Nov 2016 17:12:07 +0100 Subject: [PATCH 206/272] Returning success property for /peer/list. --- modules/transport.js | 3 ++- test/api/peer.js | 1 + 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/modules/transport.js b/modules/transport.js index 589f692e54a..ae8ca58c227 100644 --- a/modules/transport.js +++ b/modules/transport.js @@ -89,7 +89,8 @@ __private.attachApi = function () { router.get('/list', function (req, res) { modules.peers.list({limit: constants.maxPeers}, function (err, peers) { - return res.status(200).json({peers: !err ? peers : []}); + peers = (!err ? peers : []); + return res.status(200).json({success: !err, peers: peers}); }); }); diff --git a/test/api/peer.js b/test/api/peer.js index 0039a0e0968..4e92b9af66e 100644 --- a/test/api/peer.js +++ b/test/api/peer.js @@ -36,6 +36,7 @@ describe('GET /peer/list', function () { node.get('/peer/list') .end(function (err, res) { node.debug('> Response:'.grey, JSON.stringify(res.body)); + node.expect(res.body).to.have.property('success').to.be.ok; node.expect(res.body).to.have.property('peers').that.is.an('array'); res.body.peers.forEach(function (peer) { node.expect(peer).to.have.property('ip').that.is.a('string'); From e260371a7fd0db4ee842bf040baef6e02b303461 Mon Sep 17 00:00:00 2001 From: Oliver Beddows Date: Sat, 19 Nov 2016 17:14:34 +0100 Subject: [PATCH 207/272] Actually ban peer on invalid common block request. --- modules/transport.js | 3 +++ 1 file changed, 3 insertions(+) diff --git a/modules/transport.js b/modules/transport.js index ae8ca58c227..7dfd7a4bb36 100644 --- a/modules/transport.js +++ b/modules/transport.js @@ -112,6 +112,9 @@ __private.attachApi = function () { if (!escapedIds.length) { library.logger.warn('Invalid common block request, ban 60 min', req.peer.string); + // Ban peer for 60 minutes + __private.banPeer({peer: req.peer, code: 'ECOMMON', req: req, clock: 3600}); + return res.json({success: false, error: 'Invalid block id sequence'}); } From 193aacd33294f33247a3edf2d9abe085acb5031e Mon Sep 17 00:00:00 2001 From: Oliver Beddows Date: Sat, 19 Nov 2016 19:31:23 +0100 Subject: [PATCH 208/272] Requiring exact version. When release candidate character supplied. --- modules/system.js | 6 ++---- test/api/peer.js | 4 ++-- test/api/peer.signatures.js | 4 ++-- test/api/peer.transactions.main.js | 4 ++-- test/config.json | 2 +- test/node.js | 4 ++-- 6 files changed, 11 insertions(+), 13 deletions(-) diff --git a/modules/system.js b/modules/system.js index 9207bc8d577..b94247ac51b 100644 --- a/modules/system.js +++ b/modules/system.js @@ -78,12 +78,10 @@ System.prototype.versionCompatible = function (version) { version = version.replace(rcRegExp, ''); } - var semVerMatch = semver.satisfies(version, this.minVersion); - if (this.minVersionChar && versionChar) { - return semVerMatch && this.minVersionChar === versionChar; + return (version + versionChar) === (this.minVersion + this.minVersionChar); } else { - return semVerMatch; + return semver.satisfies(version, this.minVersion); } }; diff --git a/test/api/peer.js b/test/api/peer.js index 4e92b9af66e..1791d29fea0 100644 --- a/test/api/peer.js +++ b/test/api/peer.js @@ -26,7 +26,7 @@ describe('GET /peer/list', function () { node.debug('> Response:'.grey, JSON.stringify(res.body)); node.expect(res.body).to.have.property('success').to.be.not.ok; node.expect(res.body).to.have.property('message').to.eql('Request is made from incompatible version'); - node.expect(res.body).to.have.property('expected').to.eql('~0.0.0a'); + node.expect(res.body).to.have.property('expected').to.eql('0.0.0a'); node.expect(res.body).to.have.property('received').to.eql('0.1.0a'); done(); }); @@ -72,7 +72,7 @@ describe('GET /peer/height', function () { node.debug('> Response:'.grey, JSON.stringify(res.body)); node.expect(res.body).to.have.property('success').to.be.not.ok; node.expect(res.body).to.have.property('message').to.eql('Request is made from incompatible version'); - node.expect(res.body).to.have.property('expected').to.eql('~0.0.0a'); + node.expect(res.body).to.have.property('expected').to.eql('0.0.0a'); node.expect(res.body).to.have.property('received').to.eql('0.1.0a'); done(); }); diff --git a/test/api/peer.signatures.js b/test/api/peer.signatures.js index 0b0a4ea9d69..9124cad1aec 100644 --- a/test/api/peer.signatures.js +++ b/test/api/peer.signatures.js @@ -22,7 +22,7 @@ describe('GET /peer/signatures', function () { node.debug('> Response:'.grey, JSON.stringify(res.body)); node.expect(res.body).to.have.property('success').to.be.not.ok; node.expect(res.body).to.have.property('message').to.eql('Request is made from incompatible version'); - node.expect(res.body).to.have.property('expected').to.eql('~0.0.0a'); + node.expect(res.body).to.have.property('expected').to.eql('0.0.0a'); node.expect(res.body).to.have.property('received').to.eql('0.1.0a'); done(); }); @@ -73,7 +73,7 @@ describe('POST /peer/signatures', function () { node.debug('> Response:'.grey, JSON.stringify(res.body)); node.expect(res.body).to.have.property('success').to.be.not.ok; node.expect(res.body).to.have.property('message').to.eql('Request is made from incompatible version'); - node.expect(res.body).to.have.property('expected').to.eql('~0.0.0a'); + node.expect(res.body).to.have.property('expected').to.eql('0.0.0a'); node.expect(res.body).to.have.property('received').to.eql('0.1.0a'); done(); }); diff --git a/test/api/peer.transactions.main.js b/test/api/peer.transactions.main.js index acd2937bc0c..0c21bb8144d 100644 --- a/test/api/peer.transactions.main.js +++ b/test/api/peer.transactions.main.js @@ -35,7 +35,7 @@ describe('GET /peer/transactions', function () { node.debug('> Response:'.grey, JSON.stringify(res.body)); node.expect(res.body).to.have.property('success').to.be.not.ok; node.expect(res.body).to.have.property('message').to.eql('Request is made from incompatible version'); - node.expect(res.body).to.have.property('expected').to.eql('~0.0.0a'); + node.expect(res.body).to.have.property('expected').to.eql('0.0.0a'); node.expect(res.body).to.have.property('received').to.eql('0.1.0a'); done(); }); @@ -71,7 +71,7 @@ describe('POST /peer/transactions', function () { node.debug('> Response:'.grey, JSON.stringify(res.body)); node.expect(res.body).to.have.property('success').to.be.not.ok; node.expect(res.body).to.have.property('message').to.eql('Request is made from incompatible version'); - node.expect(res.body).to.have.property('expected').to.eql('~0.0.0a'); + node.expect(res.body).to.have.property('expected').to.eql('0.0.0a'); node.expect(res.body).to.have.property('received').to.eql('0.1.0a'); done(); }); diff --git a/test/config.json b/test/config.json index 43cf83a20cf..4e22d2e3d78 100644 --- a/test/config.json +++ b/test/config.json @@ -2,7 +2,7 @@ "port": 4000, "address": "0.0.0.0", "version": "0.0.0a", - "minVersion": "~0.0.0a", + "minVersion": "0.0.0a", "fileLogLevel": "info", "logFileName": "logs/lisk.log", "consoleLogLevel": "debug", diff --git a/test/node.js b/test/node.js index 8a4d4f0e4bc..bd6ef163ce8 100644 --- a/test/node.js +++ b/test/node.js @@ -29,7 +29,7 @@ node.api = node.supertest(node.baseUrl); node.normalizer = 100000000; // Use this to convert LISK amount to normal value node.blockTime = 10000; // Block time in miliseconds node.blockTimePlus = 12000; // Block time + 2 seconds in miliseconds -node.version = '0.0.0'; // Node version +node.version = node.config.version; // Node version // Transaction fees node.fees = { @@ -196,7 +196,7 @@ node.addPeers = function (numOfPeers, cb) { return i < numOfPeers; }, function (next) { os = operatingSystems[node.randomizeSelection(operatingSystems.length)]; - version = node.config.version; + version = node.version; var request = node.popsicle.get({ url: node.baseUrl + '/peer/height', From c0dcffff5f671b10705d8510be57fb777c3f87fa Mon Sep 17 00:00:00 2001 From: Oliver Beddows Date: Sun, 20 Nov 2016 00:57:23 +0100 Subject: [PATCH 209/272] Fixing incorrect callback. --- test/api/peer.transactions.votes.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/api/peer.transactions.votes.js b/test/api/peer.transactions.votes.js index 00c9225615b..5c5a07a18ba 100644 --- a/test/api/peer.transactions.votes.js +++ b/test/api/peer.transactions.votes.js @@ -160,7 +160,7 @@ describe('POST /peer/transactions', function () { var transaction2 = node.lisk.vote.createVote(account.password, ['+' + delegate]); postVote(transaction2, function (err, res) { node.expect(res.body).to.have.property('success').to.be.not.ok; - done(); + seriesCb(); }); }); }, From 1976629cba5a276cbc09ac915abb1da37d455803 Mon Sep 17 00:00:00 2001 From: Oliver Beddows Date: Sun, 20 Nov 2016 02:08:00 +0100 Subject: [PATCH 210/272] Improving vote tests. - Testing behaviour between and after blocks. - Checking votes at before / end of examples. --- test/api/peer.transactions.votes.js | 41 ++++++++++++++++++++++++----- 1 file changed, 35 insertions(+), 6 deletions(-) diff --git a/test/api/peer.transactions.votes.js b/test/api/peer.transactions.votes.js index 5c5a07a18ba..b5b2565933b 100644 --- a/test/api/peer.transactions.votes.js +++ b/test/api/peer.transactions.votes.js @@ -156,14 +156,31 @@ describe('POST /peer/transactions', function () { }); }, function (seriesCb) { - node.onNewBlock(function (err) { - var transaction2 = node.lisk.vote.createVote(account.password, ['+' + delegate]); - postVote(transaction2, function (err, res) { - node.expect(res.body).to.have.property('success').to.be.not.ok; - seriesCb(); - }); + setTimeout(seriesCb, 1000); + }, + function (seriesCb) { + var transaction2 = node.lisk.vote.createVote(account.password, ['+' + delegate]); + postVote(transaction2, function (err, res) { + node.expect(res.body).to.have.property('success').to.be.ok; + seriesCb(); + }); + }, + function (seriesCb) { + return node.onNewBlock(seriesCb); + }, + function (seriesCb) { + var transaction2 = node.lisk.vote.createVote(account.password, ['+' + delegate]); + postVote(transaction2, function (err, res) { + node.expect(res.body).to.have.property('success').to.be.not.ok; + seriesCb(); }); }, + function (seriesCb) { + getVotes(account.address, function (err, res) { + node.expect(res.body).to.have.property('delegates').that.has.lengthOf(1); + seriesCb(err); + }); + } ], function (err) { return done(err); }); @@ -303,6 +320,12 @@ describe('POST /peer/transactions after registering a new delegate', function () it('exceeding maximum of 101 votes should fail', function (done) { async.series([ + function (seriesCb) { + getVotes(account.address, function (err, res) { + node.expect(res.body).to.have.property('delegates').that.has.lengthOf(1); + seriesCb(err); + }); + }, function (seriesCb) { var slicedDelegates = delegates.slice(0, 76); node.expect(slicedDelegates).to.have.lengthOf(76); @@ -332,6 +355,12 @@ describe('POST /peer/transactions after registering a new delegate', function () node.expect(res.body).to.have.property('message').to.equal('Maximum number of 101 votes exceeded (1 too many)'); seriesCb(); }); + }, + function (seriesCb) { + getVotes(account.address, function (err, res) { + node.expect(res.body).to.have.property('delegates').that.has.lengthOf(77); + seriesCb(err); + }); } ], function (err) { return done(err); From e36e98ecd245041bf7b75ec8641328ec52de2ffb Mon Sep 17 00:00:00 2001 From: Oliver Beddows Date: Sat, 19 Nov 2016 16:57:01 +0100 Subject: [PATCH 211/272] Checking confirmed delegates before applying confirmed votes. --- logic/vote.js | 23 ++++++++++++++++------- 1 file changed, 16 insertions(+), 7 deletions(-) diff --git a/logic/vote.js b/logic/vote.js index 9ed2c9e4df7..f72cf0c44e9 100644 --- a/logic/vote.js +++ b/logic/vote.js @@ -95,13 +95,22 @@ Vote.prototype.getBytes = function (trs) { }; Vote.prototype.apply = function (trs, block, sender, cb) { - this.scope.account.merge(sender.address, { - delegates: trs.asset.votes, - blockId: block.id, - round: modules.rounds.calc(block.height) - }, function (err) { - return setImmediate(cb, err); - }); + var parent = this; + + async.series([ + function (seriesCb) { + self.checkConfirmedDelegates(trs, seriesCb); + }, + function (seriesCb) { + parent.scope.account.merge(sender.address, { + delegates: trs.asset.votes, + blockId: block.id, + round: modules.rounds.calc(block.height) + }, function (err) { + return setImmediate(cb, err); + }); + } + ], cb); }; Vote.prototype.undo = function (trs, block, sender, cb) { From 283d046b8ea3e878bbc53bb4dd5015a26375f023 Mon Sep 17 00:00:00 2001 From: Oliver Beddows Date: Sun, 20 Nov 2016 00:46:51 +0100 Subject: [PATCH 212/272] Fixing incorrect math operator in __private.checkDelegates. --- modules/delegates.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/modules/delegates.js b/modules/delegates.js index 6e7e444c7ad..691d71d346e 100644 --- a/modules/delegates.js +++ b/modules/delegates.js @@ -314,7 +314,7 @@ __private.checkDelegates = function (publicKey, votes, state, cb) { if (math === '+') { additions += 1; - } else if (math === '+') { + } else if (math === '-') { removals += 1; } From c33e909dd1c89941be13fca8b1fc9e1585924fc6 Mon Sep 17 00:00:00 2001 From: Oliver Beddows Date: Sun, 20 Nov 2016 00:53:20 +0100 Subject: [PATCH 213/272] Refactoring code. Catching invalid math operator on else branch. --- modules/delegates.js | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/modules/delegates.js b/modules/delegates.js index 691d71d346e..ae56a3a3766 100644 --- a/modules/delegates.js +++ b/modules/delegates.js @@ -308,14 +308,12 @@ __private.checkDelegates = function (publicKey, votes, state, cb) { async.eachSeries(votes, function (action, cb) { var math = action[0]; - if (math !== '+' && math !== '-') { - return setImmediate(cb, 'Invalid math operator'); - } - if (math === '+') { additions += 1; } else if (math === '-') { removals += 1; + } else { + return setImmediate(cb, 'Invalid math operator'); } var publicKey = action.slice(1); From 66b8934d6dc37e8d514f19301b83ec1d9e6c42a8 Mon Sep 17 00:00:00 2001 From: Oliver Beddows Date: Sun, 20 Nov 2016 13:31:56 +0100 Subject: [PATCH 214/272] Refactoring code. Adding Peers.prototype.isRemoved. --- modules/peers.js | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/modules/peers.js b/modules/peers.js index 5eb21046d0e..65075f0421e 100644 --- a/modules/peers.js +++ b/modules/peers.js @@ -335,13 +335,17 @@ Peers.prototype.state = function (pip, port, state, timeoutSeconds) { } }; +Peers.prototype.isRemoved = function (pip) { + return (removed.indexOf(pip) !== -1); +}; + Peers.prototype.remove = function (pip, port) { var frozenPeer = _.find(library.config.peers.list, function (peer) { return peer.ip === pip && peer.port === port; }); if (frozenPeer) { library.logger.debug('Not removing frozen peer', [pip, port].join(':')); - } else if (removed.indexOf(pip) !== -1) { + } else if (self.isRemoved(pip)) { library.logger.debug('Peer already removed', [pip, port].join(':')); } else { removed.push(pip); @@ -350,7 +354,7 @@ Peers.prototype.remove = function (pip, port) { }; Peers.prototype.update = function (peer) { - if (removed.indexOf(peer.ip) === -1) { + if (!self.isRemoved(peer.ip)) { peer.state = 2; return __private.sweeper.push('upsert', self.accept(peer).object()); } From a9343c81d9478ffba60d54fbcc69bf8ba9ab53c4 Mon Sep 17 00:00:00 2001 From: Oliver Beddows Date: Mon, 21 Nov 2016 13:37:11 +0100 Subject: [PATCH 215/272] Disabling forced forging for known nethashes. --- helpers/config.js | 13 +++++++++++++ helpers/constants.js | 6 ++++++ 2 files changed, 19 insertions(+) diff --git a/helpers/config.js b/helpers/config.js index b2b824171f1..4b00ef548ec 100644 --- a/helpers/config.js +++ b/helpers/config.js @@ -4,6 +4,7 @@ var fs = require('fs'); var path = require('path'); var z_schema = require('./z_schema.js'); var configSchema = require('../schema/config.js'); +var constants = require('../helpers/constants.js'); function Config (configPath) { var configData = fs.readFileSync(path.resolve(process.cwd(), (configPath || 'config.json')), 'utf8'); @@ -22,9 +23,21 @@ function Config (configPath) { console.log('Failed to validate config data', validator.getLastErrors()); process.exit(1); } else { + validateForce(configData); return configData; } } +function validateForce (configData) { + if (configData.forging.force) { + var index = constants.nethashes.indexOf(configData.nethash); + + if (index !== -1) { + console.log('Forced forging disabled for nethash', configData.nethash); + configData.forging.force = false; + } + } +} + // Exports module.exports = Config; diff --git a/helpers/constants.js b/helpers/constants.js index f7eb994a698..2c1b25bb010 100644 --- a/helpers/constants.js +++ b/helpers/constants.js @@ -29,6 +29,12 @@ module.exports = { maxTxsPerBlock: 25, maxTxsPerQueue: 10000, minBroadhashEfficiency: 10, + nethashes: [ + // Mainnet + 'ed14889723f24ecc54871d058d98ce91ff2f973192075c0155ba2b7b70ad2511', + // Testnet + 'da3ed6a45429278bac2666961289ca17ad86595d33b31037615d4b8e8f158bba' + ], numberLength: 100000000, requestLength: 104, rewards: { From 7bef7b8108f141b4ef134f9688a91b8344b07899 Mon Sep 17 00:00:00 2001 From: Oliver Beddows Date: Mon, 21 Nov 2016 15:41:48 +0100 Subject: [PATCH 216/272] Denesting code. --- modules/delegates.js | 32 ++++++++++++++++---------------- 1 file changed, 16 insertions(+), 16 deletions(-) diff --git a/modules/delegates.js b/modules/delegates.js index ae56a3a3766..e9b123c7b4a 100644 --- a/modules/delegates.js +++ b/modules/delegates.js @@ -259,25 +259,25 @@ __private.forge = function (cb) { } library.sequence.add(function (cb) { - if (slots.getSlotNumber(currentBlockData.time) === slots.getSlotNumber()) { - modules.blocks.generateBlock(currentBlockData.keypair, currentBlockData.time, function (err) { - modules.blocks.lastReceipt(new Date()); - - library.logger.info([ - 'Forged new block id:', - modules.blocks.getLastBlock().id, - 'height:', modules.blocks.getLastBlock().height, - 'round:', modules.rounds.calc(modules.blocks.getLastBlock().height), - 'slot:', slots.getSlotNumber(currentBlockData.time), - 'reward:' + modules.blocks.getLastBlock().reward - ].join(' ')); - - return setImmediate(cb, err); - }); - } else { + if (slots.getSlotNumber(currentBlockData.time) !== slots.getSlotNumber()) { library.logger.debug('Delegate slot', slots.getSlotNumber()); return setImmediate(cb); } + + modules.blocks.generateBlock(currentBlockData.keypair, currentBlockData.time, function (err) { + modules.blocks.lastReceipt(new Date()); + + library.logger.info([ + 'Forged new block id:', + modules.blocks.getLastBlock().id, + 'height:', modules.blocks.getLastBlock().height, + 'round:', modules.rounds.calc(modules.blocks.getLastBlock().height), + 'slot:', slots.getSlotNumber(currentBlockData.time), + 'reward:' + modules.blocks.getLastBlock().reward + ].join(' ')); + + return setImmediate(cb, err); + }); }, function (err) { if (err) { library.logger.error('Failed generate block within delegate slot', err); From 2c556bf24a09fc7807f83cc162f7cc398ea364a9 Mon Sep 17 00:00:00 2001 From: Oliver Beddows Date: Mon, 21 Nov 2016 15:55:12 +0100 Subject: [PATCH 217/272] Closes #316. Logging warning when delegate slot skipped. - Due to error or null currentBlockData. - Due to poor broadhash efficiency (on slot match). --- modules/delegates.js | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/modules/delegates.js b/modules/delegates.js index e9b123c7b4a..971476ac9a8 100644 --- a/modules/delegates.js +++ b/modules/delegates.js @@ -236,14 +236,6 @@ __private.forge = function (cb) { return setImmediate(cb); } - // Do not forge when broadhash efficiency is below threshold - var efficiency = modules.transport.efficiency(); - - if (efficiency <= constants.minBroadhashEfficiency) { - library.logger.debug(['Poor broadhash efficiency', efficiency, '%'].join(' ')); - return setImmediate(cb); - } - var currentSlot = slots.getSlotNumber(); var lastBlock = modules.blocks.getLastBlock(); @@ -254,7 +246,7 @@ __private.forge = function (cb) { __private.getBlockSlotData(currentSlot, lastBlock.height + 1, function (err, currentBlockData) { if (err || currentBlockData === null) { - library.logger.debug('Skipping delegate slot'); + library.logger.warn('Skipping delegate slot', err); return setImmediate(cb); } @@ -264,6 +256,14 @@ __private.forge = function (cb) { return setImmediate(cb); } + // Do not forge when broadhash efficiency is below threshold + var efficiency = modules.transport.efficiency(); + + if (efficiency <= constants.minBroadhashEfficiency) { + library.logger.warn('Skipping delegate slot', ['Inadequate broadhash efficiency', efficiency, '%'].join(' ')); + return setImmediate(cb); + } + modules.blocks.generateBlock(currentBlockData.keypair, currentBlockData.time, function (err) { modules.blocks.lastReceipt(new Date()); From 3cf656ee47190d6b6882b1745162d7068d037b94 Mon Sep 17 00:00:00 2001 From: Oliver Beddows Date: Mon, 21 Nov 2016 16:19:59 +0100 Subject: [PATCH 218/272] Rounding secondsAgo to 2 decimal places. --- modules/blocks.js | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/modules/blocks.js b/modules/blocks.js index 4aa2ec4db7b..d62ac9e67fc 100644 --- a/modules/blocks.js +++ b/modules/blocks.js @@ -718,7 +718,7 @@ Blocks.prototype.getLastBlock = function () { var lastBlockTime = epoch + __private.lastBlock.timestamp; var currentTime = new Date().getTime() / 1000; - __private.lastBlock.secondsAgo = currentTime - lastBlockTime; + __private.lastBlock.secondsAgo = Math.round((currentTime - lastBlockTime) * 1e2) / 1e2; __private.lastBlock.fresh = (__private.lastBlock.secondsAgo < constants.blockReceiptTimeOut); } @@ -733,6 +733,7 @@ Blocks.prototype.lastReceipt = function (lastReceipt) { if (__private.lastReceipt) { var timeNow = new Date(); __private.lastReceipt.secondsAgo = Math.floor((timeNow.getTime() - __private.lastReceipt.getTime()) / 1000); + __private.lastReceipt.secondsAgo = Math.round(__private.lastReceipt.secondsAgo * 1e2) / 1e2; __private.lastReceipt.stale = (__private.lastReceipt.secondsAgo > constants.blockReceiptTimeOut); } From 4be7e4c182d9572f1057bca5fb8c47e4b0d15502 Mon Sep 17 00:00:00 2001 From: Oliver Beddows Date: Mon, 21 Nov 2016 20:40:02 +0100 Subject: [PATCH 219/272] Updating broadhash efficiency when limit === maxPeers. --- logic/broadcaster.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/logic/broadcaster.js b/logic/broadcaster.js index 49a4b759e46..0e7e39423e0 100644 --- a/logic/broadcaster.js +++ b/logic/broadcaster.js @@ -49,7 +49,7 @@ Broadcaster.prototype.getPeers = function (params, cb) { return setImmediate(cb, err); } - if (self.efficiency !== undefined) { + if (self.efficiency !== undefined && params.limit === constants.maxPeers) { self.efficiency = efficiency; } From 9dbb8c3e0840baf16a22a62fce9d58ebf744a33a Mon Sep 17 00:00:00 2001 From: Oliver Beddows Date: Tue, 22 Nov 2016 11:39:19 +0100 Subject: [PATCH 220/272] Filling pool after forge. --- modules/delegates.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/modules/delegates.js b/modules/delegates.js index 971476ac9a8..7c19c7d9c91 100644 --- a/modules/delegates.js +++ b/modules/delegates.js @@ -554,8 +554,8 @@ Delegates.prototype.onBlockchainReady = function () { } async.series([ - modules.transactions.fillPool, - __private.forge + __private.forge, + modules.transactions.fillPool ], function (err) { return setTimeout(nextForge, 1000); }); From 36c4b3d7b35c3e92aff208ed068b5f67d2cd5b2c Mon Sep 17 00:00:00 2001 From: Oliver Beddows Date: Tue, 22 Nov 2016 11:41:43 +0100 Subject: [PATCH 221/272] Decreasing poolSize from 100 to 95. --- config.json | 2 +- test/config.json | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/config.json b/config.json index 3eadfc28d17..76453a149f0 100644 --- a/config.json +++ b/config.json @@ -14,7 +14,7 @@ "database": "lisk_main", "user": "", "password": "password", - "poolSize": 100, + "poolSize": 95, "poolIdleTimeout": 30000, "reapIntervalMillis": 1000, "logEvents": [ diff --git a/test/config.json b/test/config.json index 4e22d2e3d78..800c3f87f6b 100644 --- a/test/config.json +++ b/test/config.json @@ -14,7 +14,7 @@ "database": "lisk_test", "user": "", "password": "password", - "poolSize": 100, + "poolSize": 95, "poolIdleTimeout": 30000, "reapIntervalMillis": 1000, "logEvents": [ From b7626b9c2fd690dacd070aed5f23ad71d8465da4 Mon Sep 17 00:00:00 2001 From: Oliver Beddows Date: Tue, 22 Nov 2016 14:39:42 +0100 Subject: [PATCH 222/272] Changing operator on efficiency. --- modules/delegates.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/modules/delegates.js b/modules/delegates.js index 7c19c7d9c91..5328e2264fa 100644 --- a/modules/delegates.js +++ b/modules/delegates.js @@ -259,7 +259,7 @@ __private.forge = function (cb) { // Do not forge when broadhash efficiency is below threshold var efficiency = modules.transport.efficiency(); - if (efficiency <= constants.minBroadhashEfficiency) { + if (efficiency < constants.minBroadhashEfficiency) { library.logger.warn('Skipping delegate slot', ['Inadequate broadhash efficiency', efficiency, '%'].join(' ')); return setImmediate(cb); } From 585a1ba0583f28ea1f6f0ded735253556b196334 Mon Sep 17 00:00:00 2001 From: Oliver Beddows Date: Tue, 22 Nov 2016 14:39:54 +0100 Subject: [PATCH 223/272] Changing operator on height. --- modules/loader.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/modules/loader.js b/modules/loader.js index c3694113cff..0f104ea593f 100644 --- a/modules/loader.js +++ b/modules/loader.js @@ -543,7 +543,7 @@ __private.getPeer = function (peer, cb) { }); }, getHeight: function (seriesCb) { - if (peer.height >= modules.blocks.getLastBlock().height) { + if (peer.height > modules.blocks.getLastBlock().height) { return setImmediate(seriesCb); } else { modules.transport.getFromPeer(peer, { From eb6df8918c94d841e375bad34e900c7a8eb12326 Mon Sep 17 00:00:00 2001 From: Oliver Beddows Date: Tue, 22 Nov 2016 15:04:02 +0100 Subject: [PATCH 224/272] Only splice peers when necessary. --- logic/broadcaster.js | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/logic/broadcaster.js b/logic/broadcaster.js index 0e7e39423e0..61e4f678021 100644 --- a/logic/broadcaster.js +++ b/logic/broadcaster.js @@ -83,7 +83,9 @@ Broadcaster.prototype.broadcast = function (params, options, cb) { function getFromPeer (peers, waterCb) { library.logger.debug('Begin broadcast', options); - async.eachLimit(peers.slice(0, self.config.broadcastLimit), self.config.parallelLimit, function (peer, eachLimitCb) { + if (params.limit === self.config.peerLimit) { peers.splice(0, self.config.broadcastLimit); } + + async.eachLimit(peers, self.config.parallelLimit, function (peer, eachLimitCb) { peer = modules.peers.accept(peer); modules.transport.getFromPeer(peer, options, function (err) { From 5014d99b17ed5281425ebb2e3dc9f84f1e106ab3 Mon Sep 17 00:00:00 2001 From: Oliver Beddows Date: Tue, 22 Nov 2016 16:43:54 +0100 Subject: [PATCH 225/272] Increasing compression level. --- app.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app.js b/app.js index 240f93e7bfd..31fdb24f2cb 100644 --- a/app.js +++ b/app.js @@ -163,7 +163,7 @@ d.run(function () { require('./helpers/request-limiter')(app, appConfig); - app.use(compression({ level: 6 })); + app.use(compression({ level: 9 })); app.use(cors()); app.options('*', cors()); From 631b1e1a4a9b2a8dfa9cf435ece7315c7b402472 Mon Sep 17 00:00:00 2001 From: Oliver Beddows Date: Tue, 22 Nov 2016 17:54:03 +0100 Subject: [PATCH 226/272] Beautifying code. --- helpers/constants.js | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/helpers/constants.js b/helpers/constants.js index 2c1b25bb010..3d4fddea9d0 100644 --- a/helpers/constants.js +++ b/helpers/constants.js @@ -7,7 +7,7 @@ module.exports = { blockReceiptTimeOut: 120, // 12 blocks confirmationLength: 77, epochTime: new Date(Date.UTC(2016, 4, 24, 17, 0, 0, 0)), - fees:{ + fees: { send: 10000000, vote: 100000000, secondsignature: 500000000, @@ -17,10 +17,10 @@ module.exports = { }, feeStart: 1, feeStartVolume: 10000 * 100000000, - fixedPoint : Math.pow(10, 8), + fixedPoint: Math.pow(10, 8), maxAddressesLength: 208 * 128, maxAmount: 100000000, - maxConfirmations : 77 * 100, + maxConfirmations: 77 * 100, maxPayloadLength: 1024 * 1024, maxPeers: 100, maxRequests: 10000 * 12, From 657925a8762f5597ed5a773ae61161cfc8e544d2 Mon Sep 17 00:00:00 2001 From: Oliver Beddows Date: Tue, 22 Nov 2016 23:41:34 +0100 Subject: [PATCH 227/272] Additionally filtering signature broadcasts. --- logic/broadcaster.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/logic/broadcaster.js b/logic/broadcaster.js index 61e4f678021..7af30eb81be 100644 --- a/logic/broadcaster.js +++ b/logic/broadcaster.js @@ -129,8 +129,8 @@ __private.filterQueue = function (cb) { async.filter(self.queue, function (broadcast, filterCb) { if (broadcast.options.immediate) { return setImmediate(filterCb, null, false); - } else if (broadcast.options.data.transaction) { - var transaction = broadcast.options.data.transaction; + } else if (broadcast.options.data) { + var transaction = (broadcast.options.data.transaction || broadcast.options.data.signature); return __private.filterTransaction(transaction, filterCb); } else { return setImmediate(filterCb, null, true); From 93cae1e083334a231c216c15ac7cf8b69b8245a8 Mon Sep 17 00:00:00 2001 From: Oliver Beddows Date: Tue, 22 Nov 2016 23:41:13 +0100 Subject: [PATCH 228/272] Improving broadcast efficiency. Squashing queued broadcasts from many to one per route. Accepting collection or object on POST /peer/transactions and POST /peer/signatures. --- logic/broadcaster.js | 61 ++++++++-- modules/transport.js | 184 +++++++++++++++++++++++------ schema/transport.js | 26 ++++ test/api/peer.signatures.js | 6 + test/api/peer.transactions.main.js | 6 + 5 files changed, 236 insertions(+), 47 deletions(-) diff --git a/logic/broadcaster.js b/logic/broadcaster.js index 7af30eb81be..adbc9e724d6 100644 --- a/logic/broadcaster.js +++ b/logic/broadcaster.js @@ -3,6 +3,7 @@ var async = require('async'); var constants = require('../helpers/constants.js'); var extend = require('extend'); +var _ = require('lodash'); // Private fields var modules, library, self, __private = {}; @@ -23,6 +24,19 @@ function Broadcaster (scope) { self.efficiency = undefined; } + // Broadcast routes + self.routes = [{ + path: '/transactions', + collection: 'transactions', + object: 'transaction', + method: 'POST' + }, { + path: '/signatures', + collection: 'signatures', + object: 'signature', + method: 'POST' + }]; + // Broadcaster timer setInterval(function () { async.series([ @@ -157,26 +171,55 @@ __private.filterTransaction = function (transaction, cb) { } }; -__private.releaseQueue = function (cb) { - var broadcasts; +__private.squashQueue = function (broadcasts) { + var grouped = _.groupBy(broadcasts, function (broadcast) { + return broadcast.options.api; + }); + + var squashed = []; + + self.routes.forEach(function (route) { + if (Array.isArray(grouped[route.path])) { + var data = {}; + + data[route.collection] = grouped[route.path].map(function (broadcast) { + return broadcast.options.data[route.object]; + }).filter(Boolean); + + squashed.push({ + options: { api: route.path, data: data, method: route.method }, + immediate: false + }); + } + }); + return squashed; +}; + +__private.releaseQueue = function (cb) { library.logger.debug('Releasing enqueued broadcasts'); async.waterfall([ function filterQueue (waterCb) { return __private.filterQueue(waterCb); }, - function getPeers (waterCb) { - return self.getPeers({}, waterCb); + function squashQueue (waterCb) { + var broadcasts = self.queue.splice(0, self.config.releaseLimit); + return setImmediate(waterCb, null, __private.squashQueue(broadcasts)); }, - function broadcast (peers, waterCb) { - broadcasts = self.queue.splice(0, self.config.releaseLimit); - + function getPeers (broadcasts, waterCb) { + self.getPeers({}, function (err, peers) { + return setImmediate(waterCb, err, broadcasts, peers); + }); + }, + function broadcast (broadcasts, peers, waterCb) { async.eachSeries(broadcasts, function (broadcast, eachSeriesCb) { self.broadcast(extend({peers: peers}, broadcast.params), broadcast.options, eachSeriesCb); - }, waterCb); + }, function (err) { + return setImmediate(waterCb, err, broadcasts); + }); } - ], function (err) { + ], function (err, broadcasts) { if (err) { library.logger.debug('Failed to release broadcast queue', err); } else { diff --git a/modules/transport.js b/modules/transport.js index 7dfd7a4bb36..010d9786e19 100644 --- a/modules/transport.js +++ b/modules/transport.js @@ -172,19 +172,23 @@ __private.attachApi = function () { }); router.post('/signatures', function (req, res) { - library.schema.validate(req.body, schema.signatures, function (err) { - if (err) { - return res.status(200).json({success: false, error: 'Signature validation failed'}); - } - - modules.multisignatures.processSignature(req.body.signature, function (err) { + if (req.body.signatures) { + __private.receiveSignatures(req, function (err, ids) { if (err) { - return res.status(200).json({success: false, error: 'Error processing signature'}); + return res.status(200).json({success: false, message: err}); } else { - return res.status(200).json({success: true}); + return res.status(200).json({success: true, signatureIds: ids}); } }); - }); + } else { + __private.receiveSignature(req.body.signature, req, function (err, id) { + if (err) { + return res.status(200).json({success: false, message: err}); + } else { + return res.status(200).json({success: true, signatureId: id}); + } + }); + } }); router.get('/signatures', function (req, res) { @@ -212,36 +216,23 @@ __private.attachApi = function () { }); router.post('/transactions', function (req, res) { - var transaction = req.body.transaction; - var id = (transaction? transaction.id : 'null'); - - try { - transaction = library.logic.transaction.objectNormalize(transaction); - } catch (e) { - library.logger.debug(['Transaction', id].join(' '), e.toString()); - if (transaction) { library.logger.debug('Transaction', transaction); } - - if (req.peer) { - // Ban peer for 60 minutes - __private.banPeer({peer: req.peer, code: 'ETRANSACTION', req: req, clock: 3600}); - } - - return res.status(200).json({success: false, message: 'Invalid transaction body'}); + if (req.body.transactions) { + __private.receiveTransactions(req, function (err, ids) { + if (err) { + return res.status(200).json({success: false, message: err}); + } else { + return res.status(200).json({success: true, transactionIds: ids}); + } + }); + } else { + __private.receiveTransaction(req.body.transaction, req, function (err, id) { + if (err) { + return res.status(200).json({success: false, message: err}); + } else { + return res.status(200).json({success: true, transactionId: id}); + } + }); } - - library.balancesSequence.add(function (cb) { - library.logger.debug('Received transaction ' + transaction.id + ' from peer ' + req.peer.string); - modules.transactions.receiveTransactions([transaction], true, cb); - }, function (err) { - if (err) { - library.logger.debug(['Transaction', id].join(' '), err.toString()); - if (transaction) { library.logger.debug('Transaction', transaction); } - - res.status(200).json({success: false, message: err.toString()}); - } else { - res.status(200).json({success: true, transactionId: transaction.id}); - } - }); }); router.get('/height', function (req, res) { @@ -337,6 +328,7 @@ __private.attachApi = function () { }); }; +// Private methods __private.hashsum = function (obj) { var buf = new Buffer(JSON.stringify(obj), 'utf8'); var hashdig = crypto.createHash('sha256').update(buf).digest(); @@ -358,6 +350,122 @@ __private.removePeer = function (options) { modules.peers.remove(options.peer.ip, options.peer.port); }; +__private.receiveSignatures = function (req, cb) { + var ids, signatures; + + async.series({ + validateSchema: function (seriesCb) { + library.schema.validate(req.body, schema.signatures, function (err) { + if (err) { + return setImmediate(seriesCb, 'Invalid signatures body'); + } else { + return setImmediate(seriesCb); + } + }); + }, + receiveSignatures: function (seriesCb) { + signatures = req.body.signatures; + ids = signatures.map(function (signature) { return signature.id; }); + + async.eachSeries(signatures, function (signature, eachSeriesCb) { + __private.receiveSignature(signature, req, function (err) { + if (err) { + library.logger.debug(err, signature); + ids.splice(ids.indexOf(signature.id)); + } + + return setImmediate(eachSeriesCb); + }); + }, seriesCb); + } + }, function (err) { + return setImmediate(cb, err, (ids || [])); + }); +}; + +__private.receiveSignature = function (signature, req, cb) { + library.schema.validate({signature: signature}, schema.signature, function (err) { + if (err) { + return setImmediate(cb, 'Signature validation failed'); + } + + library.balancesSequence.add(function (cb) { + modules.multisignatures.processSignature(signature, function (err) { + if (err) { + return setImmediate(cb, 'Error processing signature'); + } else { + return setImmediate(cb); + } + }); + }, cb); + }); +}; + +__private.receiveTransactions = function (req, cb) { + var ids, transactions; + + async.series({ + validateSchema: function (seriesCb) { + library.schema.validate(req.body, schema.transactions, function (err) { + if (err) { + return setImmediate(seriesCb, 'Invalid transactions body'); + } else { + return setImmediate(seriesCb); + } + }); + }, + receiveTransactions: function (seriesCb) { + transactions = req.body.transactions; + ids = transactions.map(function (transaction) { return transaction.id; }); + + async.eachSeries(transactions, function (transaction, eachSeriesCb) { + __private.receiveTransaction(transaction, req, function (err) { + if (err) { + library.logger.debug(err, transaction); + ids.splice(ids.indexOf(transaction.id)); + } + + return setImmediate(eachSeriesCb); + }); + }, seriesCb); + } + }, function (err) { + return setImmediate(cb, err, (ids || [])); + }); +}; + +__private.receiveTransaction = function (transaction, req, cb) { + var id = (transaction ? transaction.id : 'null'); + + try { + transaction = library.logic.transaction.objectNormalize(transaction); + } catch (e) { + library.logger.debug(['Transaction', id].join(' '), e.toString()); + if (transaction) { library.logger.debug('Transaction', transaction); } + + if (req.peer) { + // Ban peer for 60 minutes + __private.banPeer({peer: req.peer, code: 'ETRANSACTION', req: req, clock: 3600}); + } + + return setImmediate(cb, 'Invalid transaction body'); + } + + library.balancesSequence.add(function (cb) { + library.logger.debug('Received transaction ' + transaction.id + ' from peer ' + req.peer.string); + modules.transactions.receiveTransactions([transaction], true, function (err) { + if (err) { + library.logger.debug(['Transaction', id].join(' '), err.toString()); + if (transaction) { library.logger.debug('Transaction', transaction); } + + return setImmediate(cb, err.toString()); + } else { + return setImmediate(cb, null, transaction.id); + } + }); + }, cb); +}; + // Public methods Transport.prototype.headers = function (headers) { if (headers) { diff --git a/schema/transport.js b/schema/transport.js index 655ebed61d9..99001698b42 100644 --- a/schema/transport.js +++ b/schema/transport.js @@ -1,5 +1,7 @@ 'use strict'; +var constants = require('../helpers/constants.js'); + module.exports = { headers: { id: 'transport.headers', @@ -64,9 +66,33 @@ module.exports = { } }, }, + transactions: { + id: 'transport.transactions', + type: 'object', + properties: { + transactions: { + type: 'array', + minItems: 1, + maxItems: 25 + } + }, + required: ['transactions'] + }, signatures: { id: 'transport.signatures', type: 'object', + properties: { + signatures: { + type: 'array', + minItems: 1, + maxItems: 25 + } + }, + required: ['signatures'] + }, + signature: { + id: 'transport.signature', + type: 'object', properties: { signature: { type: 'object', diff --git a/test/api/peer.signatures.js b/test/api/peer.signatures.js index 9124cad1aec..03879afa702 100644 --- a/test/api/peer.signatures.js +++ b/test/api/peer.signatures.js @@ -104,4 +104,10 @@ describe('POST /peer/signatures', function () { }); it('using processable signature should be ok'); + + describe('using multiple signatures', function () { + it('with unprocessable signature should fail'); + + it('with processable signature should be ok'); + }); }); diff --git a/test/api/peer.transactions.main.js b/test/api/peer.transactions.main.js index 0c21bb8144d..8eb8b98067c 100644 --- a/test/api/peer.transactions.main.js +++ b/test/api/peer.transactions.main.js @@ -295,6 +295,12 @@ describe('POST /peer/transactions', function () { }); }); + describe('using multiple transactions', function () { + it('with invalid transaction should fail'); + + it('with valid transaction should be ok'); + }); + describe('when two passphrases collide into the same address', function () { var collision = { From 7420c9c12575f16e9a75d104d9167770b5fbf6d5 Mon Sep 17 00:00:00 2001 From: Oliver Beddows Date: Wed, 23 Nov 2016 00:55:18 +0100 Subject: [PATCH 229/272] Exiting early on empty queue. --- logic/broadcaster.js | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/logic/broadcaster.js b/logic/broadcaster.js index adbc9e724d6..0ebfc2548da 100644 --- a/logic/broadcaster.js +++ b/logic/broadcaster.js @@ -199,6 +199,11 @@ __private.squashQueue = function (broadcasts) { __private.releaseQueue = function (cb) { library.logger.debug('Releasing enqueued broadcasts'); + if (!self.queue.length) { + library.logger.debug('Queue empty'); + return setImmediate(cb); + } + async.waterfall([ function filterQueue (waterCb) { return __private.filterQueue(waterCb); From f4ff657244970eee6588fe7f3e8da834237caa11 Mon Sep 17 00:00:00 2001 From: Oliver Beddows Date: Wed, 23 Nov 2016 01:17:11 +0100 Subject: [PATCH 230/272] Removing immediate broadcasting. --- logic/broadcaster.js | 8 +------- 1 file changed, 1 insertion(+), 7 deletions(-) diff --git a/logic/broadcaster.js b/logic/broadcaster.js index 0ebfc2548da..16d76798390 100644 --- a/logic/broadcaster.js +++ b/logic/broadcaster.js @@ -72,13 +72,7 @@ Broadcaster.prototype.getPeers = function (params, cb) { }; Broadcaster.prototype.enqueue = function (params, options) { - if (self.queue.length <= self.config.releaseLimit) { - options.immediate = true; - self.broadcast(params, options); - } else { - options.immediate = false; - } - + options.immediate = false; return self.queue.push({params: params, options: options}); }; From 13bd1c90f3cdb80f5c5465e357ab800ec1de6265 Mon Sep 17 00:00:00 2001 From: Oliver Beddows Date: Wed, 23 Nov 2016 01:22:44 +0100 Subject: [PATCH 231/272] Expecting message property. --- test/api/peer.signatures.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/test/api/peer.signatures.js b/test/api/peer.signatures.js index 03879afa702..216fa875727 100644 --- a/test/api/peer.signatures.js +++ b/test/api/peer.signatures.js @@ -86,7 +86,7 @@ describe('POST /peer/signatures', function () { .end(function (err, res) { node.debug('> Response:'.grey, JSON.stringify(res.body)); node.expect(res.body).to.have.property('success').to.be.not.ok; - node.expect(res.body).to.have.property('error').to.equal('Signature validation failed'); + node.expect(res.body).to.have.property('message').to.equal('Signature validation failed'); done(); }); }); @@ -98,7 +98,7 @@ describe('POST /peer/signatures', function () { .end(function (err, res) { node.debug('> Response:'.grey, JSON.stringify(res.body)); node.expect(res.body).to.have.property('success').to.be.not.ok; - node.expect(res.body).to.have.property('error').to.equal('Error processing signature'); + node.expect(res.body).to.have.property('message').to.equal('Error processing signature'); done(); }); }); From b8dad253623a7b221b5ddba3e309e280bd1a2596 Mon Sep 17 00:00:00 2001 From: Oliver Beddows Date: Wed, 23 Nov 2016 01:40:11 +0100 Subject: [PATCH 232/272] Converting space to tabs. --- test/api/peer.signatures.js | 196 ++++++++++++++++++------------------ 1 file changed, 98 insertions(+), 98 deletions(-) diff --git a/test/api/peer.signatures.js b/test/api/peer.signatures.js index 216fa875727..fab8a31304b 100644 --- a/test/api/peer.signatures.js +++ b/test/api/peer.signatures.js @@ -4,110 +4,110 @@ var node = require('./../node.js'); describe('GET /peer/signatures', function () { - it('using incorrect nethash in headers should fail', function (done) { - node.get('/peer/signatures') - .set('nethash', 'incorrect') - .end(function (err, res) { - node.debug('> Response:'.grey, JSON.stringify(res.body)); - node.expect(res.body).to.have.property('success').to.be.not.ok; - node.expect(res.body.expected).to.equal(node.config.nethash); - done(); - }); - }); - - it('using incompatible version in headers should fail', function (done) { - node.get('/peer/signatures') - .set('version', '0.1.0a') - .end(function (err, res) { - node.debug('> Response:'.grey, JSON.stringify(res.body)); - node.expect(res.body).to.have.property('success').to.be.not.ok; - node.expect(res.body).to.have.property('message').to.eql('Request is made from incompatible version'); + it('using incorrect nethash in headers should fail', function (done) { + node.get('/peer/signatures') + .set('nethash', 'incorrect') + .end(function (err, res) { + node.debug('> Response:'.grey, JSON.stringify(res.body)); + node.expect(res.body).to.have.property('success').to.be.not.ok; + node.expect(res.body.expected).to.equal(node.config.nethash); + done(); + }); + }); + + it('using incompatible version in headers should fail', function (done) { + node.get('/peer/signatures') + .set('version', '0.1.0a') + .end(function (err, res) { + node.debug('> Response:'.grey, JSON.stringify(res.body)); + node.expect(res.body).to.have.property('success').to.be.not.ok; + node.expect(res.body).to.have.property('message').to.eql('Request is made from incompatible version'); node.expect(res.body).to.have.property('expected').to.eql('0.0.0a'); node.expect(res.body).to.have.property('received').to.eql('0.1.0a'); - done(); - }); - }); - - it('using valid headers should be ok', function (done) { - node.get('/peer/signatures') - .end(function (err, res) { - node.debug('> Response:'.grey, JSON.stringify(res.body)); - node.expect(res.body).to.have.property('success').to.be.ok; - node.expect(res.body).to.have.property('signatures').that.is.an('array'); - done(); - }); - }); + done(); + }); + }); + + it('using valid headers should be ok', function (done) { + node.get('/peer/signatures') + .end(function (err, res) { + node.debug('> Response:'.grey, JSON.stringify(res.body)); + node.expect(res.body).to.have.property('success').to.be.ok; + node.expect(res.body).to.have.property('signatures').that.is.an('array'); + done(); + }); + }); }); describe('POST /peer/signatures', function () { - var validParams; - - var transaction = node.lisk.transaction.createTransaction('1L', 1, node.gAccount.password); - - beforeEach(function (done) { - validParams = { - signature: { - signature: transaction.signature, - transaction: transaction.id - } - }; - done(); - }); - - it('using incorrect nethash in headers should fail', function (done) { - node.post('/peer/signatures') - .set('nethash', 'incorrect') - .end(function (err, res) { - node.debug('> Response:'.grey, JSON.stringify(res.body)); - node.expect(res.body).to.have.property('success').to.be.not.ok; - node.expect(res.body.expected).to.equal(node.config.nethash); - done(); - }); - }); - - it('using incompatible version in headers should fail', function (done) { - node.post('/peer/signatures') - .set('version', '0.1.0a') - .end(function (err, res) { - node.debug('> Response:'.grey, JSON.stringify(res.body)); - node.expect(res.body).to.have.property('success').to.be.not.ok; - node.expect(res.body).to.have.property('message').to.eql('Request is made from incompatible version'); + var validParams; + + var transaction = node.lisk.transaction.createTransaction('1L', 1, node.gAccount.password); + + beforeEach(function (done) { + validParams = { + signature: { + signature: transaction.signature, + transaction: transaction.id + } + }; + done(); + }); + + it('using incorrect nethash in headers should fail', function (done) { + node.post('/peer/signatures') + .set('nethash', 'incorrect') + .end(function (err, res) { + node.debug('> Response:'.grey, JSON.stringify(res.body)); + node.expect(res.body).to.have.property('success').to.be.not.ok; + node.expect(res.body.expected).to.equal(node.config.nethash); + done(); + }); + }); + + it('using incompatible version in headers should fail', function (done) { + node.post('/peer/signatures') + .set('version', '0.1.0a') + .end(function (err, res) { + node.debug('> Response:'.grey, JSON.stringify(res.body)); + node.expect(res.body).to.have.property('success').to.be.not.ok; + node.expect(res.body).to.have.property('message').to.eql('Request is made from incompatible version'); node.expect(res.body).to.have.property('expected').to.eql('0.0.0a'); node.expect(res.body).to.have.property('received').to.eql('0.1.0a'); - done(); - }); - }); - - it('using invalid signature schema should fail', function (done) { - delete validParams.signature.transaction; - - node.post('/peer/signatures', validParams) - .end(function (err, res) { - node.debug('> Response:'.grey, JSON.stringify(res.body)); - node.expect(res.body).to.have.property('success').to.be.not.ok; - node.expect(res.body).to.have.property('message').to.equal('Signature validation failed'); - done(); - }); - }); - - it('using unprocessable signature should fail', function (done) { - validParams.signature.transaction = '1'; - - node.post('/peer/signatures', validParams) - .end(function (err, res) { - node.debug('> Response:'.grey, JSON.stringify(res.body)); - node.expect(res.body).to.have.property('success').to.be.not.ok; - node.expect(res.body).to.have.property('message').to.equal('Error processing signature'); - done(); - }); - }); - - it('using processable signature should be ok'); - - describe('using multiple signatures', function () { - it('with unprocessable signature should fail'); - - it('with processable signature should be ok'); - }); + done(); + }); + }); + + it('using invalid signature schema should fail', function (done) { + delete validParams.signature.transaction; + + node.post('/peer/signatures', validParams) + .end(function (err, res) { + node.debug('> Response:'.grey, JSON.stringify(res.body)); + node.expect(res.body).to.have.property('success').to.be.not.ok; + node.expect(res.body).to.have.property('message').to.equal('Signature validation failed'); + done(); + }); + }); + + it('using unprocessable signature should fail', function (done) { + validParams.signature.transaction = '1'; + + node.post('/peer/signatures', validParams) + .end(function (err, res) { + node.debug('> Response:'.grey, JSON.stringify(res.body)); + node.expect(res.body).to.have.property('success').to.be.not.ok; + node.expect(res.body).to.have.property('message').to.equal('Error processing signature'); + done(); + }); + }); + + it('using processable signature should be ok'); + + describe('using multiple signatures', function () { + it('with unprocessable signature should fail'); + + it('with processable signature should be ok'); + }); }); From 5829d2d637cc4b6ab222d8bff824b7fb03a9621a Mon Sep 17 00:00:00 2001 From: Oliver Beddows Date: Wed, 23 Nov 2016 02:25:23 +0100 Subject: [PATCH 233/272] Receiving each transaction independently. --- modules/loader.js | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/modules/loader.js b/modules/loader.js index 0f104ea593f..620a2018b08 100644 --- a/modules/loader.js +++ b/modules/loader.js @@ -181,8 +181,13 @@ __private.loadTransactions = function (cb) { }); }, function (transactions, waterCb) { - library.balancesSequence.add(function (cb) { - modules.transactions.receiveTransactions(transactions, false, cb); + async.eachSeries(transactions, function (transaction, eachSeriesCb) { + library.balancesSequence.add(function (cb) { + modules.transactions.processUnconfirmedTransaction(transaction, false, cb); + }, function (err) { + library.logger.debug(err); + return setImmediate(eachSeriesCb); + }); }, waterCb); } ], function (err) { From 582d2b3382518335ebb766e2b247e588d5d66818 Mon Sep 17 00:00:00 2001 From: Oliver Beddows Date: Wed, 23 Nov 2016 12:49:20 +0100 Subject: [PATCH 234/272] Do not block update of removed peers. --- modules/peers.js | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/modules/peers.js b/modules/peers.js index 65075f0421e..a44306b16ad 100644 --- a/modules/peers.js +++ b/modules/peers.js @@ -354,10 +354,9 @@ Peers.prototype.remove = function (pip, port) { }; Peers.prototype.update = function (peer) { - if (!self.isRemoved(peer.ip)) { - peer.state = 2; - return __private.sweeper.push('upsert', self.accept(peer).object()); - } + peer.state = 2; + removed.splice(removed.indexOf(peer.ip)); + return __private.sweeper.push('upsert', self.accept(peer).object()); }; Peers.prototype.sandboxApi = function (call, args, cb) { From 6e305df5b1d5a2e38dfcca1ff05a489bab2ac917 Mon Sep 17 00:00:00 2001 From: Oliver Beddows Date: Thu, 24 Nov 2016 17:19:15 +0100 Subject: [PATCH 235/272] Fixing Broadcaster.prototype.maxRelays. - Changing operator on check. - Incrementing after check. --- logic/broadcaster.js | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/logic/broadcaster.js b/logic/broadcaster.js index 16d76798390..ce1b03826e3 100644 --- a/logic/broadcaster.js +++ b/logic/broadcaster.js @@ -116,16 +116,14 @@ Broadcaster.prototype.broadcast = function (params, options, cb) { Broadcaster.prototype.maxRelays = function (object) { if (!Number.isInteger(object.relays)) { - object.relays = 1; // First broadcast - } else { - object.relays++; // Next broadcast + object.relays = 0; // First broadcast } - if (Math.abs(object.relays) > self.config.relayLimit) { + if (Math.abs(object.relays) >= self.config.relayLimit) { library.logger.debug('Broadcast relays exhausted', object); - object.relays--; return true; } else { + object.relays++; // Next broadcast return false; } }; From 5082f73cc49242b93b4f2da7377622e61ad76513 Mon Sep 17 00:00:00 2001 From: Oliver Beddows Date: Thu, 24 Nov 2016 21:53:32 +0100 Subject: [PATCH 236/272] Implementing bundled transaction processing. Dramatically speeding up response times for POST /peer/transactions. --- logic/transactionPool.js | 136 ++++++++++++++++++++++++++++++++------- modules/loader.js | 1 + modules/transport.js | 2 + 3 files changed, 116 insertions(+), 23 deletions(-) diff --git a/logic/transactionPool.js b/logic/transactionPool.js index db12e21e4f2..d5c263e35d7 100644 --- a/logic/transactionPool.js +++ b/logic/transactionPool.js @@ -13,11 +13,25 @@ function TransactionPool (scope) { self = this; self.unconfirmed = { transactions: [], index: {} }; + self.bundled = { transactions: [], index: {} }; self.queued = { transactions: [], index: {} }; self.multisignature = { transactions: [], index: {} }; self.poolInterval = 30000; + self.bundledInterval = library.config.broadcasts.broadcastInterval; + self.bundleLimit = library.config.broadcasts.releaseLimit; self.processed = 0; + // Bundled transaction timer + setInterval(function () { + async.series([ + self.processBundled + ], function (err) { + if (err) { + library.logger.log('Bundled transaction timer', err); + } + }); + }, self.bundledInterval); + // Transaction pool timer setInterval(function () { async.series([ @@ -38,6 +52,7 @@ TransactionPool.prototype.bind = function (scope) { TransactionPool.prototype.transactionInPool = function (id) { return [ self.unconfirmed.index[id], + self.bundled.index[id], self.queued.index[id], self.multisignature.index[id] ].filter(Boolean).length > 0; @@ -48,6 +63,11 @@ TransactionPool.prototype.getUnconfirmedTransaction = function (id) { return self.unconfirmed.transactions[index]; }; +TransactionPool.prototype.getBundledTransaction = function (id) { + var index = self.bundled.index[id]; + return self.bundled.transactions[index]; +}; + TransactionPool.prototype.getQueuedTransaction = function (id) { var index = self.queued.index[id]; return self.queued.transactions[index]; @@ -62,6 +82,10 @@ TransactionPool.prototype.getUnconfirmedTransactionList = function (reverse, lim return __private.getTransactionList(self.unconfirmed.transactions, reverse, limit); }; +TransactionPool.prototype.getBundledTransactionList = function (reverse, limit) { + return __private.getTransactionList(self.bundled.transactions, reverse, limit); +}; + TransactionPool.prototype.getQueuedTransactionList = function (reverse, limit) { return __private.getTransactionList(self.queued.transactions, reverse, limit); }; @@ -129,6 +153,25 @@ TransactionPool.prototype.countUnconfirmed = function () { return Object.keys(self.unconfirmed.index).length; }; +TransactionPool.prototype.addBundledTransaction = function (transaction) { + self.bundled.transactions.push(transaction); + var index = self.bundled.transactions.indexOf(transaction); + self.bundled.index[transaction.id] = index; +}; + +TransactionPool.prototype.removeBundledTransaction = function (id) { + var index = self.bundled.index[id]; + + if (index !== undefined) { + self.bundled.transactions[index] = false; + delete self.bundled.index[id]; + } +}; + +TransactionPool.prototype.countBundled = function () { + return Object.keys(self.bundled.index).length; +}; + TransactionPool.prototype.addQueuedTransaction = function (transaction) { if (self.queued.index[transaction.id] === undefined) { if (!transaction.receivedAt) { @@ -188,7 +231,7 @@ TransactionPool.prototype.receiveTransactions = function (transactions, broadcas }; TransactionPool.prototype.reindexQueues = function () { - ['unconfirmed', 'queued', 'multisignature'].forEach(function (queue) { + ['bundled', 'queued', 'multisignature', 'unconfirmed'].forEach(function (queue) { self[queue].index = {}; self[queue].transactions = self[queue].transactions.filter(Boolean); self[queue].transactions.forEach(function (transaction) { @@ -198,6 +241,36 @@ TransactionPool.prototype.reindexQueues = function () { }); }; +TransactionPool.prototype.processBundled = function (cb) { + var bundled = self.getBundledTransactionList(true, self.bundleLimit); + + async.eachSeries(bundled, function (transaction, eachSeriesCb) { + if (!transaction) { + return setImmediate(eachSeriesCb); + } + + self.removeBundledTransaction(transaction.id); + delete transaction.bundled; + + __private.processVerifyTransaction(transaction, true, function (err, sender) { + if (err) { + library.logger.error('Failed to process / verify bundled transaction: ' + transaction.id, err); + self.removeUnconfirmedTransaction(transaction); + return setImmediate(eachSeriesCb); + } else { + self.queueTransaction(transaction, function (err) { + if (err) { + library.logger.error('Failed to queue bundled transaction: ' + transaction.id, err); + } + return setImmediate(eachSeriesCb); + }); + } + }); + }, function (err) { + return setImmediate(cb, err); + }); +}; + TransactionPool.prototype.processUnconfirmedTransaction = function (transaction, broadcast, cb) { if (self.transactionInPool(transaction.id)) { return setImmediate(cb, 'Transaction is already processed: ' + transaction.id); @@ -209,30 +282,43 @@ TransactionPool.prototype.processUnconfirmedTransaction = function (transaction, } } - __private.processVerifyTransaction(transaction, function (err) { - if (err) { - return setImmediate(cb, err); + if (transaction.bundled) { + return self.queueTransaction(transaction, cb); + } + + __private.processVerifyTransaction(transaction, broadcast, function (err) { + if (!err) { + return self.queueTransaction(transaction, cb); } else { - delete transaction.receivedAt; + return setImmediate(cb, err); + } + }); +}; - if (transaction.type === transactionTypes.MULTI || Array.isArray(transaction.signatures)) { - if (self.countMultisignature() >= constants.maxTxsPerQueue) { - return setImmediate(cb, 'Transaction pool is full'); - } else { - self.addMultisignatureTransaction(transaction); - } - } else { - if (self.countQueued() >= constants.maxTxsPerQueue) { - return setImmediate(cb, 'Transaction pool is full'); - } else { - self.addQueuedTransaction(transaction); - } - } +TransactionPool.prototype.queueTransaction = function (transaction, cb) { + delete transaction.receivedAt; - library.bus.message('unconfirmedTransaction', transaction, broadcast); - return setImmediate(cb); + if (transaction.bundled) { + if (self.countBundled() >= constants.maxTxsPerQueue) { + return setImmediate(cb, 'Transaction pool is full'); + } else { + self.addBundledTransaction(transaction); } - }); + } else if (transaction.type === transactionTypes.MULTI || Array.isArray(transaction.signatures)) { + if (self.countMultisignature() >= constants.maxTxsPerQueue) { + return setImmediate(cb, 'Transaction pool is full'); + } else { + self.addMultisignatureTransaction(transaction); + } + } else { + if (self.countQueued() >= constants.maxTxsPerQueue) { + return setImmediate(cb, 'Transaction pool is full'); + } else { + self.addQueuedTransaction(transaction); + } + } + + return setImmediate(cb); }; TransactionPool.prototype.applyUnconfirmedList = function (cb) { @@ -332,7 +418,7 @@ __private.getTransactionList = function (transactions, reverse, limit) { return a; }; -__private.processVerifyTransaction = function (transaction, cb) { +__private.processVerifyTransaction = function (transaction, broadcast, cb) { if (!transaction) { return setImmediate(cb, 'Missing transaction'); } @@ -379,6 +465,10 @@ __private.processVerifyTransaction = function (transaction, cb) { }); } ], function (err, sender) { + if (!err) { + library.bus.message('unconfirmedTransaction', transaction, broadcast); + } + return setImmediate(cb, err, sender); }); }; @@ -391,7 +481,7 @@ __private.applyUnconfirmedList = function (transactions, cb) { if (!transaction) { return setImmediate(eachSeriesCb); } - __private.processVerifyTransaction(transaction, function (err, sender) { + __private.processVerifyTransaction(transaction, false, function (err, sender) { if (err) { library.logger.error('Failed to process / verify unconfirmed transaction: ' + transaction.id, err); self.removeUnconfirmedTransaction(transaction.id); diff --git a/modules/loader.js b/modules/loader.js index 620a2018b08..287658d3ac8 100644 --- a/modules/loader.js +++ b/modules/loader.js @@ -183,6 +183,7 @@ __private.loadTransactions = function (cb) { function (transactions, waterCb) { async.eachSeries(transactions, function (transaction, eachSeriesCb) { library.balancesSequence.add(function (cb) { + transaction.bundled = true; modules.transactions.processUnconfirmedTransaction(transaction, false, cb); }, function (err) { library.logger.debug(err); diff --git a/modules/transport.js b/modules/transport.js index 010d9786e19..a2c7b76aef7 100644 --- a/modules/transport.js +++ b/modules/transport.js @@ -419,6 +419,8 @@ __private.receiveTransactions = function (req, cb) { ids = transactions.map(function (transaction) { return transaction.id; }); async.eachSeries(transactions, function (transaction, eachSeriesCb) { + transaction.bundled = true; + __private.receiveTransaction(transaction, req, function (err) { if (err) { library.logger.debug(err, transaction); From a788533175f520efe5caa5548465cb720040f401 Mon Sep 17 00:00:00 2001 From: Oliver Beddows Date: Thu, 24 Nov 2016 23:22:39 +0100 Subject: [PATCH 237/272] Describing POST /peer/transactions. Sending 1000 bundled transfers to random addresses. --- test/api/peer.transactions.stress.js | 58 +++++++++++++++++++++++++++- 1 file changed, 56 insertions(+), 2 deletions(-) diff --git a/test/api/peer.transactions.stress.js b/test/api/peer.transactions.stress.js index df3893eecd1..a1298a3e84f 100644 --- a/test/api/peer.transactions.stress.js +++ b/test/api/peer.transactions.stress.js @@ -8,9 +8,63 @@ function postTransaction (transaction, done) { }, done); } +function postTransactions (transactions, done) { + node.post('/peer/transactions', { + transactions: transactions + }, done); +} + describe('POST /peer/transactions', function () { - describe('sending 1000 transfers to random addresses', function () { + describe('sending 1000 bundled transfers to random addresses', function () { + + var transactions = []; + var maximum = 1000; + var count = 1; + + before(function (done) { + node.async.doUntil(function (next) { + var bundled = []; + + for (var i = 0; i < node.config.broadcasts.releaseLimit; i++) { + var transaction = node.lisk.transaction.createTransaction( + node.randomAccount().address, + node.randomNumber(100000000, 1000000000), + node.gAccount.password + ); + + transactions.push(transaction); + bundled.push(transaction); + count++; + } + + postTransactions(bundled, function (err, res) { + node.expect(res.body).to.have.property('success').to.be.ok; + node.expect(res.body).to.have.property('transactionIds').that.is.an('array').with.lengthOf(node.config.broadcasts.releaseLimit); + next(); + }); + }, function () { + return (count >= maximum); + }, function (err) { + done(err); + }); + }); + + it('should confirm all transactions', function (done) { + var blocksToWait = maximum / node.constants.maxTxsPerBlock + 1; + node.waitForBlocks(blocksToWait, function (err) { + node.async.eachSeries(transactions, function (transaction, eachSeriesCb) { + node.get('/api/transactions/get?id=' + transaction.id, function (err, res) { + node.expect(res.body).to.have.property('success').to.be.ok; + node.expect(res.body).to.have.property('transaction').that.is.an('object'); + return setImmediate(eachSeriesCb); + }); + }, done); + }); + }).timeout(500000); + }); + + describe('sending 1000 single transfers to random addresses', function () { var transactions = []; var maximum = 1000; @@ -32,7 +86,7 @@ describe('POST /peer/transactions', function () { next(); }); }, function () { - return (count === maximum); + return (count >= maximum); }, function (err) { done(err); }); From b357a2cadf22a3efaa1cbd9c33a81a3e8f0bd1da Mon Sep 17 00:00:00 2001 From: Oliver Beddows Date: Thu, 24 Nov 2016 23:48:42 +0100 Subject: [PATCH 238/272] Increasing release limit to 25. --- config.json | 2 +- test/config.json | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/config.json b/config.json index 76453a149f0..681c7267819 100644 --- a/config.json +++ b/config.json @@ -72,7 +72,7 @@ "broadcastInterval": 5000, "broadcastLimit": 20, "parallelLimit": 20, - "releaseLimit": 5, + "releaseLimit": 25, "relayLimit": 5 }, "forging": { diff --git a/test/config.json b/test/config.json index 800c3f87f6b..9e483dc1448 100644 --- a/test/config.json +++ b/test/config.json @@ -51,7 +51,7 @@ "broadcastInterval": 5000, "broadcastLimit": 20, "parallelLimit": 20, - "releaseLimit": 5, + "releaseLimit": 25, "relayLimit": 5 }, "forging": { From a87001345a020ff9e15802cfd8a80c797b153d5f Mon Sep 17 00:00:00 2001 From: Oliver Beddows Date: Fri, 25 Nov 2016 00:39:24 +0100 Subject: [PATCH 239/272] Adding missing condition. --- modules/loader.js | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/modules/loader.js b/modules/loader.js index 287658d3ac8..5709d590f54 100644 --- a/modules/loader.js +++ b/modules/loader.js @@ -186,7 +186,9 @@ __private.loadTransactions = function (cb) { transaction.bundled = true; modules.transactions.processUnconfirmedTransaction(transaction, false, cb); }, function (err) { - library.logger.debug(err); + if (err) { + library.logger.debug(err); + } return setImmediate(eachSeriesCb); }); }, waterCb); From 3931b8c28eb0bc5c055a24ea5d6dd66615cad74e Mon Sep 17 00:00:00 2001 From: Oliver Beddows Date: Fri, 25 Nov 2016 15:18:00 +0100 Subject: [PATCH 240/272] Fixing call to undefined getTime. --- logic/transactionPool.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/logic/transactionPool.js b/logic/transactionPool.js index d5c263e35d7..3808b5d12af 100644 --- a/logic/transactionPool.js +++ b/logic/transactionPool.js @@ -518,7 +518,7 @@ __private.expireTransactions = function (transactions, parentIds, cb) { var timeNow = new Date(); var timeOut = __private.transactionTimeOut(transaction); - var seconds = Math.floor((timeNow.getTime() - transaction.receivedAt.getTime()) / 1000); + var seconds = Math.floor((timeNow.getTime() - new Date(transaction.receivedAt).getTime()) / 1000); if (seconds > timeOut) { ids.push(transaction.id); From be4d1b3707f1cfd75e9c87faec0a5ce5a206005c Mon Sep 17 00:00:00 2001 From: Oliver Beddows Date: Fri, 25 Nov 2016 16:07:07 +0100 Subject: [PATCH 241/272] Renaming legacy to fallback. --- modules/peers.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/modules/peers.js b/modules/peers.js index a44306b16ad..7f83fbebdec 100644 --- a/modules/peers.js +++ b/modules/peers.js @@ -254,7 +254,7 @@ Peers.prototype.acceptable = function (peers) { Peers.prototype.list = function (options, cb) { options.limit = options.limit || constants.maxPeers; options.broadhash = options.broadhash || modules.system.getBroadhash(); - options.attempts = ['matched broadhash', 'unmatched broadhash', 'legacy']; + options.attempts = ['matched broadhash', 'unmatched broadhash', 'fallback']; options.attempt = 0; options.matched = 0; From 0ccc2be7f396d7b31b3312cd8403957a5ef6691f Mon Sep 17 00:00:00 2001 From: Oliver Beddows Date: Fri, 25 Nov 2016 20:19:37 +0100 Subject: [PATCH 242/272] Refactoring code. Accessing Transactions.prototype.processUnconfirmedTransaction directly. --- modules/transport.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/modules/transport.js b/modules/transport.js index a2c7b76aef7..4b0ee50b2c7 100644 --- a/modules/transport.js +++ b/modules/transport.js @@ -455,7 +455,7 @@ __private.receiveTransaction = function (transaction, req, cb) { library.balancesSequence.add(function (cb) { library.logger.debug('Received transaction ' + transaction.id + ' from peer ' + req.peer.string); - modules.transactions.receiveTransactions([transaction], true, function (err) { + modules.transactions.processUnconfirmedTransaction(transaction, true, function (err) { if (err) { library.logger.debug(['Transaction', id].join(' '), err.toString()); if (transaction) { library.logger.debug('Transaction', transaction); } From eb3aa38f32e5fb3266bb9e9a6c60046017c886ab Mon Sep 17 00:00:00 2001 From: Oliver Beddows Date: Fri, 25 Nov 2016 20:53:57 +0100 Subject: [PATCH 243/272] Beautifying code. --- logic/transactionPool.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/logic/transactionPool.js b/logic/transactionPool.js index 3808b5d12af..005a501dcb5 100644 --- a/logic/transactionPool.js +++ b/logic/transactionPool.js @@ -218,7 +218,7 @@ TransactionPool.prototype.removeMultisignatureTransaction = function (id) { } }; -TransactionPool.prototype.countMultisignature= function () { +TransactionPool.prototype.countMultisignature = function () { return Object.keys(self.multisignature.index).length; }; From 0d1de04d41ad31d03ca821ce4f28e6ec8d6383c0 Mon Sep 17 00:00:00 2001 From: Oliver Beddows Date: Sat, 26 Nov 2016 00:32:00 +0100 Subject: [PATCH 244/272] Decreasing relay limit to 2. --- config.json | 2 +- test/config.json | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/config.json b/config.json index 681c7267819..359614bf95d 100644 --- a/config.json +++ b/config.json @@ -73,7 +73,7 @@ "broadcastLimit": 20, "parallelLimit": 20, "releaseLimit": 25, - "relayLimit": 5 + "relayLimit": 2 }, "forging": { "force": false, diff --git a/test/config.json b/test/config.json index 9e483dc1448..bf3503796da 100644 --- a/test/config.json +++ b/test/config.json @@ -52,7 +52,7 @@ "broadcastLimit": 20, "parallelLimit": 20, "releaseLimit": 25, - "relayLimit": 5 + "relayLimit": 2 }, "forging": { "force": true, From 98fd0c7b7ac452f6d15d23085109f814d1ea7e4f Mon Sep 17 00:00:00 2001 From: Oliver Beddows Date: Sat, 26 Nov 2016 19:38:49 +0100 Subject: [PATCH 245/272] Closes #307. Implementing auto recovery from forks. - Deleting last block on failed chain comparision and poor broadhash efficiency. - Deleting losing block in case of received fork 1 and 5. --- modules/blocks.js | 58 +++++++++++++++++++++++++++++++++++++++++--- modules/delegates.js | 6 ++--- modules/transport.js | 8 ++++++ 3 files changed, 64 insertions(+), 8 deletions(-) diff --git a/modules/blocks.js b/modules/blocks.js index d62ac9e67fc..9a0fca4ba97 100644 --- a/modules/blocks.js +++ b/modules/blocks.js @@ -700,6 +700,18 @@ __private.deleteBlock = function (blockId, cb) { }); }; +__private.recoverChain = function (cb) { + library.logger.warn('Chain comparison failed, starting recovery'); + self.deleteLastBlock(function (err, newLastBlock) { + if (err) { + library.logger.error('Recovery failed'); + } else { + library.logger.info('Recovery complete, new last block', newLastBlock.id); + } + return setImmediate(cb, err); + }); +}; + // Public methods Blocks.prototype.count = function (cb) { library.db.query(sql.countByRowId).then(function (rows) { @@ -741,6 +753,8 @@ Blocks.prototype.lastReceipt = function (lastReceipt) { }; Blocks.prototype.getCommonBlock = function (peer, height, cb) { + var comparisionFailed = false; + async.waterfall([ function (waterCb) { __private.getIdSequence(height, function (err, res) { @@ -757,6 +771,7 @@ Blocks.prototype.getCommonBlock = function (peer, height, cb) { if (err || res.body.error) { return setImmediate(waterCb, err || res.body.error.toString()); } else if (!res.body.common) { + comparisionFailed = true; return setImmediate(waterCb, ['Chain comparison failed with peer:', peer.string, 'using ids:', ids].join(' ')); } else { return setImmediate(waterCb, null, res); @@ -779,6 +794,7 @@ Blocks.prototype.getCommonBlock = function (peer, height, cb) { height: res.body.common.height }).then(function (rows) { if (!rows.length || !rows[0].count) { + comparisionFailed = true; return setImmediate(waterCb, ['Chain comparison failed with peer:', peer.string, 'using block:', JSON.stringify(res.body.common)].join(' ')); } else { return setImmediate(waterCb, null, res.body.common); @@ -789,7 +805,11 @@ Blocks.prototype.getCommonBlock = function (peer, height, cb) { }); } ], function (err, res) { - return setImmediate(cb, err, res); + if (comparisionFailed && modules.transport.poorEfficiency()) { + return __private.recoverChain(cb); + } else { + return setImmediate(cb, err, res); + } }); }; @@ -961,6 +981,22 @@ Blocks.prototype.loadBlocksOffset = function (limit, offset, verify, cb) { }, cb); }; +Blocks.prototype.deleteLastBlock = function (cb) { + library.logger.warn('Deleting last block', __private.lastBlock); + if (__private.lastBlock.height !== 1) { + __private.popLastBlock(__private.lastBlock, function (err, newLastBlock) { + if (err) { + library.logger.error('Error deleting last block', __private.lastBlock); + } + + __private.lastBlock = newLastBlock; + return setImmediate(cb, err, newLastBlock); + }); + } else { + return setImmediate(cb); + } +}; + Blocks.prototype.loadLastBlock = function (cb) { library.dbSequence.add(function (cb) { library.db.query(sql.loadLastBlock).then(function (rows) { @@ -1265,13 +1301,27 @@ Blocks.prototype.onReceiveBlock = function (block) { self.lastReceipt(new Date()); self.processBlock(block, true, cb, true); } else if (block.previousBlock !== __private.lastBlock.id && __private.lastBlock.height + 1 === block.height) { - // Fork: Same height but different previous block id. + // Fork: Consecutive height but different previous block id. modules.delegates.fork(block, 1); - return setImmediate(cb, 'Fork'); + + if (block.previousBlock < __private.lastBlock.id) { + library.logger.info('Last block loses'); + return self.deleteLastBlock(cb); + } else { + library.logger.info('Newly received block wins'); + return setImmediate(cb); + } } else if (block.previousBlock === __private.lastBlock.previousBlock && block.height === __private.lastBlock.height && block.id !== __private.lastBlock.id) { // Fork: Same height and previous block id, but different block id. modules.delegates.fork(block, 5); - return setImmediate(cb, 'Fork'); + + if (block.id < __private.lastBlock.id) { + library.logger.info('Last block loses'); + return self.deleteLastBlock(cb); + } else { + library.logger.info('Newly received block wins'); + return setImmediate(cb); + } } else { return setImmediate(cb); } diff --git a/modules/delegates.js b/modules/delegates.js index 5328e2264fa..9eb35d6e913 100644 --- a/modules/delegates.js +++ b/modules/delegates.js @@ -257,10 +257,8 @@ __private.forge = function (cb) { } // Do not forge when broadhash efficiency is below threshold - var efficiency = modules.transport.efficiency(); - - if (efficiency < constants.minBroadhashEfficiency) { - library.logger.warn('Skipping delegate slot', ['Inadequate broadhash efficiency', efficiency, '%'].join(' ')); + if (modules.transport.poorEfficiency()) { + library.logger.warn('Skipping delegate slot', ['Inadequate broadhash efficiency', modules.transport.efficiency(), '%'].join(' ')); return setImmediate(cb); } diff --git a/modules/transport.js b/modules/transport.js index 4b0ee50b2c7..76aee4b3579 100644 --- a/modules/transport.js +++ b/modules/transport.js @@ -481,6 +481,14 @@ Transport.prototype.efficiency = function () { return __private.broadcaster.efficiency; }; +Transport.prototype.poorEfficiency = function () { + if (__private.broadcaster.efficiency === undefined) { + return false; + } else { + return (__private.broadcaster.efficiency < constants.minBroadhashEfficiency); + } +}; + Transport.prototype.getFromRandomPeer = function (config, options, cb) { if (typeof options === 'function') { cb = options; From 9ee85e2961e94caa0e2a6b996370cc070c715014 Mon Sep 17 00:00:00 2001 From: Oliver Beddows Date: Sat, 26 Nov 2016 19:39:15 +0100 Subject: [PATCH 246/272] Reversing if / else. --- logic/broadcaster.js | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/logic/broadcaster.js b/logic/broadcaster.js index ce1b03826e3..a51db7f1ac7 100644 --- a/logic/broadcaster.js +++ b/logic/broadcaster.js @@ -18,10 +18,10 @@ function Broadcaster (scope) { self.config.peerLimit = constants.maxPeers; // Optionally ignore broadhash efficiency - if (!library.config.forging.force) { - self.efficiency = 100; - } else { + if (library.config.forging.force) { self.efficiency = undefined; + } else { + self.efficiency = 100; } // Broadcast routes From 25fda9441b0bded7bc5545c191dbbe9f95fd8305 Mon Sep 17 00:00:00 2001 From: Oliver Beddows Date: Sat, 26 Nov 2016 21:17:19 +0100 Subject: [PATCH 247/272] Logging broadhash efficiency update more clearly. --- logic/broadcaster.js | 1 + modules/peers.js | 1 - 2 files changed, 1 insertion(+), 1 deletion(-) diff --git a/logic/broadcaster.js b/logic/broadcaster.js index a51db7f1ac7..d8d778cce2a 100644 --- a/logic/broadcaster.js +++ b/logic/broadcaster.js @@ -64,6 +64,7 @@ Broadcaster.prototype.getPeers = function (params, cb) { } if (self.efficiency !== undefined && params.limit === constants.maxPeers) { + library.logger.info(['Broadhash efficiency updated to', efficiency, '%'].join(' ')); self.efficiency = efficiency; } diff --git a/modules/peers.js b/modules/peers.js index 7f83fbebdec..45faa2e39a3 100644 --- a/modules/peers.js +++ b/modules/peers.js @@ -305,7 +305,6 @@ Peers.prototype.list = function (options, cb) { var efficiency = Math.round(options.matched / peers.length * 100 * 1e2) / 1e2; efficiency = isNaN(efficiency) ? 0 : efficiency; - library.logger.debug(['Listing efficiency', efficiency, '%'].join(' ')); library.logger.debug(['Listing', peers.length, 'total peers'].join(' ')); return setImmediate(cb, err, peers, efficiency); }); From bb8b87a147bbb5e52555110675c9a10c77f5819f Mon Sep 17 00:00:00 2001 From: Oliver Beddows Date: Sat, 26 Nov 2016 21:22:05 +0100 Subject: [PATCH 248/272] Updating broadhash efficiency during sync. --- modules/loader.js | 4 ++++ modules/transport.js | 4 ++++ 2 files changed, 8 insertions(+) diff --git a/modules/loader.js b/modules/loader.js index 5709d590f54..c2625ad84ec 100644 --- a/modules/loader.js +++ b/modules/loader.js @@ -463,6 +463,10 @@ __private.sync = function (cb) { library.logger.debug('Undoing unconfirmed transactions before sync'); return modules.transactions.undoUnconfirmedList(seriesCb); }, + getPeers: function (seriesCb) { + library.logger.debug('Getting peers to establish broadhash efficiency'); + return modules.transport.getPeers({limit: constants.maxPeers}, seriesCb); + }, loadBlocksFromNetwork: function (seriesCb) { return __private.loadBlocksFromNetwork(seriesCb); }, diff --git a/modules/transport.js b/modules/transport.js index 76aee4b3579..51c5d8f4890 100644 --- a/modules/transport.js +++ b/modules/transport.js @@ -489,6 +489,10 @@ Transport.prototype.poorEfficiency = function () { } }; +Transport.prototype.getPeers = function (params, cb) { + return __private.broadcaster.getPeers(params, cb); +}; + Transport.prototype.getFromRandomPeer = function (config, options, cb) { if (typeof options === 'function') { cb = options; From 9b7b4ce7b14a3357ce479ed502fb42aa57727b10 Mon Sep 17 00:00:00 2001 From: Oliver Beddows Date: Sat, 26 Nov 2016 21:27:38 +0100 Subject: [PATCH 249/272] Maintaining original limit. --- logic/broadcaster.js | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/logic/broadcaster.js b/logic/broadcaster.js index d8d778cce2a..2ccd79876dd 100644 --- a/logic/broadcaster.js +++ b/logic/broadcaster.js @@ -58,12 +58,14 @@ Broadcaster.prototype.getPeers = function (params, cb) { params.limit = params.limit || self.config.peerLimit; params.broadhash = params.broadhash || null; + var originalLimit = params.limit; + modules.peers.list(params, function (err, peers, efficiency) { if (err) { return setImmediate(cb, err); } - if (self.efficiency !== undefined && params.limit === constants.maxPeers) { + if (self.efficiency !== undefined && originalLimit === constants.maxPeers) { library.logger.info(['Broadhash efficiency updated to', efficiency, '%'].join(' ')); self.efficiency = efficiency; } From b09aa880d8add15ad8a84710f2a3f0ab41379178 Mon Sep 17 00:00:00 2001 From: Oliver Beddows Date: Sat, 26 Nov 2016 23:01:57 +0100 Subject: [PATCH 250/272] Updating broadhash efficiency before delegate slot. --- modules/delegates.js | 47 +++++++++++++++++++++++++++----------------- 1 file changed, 29 insertions(+), 18 deletions(-) diff --git a/modules/delegates.js b/modules/delegates.js index 9eb35d6e913..f44a46345c3 100644 --- a/modules/delegates.js +++ b/modules/delegates.js @@ -250,19 +250,35 @@ __private.forge = function (cb) { return setImmediate(cb); } - library.sequence.add(function (cb) { - if (slots.getSlotNumber(currentBlockData.time) !== slots.getSlotNumber()) { - library.logger.debug('Delegate slot', slots.getSlotNumber()); - return setImmediate(cb); - } - - // Do not forge when broadhash efficiency is below threshold - if (modules.transport.poorEfficiency()) { - library.logger.warn('Skipping delegate slot', ['Inadequate broadhash efficiency', modules.transport.efficiency(), '%'].join(' ')); - return setImmediate(cb); - } + if (slots.getSlotNumber(currentBlockData.time) !== slots.getSlotNumber()) { + library.logger.debug('Delegate slot', slots.getSlotNumber()); + return setImmediate(cb); + } - modules.blocks.generateBlock(currentBlockData.keypair, currentBlockData.time, function (err) { + library.sequence.add(function (cb) { + async.series({ + getPeers: function (seriesCb) { + return modules.transport.getPeers({limit: constants.maxPeers}, seriesCb); + }, + checkBroadhash: function (seriesCb) { + if (modules.transport.poorEfficiency()) { + return setImmediate(seriesCb, ['Inadequate broadhash efficiency', modules.transport.consensus(), '%'].join(' ')); + } else { + return setImmediate(seriesCb); + } + } + }, function (err) { + if (err) { + library.logger.warn(err); + return setImmediate(cb, err); + } else { + return modules.blocks.generateBlock(currentBlockData.keypair, currentBlockData.time, cb); + } + }); + }, function (err) { + if (err) { + library.logger.error('Failed generate block within delegate slot', err); + } else { modules.blocks.lastReceipt(new Date()); library.logger.info([ @@ -273,13 +289,8 @@ __private.forge = function (cb) { 'slot:', slots.getSlotNumber(currentBlockData.time), 'reward:' + modules.blocks.getLastBlock().reward ].join(' ')); - - return setImmediate(cb, err); - }); - }, function (err) { - if (err) { - library.logger.error('Failed generate block within delegate slot', err); } + return setImmediate(cb); }); }); From ff6b66545f985689adc45877b5038de41624208a Mon Sep 17 00:00:00 2001 From: Oliver Beddows Date: Mon, 21 Nov 2016 20:52:38 +0100 Subject: [PATCH 251/272] Renaming broadhash efficiency to consensus. --- helpers/constants.js | 2 +- logic/broadcaster.js | 14 +++++++------- modules/blocks.js | 2 +- modules/delegates.js | 4 ++-- modules/loader.js | 2 +- modules/peers.js | 6 +++--- modules/transport.js | 10 +++++----- 7 files changed, 20 insertions(+), 20 deletions(-) diff --git a/helpers/constants.js b/helpers/constants.js index 3d4fddea9d0..3b4fb5ed827 100644 --- a/helpers/constants.js +++ b/helpers/constants.js @@ -28,7 +28,7 @@ module.exports = { maxSignaturesLength: 196 * 256, maxTxsPerBlock: 25, maxTxsPerQueue: 10000, - minBroadhashEfficiency: 10, + minBroadhashConsensus: 10, nethashes: [ // Mainnet 'ed14889723f24ecc54871d058d98ce91ff2f973192075c0155ba2b7b70ad2511', diff --git a/logic/broadcaster.js b/logic/broadcaster.js index 2ccd79876dd..c086b9d5bf9 100644 --- a/logic/broadcaster.js +++ b/logic/broadcaster.js @@ -17,11 +17,11 @@ function Broadcaster (scope) { self.config = library.config.broadcasts; self.config.peerLimit = constants.maxPeers; - // Optionally ignore broadhash efficiency + // Optionally ignore broadhash consensus if (library.config.forging.force) { - self.efficiency = undefined; + self.consensus = undefined; } else { - self.efficiency = 100; + self.consensus = 100; } // Broadcast routes @@ -60,14 +60,14 @@ Broadcaster.prototype.getPeers = function (params, cb) { var originalLimit = params.limit; - modules.peers.list(params, function (err, peers, efficiency) { + modules.peers.list(params, function (err, peers, consensus) { if (err) { return setImmediate(cb, err); } - if (self.efficiency !== undefined && originalLimit === constants.maxPeers) { - library.logger.info(['Broadhash efficiency updated to', efficiency, '%'].join(' ')); - self.efficiency = efficiency; + if (self.consensus !== undefined && originalLimit === constants.maxPeers) { + library.logger.info(['Broadhash consensus updated to', consensus, '%'].join(' ')); + self.consensus = consensus; } return setImmediate(cb, null, peers); diff --git a/modules/blocks.js b/modules/blocks.js index 9a0fca4ba97..ea8aefd9379 100644 --- a/modules/blocks.js +++ b/modules/blocks.js @@ -805,7 +805,7 @@ Blocks.prototype.getCommonBlock = function (peer, height, cb) { }); } ], function (err, res) { - if (comparisionFailed && modules.transport.poorEfficiency()) { + if (comparisionFailed && modules.transport.poorConsensus()) { return __private.recoverChain(cb); } else { return setImmediate(cb, err, res); diff --git a/modules/delegates.js b/modules/delegates.js index f44a46345c3..58dd5b86003 100644 --- a/modules/delegates.js +++ b/modules/delegates.js @@ -261,8 +261,8 @@ __private.forge = function (cb) { return modules.transport.getPeers({limit: constants.maxPeers}, seriesCb); }, checkBroadhash: function (seriesCb) { - if (modules.transport.poorEfficiency()) { - return setImmediate(seriesCb, ['Inadequate broadhash efficiency', modules.transport.consensus(), '%'].join(' ')); + if (modules.transport.poorConsensus()) { + return setImmediate(seriesCb, ['Inadequate broadhash consensus', modules.transport.consensus(), '%'].join(' ')); } else { return setImmediate(seriesCb); } diff --git a/modules/loader.js b/modules/loader.js index c2625ad84ec..ba056ae2076 100644 --- a/modules/loader.js +++ b/modules/loader.js @@ -464,7 +464,7 @@ __private.sync = function (cb) { return modules.transactions.undoUnconfirmedList(seriesCb); }, getPeers: function (seriesCb) { - library.logger.debug('Getting peers to establish broadhash efficiency'); + library.logger.debug('Getting peers to establish broadhash consensus'); return modules.transport.getPeers({limit: constants.maxPeers}, seriesCb); }, loadBlocksFromNetwork: function (seriesCb) { diff --git a/modules/peers.js b/modules/peers.js index 45faa2e39a3..c5a852b89ff 100644 --- a/modules/peers.js +++ b/modules/peers.js @@ -302,11 +302,11 @@ Peers.prototype.list = function (options, cb) { } } ], function (err, peers) { - var efficiency = Math.round(options.matched / peers.length * 100 * 1e2) / 1e2; - efficiency = isNaN(efficiency) ? 0 : efficiency; + var consensus = Math.round(options.matched / peers.length * 100 * 1e2) / 1e2; + consensus = isNaN(consensus) ? 0 : consensus; library.logger.debug(['Listing', peers.length, 'total peers'].join(' ')); - return setImmediate(cb, err, peers, efficiency); + return setImmediate(cb, err, peers, consensus); }); }; diff --git a/modules/transport.js b/modules/transport.js index 51c5d8f4890..7919a57126f 100644 --- a/modules/transport.js +++ b/modules/transport.js @@ -477,15 +477,15 @@ Transport.prototype.headers = function (headers) { return __private.headers; }; -Transport.prototype.efficiency = function () { - return __private.broadcaster.efficiency; +Transport.prototype.consensus = function () { + return __private.broadcaster.consensus; }; -Transport.prototype.poorEfficiency = function () { - if (__private.broadcaster.efficiency === undefined) { +Transport.prototype.poorConsensus = function () { + if (__private.broadcaster.consensus === undefined) { return false; } else { - return (__private.broadcaster.efficiency < constants.minBroadhashEfficiency); + return (__private.broadcaster.consensus < constants.minBroadhashConsensus); } }; From a923ff956ffb481f5ba377aee2df4707e25f726d Mon Sep 17 00:00:00 2001 From: Oliver Beddows Date: Sun, 27 Nov 2016 14:47:48 +0100 Subject: [PATCH 252/272] Adding missing preposition. --- modules/delegates.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/modules/delegates.js b/modules/delegates.js index 58dd5b86003..c37681864ea 100644 --- a/modules/delegates.js +++ b/modules/delegates.js @@ -277,7 +277,7 @@ __private.forge = function (cb) { }); }, function (err) { if (err) { - library.logger.error('Failed generate block within delegate slot', err); + library.logger.error('Failed to generate block within delegate slot', err); } else { modules.blocks.lastReceipt(new Date()); From f0223b33c43386f7b797c10f0219625aadbd71a4 Mon Sep 17 00:00:00 2001 From: 4miners Date: Mon, 28 Nov 2016 04:27:15 +0100 Subject: [PATCH 253/272] Allow "+" char in os version check It's used in some kernels, for example: "3.18.11-v7+" --- helpers/z_schema.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/helpers/z_schema.js b/helpers/z_schema.js index 978089f6225..2b3a409dc8d 100644 --- a/helpers/z_schema.js +++ b/helpers/z_schema.js @@ -106,7 +106,7 @@ z_schema.registerFormat('os', function (str) { return true; } - return /^[a-z0-9-_.]+$/ig.test(str); + return /^[a-z0-9-_.+]+$/ig.test(str); }); z_schema.registerFormat('version', function (str) { From 64ccdc7dc16f0af1ea246ec0c53b7873a1080df7 Mon Sep 17 00:00:00 2001 From: Oliver Beddows Date: Sun, 27 Nov 2016 17:42:53 +0100 Subject: [PATCH 254/272] Closes #307. Fixing broken backwards round tick. - Refactoring code. Adding __private.sumRound function. - Fixing property typo in Rounds.prototype.backwardTick. - Fixing property typo in Rounds.prototype.tick. - Fixing broken round changes on backwards tick. - Fixing broken missed blocks on backwards tick. - Fixing broken Rounds.prototype.directionSwap. - Summing round from blocks on forwards direction. - Do not flush round on direction swap. Adding missing direction swaps. - Before/after Blocks.prototype.deleteLastBlock. - Before/after Blocks.prototype.deleteBlocksBefore. Marking undone blocks in mem_accounts. - Excluding marked accounts from orphaned mem_accounts check. --- logic/round.js | 19 +++++++++++--- modules/blocks.js | 65 ++++++++++++++++++++++++++++++++--------------- modules/rounds.js | 58 +++++++++++++++++++++++++++--------------- sql/loader.js | 2 +- sql/rounds.js | 10 +++++++- 5 files changed, 107 insertions(+), 47 deletions(-) diff --git a/logic/round.js b/logic/round.js index 0bf37fcd99a..cb06dc5106e 100644 --- a/logic/round.js +++ b/logic/round.js @@ -27,7 +27,7 @@ Round.prototype.updateMissedBlocks = function () { return this.t; } - return this.t.none(sql.updateMissedBlocks, [this.scope.outsiders]); + return this.t.none(sql.updateMissedBlocks(this.scope.backwards), [this.scope.outsiders]); }; Round.prototype.getVotes = function () { @@ -53,6 +53,14 @@ Round.prototype.updateVotes = function () { }); }; +Round.prototype.markBlockId = function () { + if (this.scope.backwards) { + return this.t.none(sql.updateBlockId, { oldId: this.scope.block.id, newId: '0' }); + } else { + return this.t; + } +}; + Round.prototype.flushRound = function () { return this.t.none(sql.flush, { round: this.scope.round }); }; @@ -110,8 +118,13 @@ Round.prototype.land = function () { // Constructor function RoundChanges (scope) { - this.roundFees = Math.floor(scope.__private.feesByRound[scope.round]) || 0; - this.roundRewards = (scope.__private.rewardsByRound[scope.round] || []); + if (scope.backwards) { + this.roundFees = Math.floor(scope.__private.unFeesByRound[scope.round]) || 0; + this.roundRewards = (scope.__private.unRewardsByRound[scope.round] || []); + } else { + this.roundFees = Math.floor(scope.__private.feesByRound[scope.round]) || 0; + this.roundRewards = (scope.__private.rewardsByRound[scope.round] || []); + } } // Public methods diff --git a/modules/blocks.js b/modules/blocks.js index ea8aefd9379..713b0dcb23b 100644 --- a/modules/blocks.js +++ b/modules/blocks.js @@ -983,18 +983,31 @@ Blocks.prototype.loadBlocksOffset = function (limit, offset, verify, cb) { Blocks.prototype.deleteLastBlock = function (cb) { library.logger.warn('Deleting last block', __private.lastBlock); - if (__private.lastBlock.height !== 1) { - __private.popLastBlock(__private.lastBlock, function (err, newLastBlock) { - if (err) { - library.logger.error('Error deleting last block', __private.lastBlock); - } - __private.lastBlock = newLastBlock; - return setImmediate(cb, err, newLastBlock); - }); - } else { + if (__private.lastBlock.height === 1) { return setImmediate(cb); } + + async.series({ + backwardSwap: function (seriesCb) { + modules.rounds.directionSwap('backward', null, seriesCb); + }, + popLastBlock: function (seriesCb) { + __private.popLastBlock(__private.lastBlock, function (err, newLastBlock) { + if (err) { + library.logger.error('Error deleting last block', __private.lastBlock); + } + + __private.lastBlock = newLastBlock; + return setImmediate(seriesCb); + }); + }, + forwardSwap: function (seriesCb) { + modules.rounds.directionSwap('forward', __private.lastBlock, seriesCb); + } + }, function (err) { + return setImmediate(cb, err, __private.lastBlock); + }); }; Blocks.prototype.loadLastBlock = function (cb) { @@ -1249,21 +1262,31 @@ Blocks.prototype.verifyBlock = function (block) { Blocks.prototype.deleteBlocksBefore = function (block, cb) { var blocks = []; - async.whilst( - function () { - return (block.height < __private.lastBlock.height); + async.series({ + backwardSwap: function (seriesCb) { + modules.rounds.directionSwap('backward', null, seriesCb); }, - function (next) { - blocks.unshift(__private.lastBlock); - __private.popLastBlock(__private.lastBlock, function (err, newLastBlock) { - __private.lastBlock = newLastBlock; - next(err); - }); + popBlocks: function (seriesCb) { + async.whilst( + function () { + return (block.height < __private.lastBlock.height); + }, + function (next) { + blocks.unshift(__private.lastBlock); + __private.popLastBlock(__private.lastBlock, function (err, newLastBlock) { + __private.lastBlock = newLastBlock; + next(err); + }); + }, + function (err) { + return setImmediate(seriesCb, err, blocks); + } + ); }, - function (err) { - return setImmediate(cb, err, blocks); + forwardSwap: function (seriesCb) { + modules.rounds.directionSwap('forward', __private.lastBlock, seriesCb); } - ); + }); }; Blocks.prototype.simpleDeleteAfterBlock = function (blockId, cb) { diff --git a/modules/rounds.js b/modules/rounds.js index 19095d5c962..98380bf7bcc 100644 --- a/modules/rounds.js +++ b/modules/rounds.js @@ -55,12 +55,18 @@ Rounds.prototype.directionSwap = function (direction, lastBlock, cb) { __private.feesByRound = {}; __private.rewardsByRound = {}; __private.delegatesByRound = {}; - self.flush(self.calc(lastBlock.height), cb); + + return setImmediate(cb); } else { __private.unFeesByRound = {}; __private.unRewardsByRound = {}; __private.unDelegatesByRound = {}; - self.flush(self.calc(lastBlock.height), cb); + + if (lastBlock) { + return __private.sumRound(self.calc(lastBlock.height), cb); + } else { + return setImmediate(cb); + } } }; @@ -71,7 +77,7 @@ Rounds.prototype.backwardTick = function (block, previousBlock, done) { __private.unFeesByRound[round] = Math.floor(__private.unFeesByRound[round]) || 0; __private.unFeesByRound[round] += Math.floor(block.totalFee); - __private.unRewardsByRound[round] = (__private.rewardsByRound[round] || []); + __private.unRewardsByRound[round] = (__private.unRewardsByRound[round] || []); __private.unRewardsByRound[round].push(block.reward); __private.unDelegatesByRound[round] = __private.unDelegatesByRound[round] || []; @@ -100,7 +106,9 @@ Rounds.prototype.backwardTick = function (block, previousBlock, done) { delete __private.unFeesByRound[round]; delete __private.unRewardsByRound[round]; delete __private.unDelegatesByRound[round]; - }); + }).then(promised.markBlockId); + } else { + return promised.markBlockId(); } }); } @@ -154,7 +162,7 @@ Rounds.prototype.tick = function (block, done) { scope.finishRound = ( (round !== nextRound && __private.delegatesByRound[round].length === slots.delegates) || - (block.height === 1 || block.heighti === 101) + (block.height === 1 || block.height === 101) ); function Tick (t) { @@ -214,21 +222,10 @@ Rounds.prototype.onBind = function (scope) { Rounds.prototype.onBlockchainReady = function () { var round = self.calc(modules.blocks.getLastBlock().height); - library.db.query(sql.summedRound, { round: round, activeDelegates:constants.activeDelegates }).then(function (rows) { - - var rewards = []; - - rows[0].rewards.forEach(function (reward) { - rewards.push(Math.floor(reward)); - }); - - __private.feesByRound[round] = Math.floor(rows[0].fees); - __private.rewardsByRound[round] = rewards; - __private.delegatesByRound[round] = rows[0].delegates; - __private.loaded = true; - - }).catch(function (err) { - library.logger.error('Round#onBlockchainReady error', err); + __private.sumRound(round, function (err) { + if (!err) { + __private.loaded = true; + } }); }; @@ -242,7 +239,6 @@ Rounds.prototype.cleanup = function (cb) { }; // Private - __private.getOutsiders = function (scope, cb) { scope.outsiders = []; @@ -264,6 +260,26 @@ __private.getOutsiders = function (scope, cb) { }); }; +__private.sumRound = function (round, cb) { + library.db.query(sql.summedRound, { round: round, activeDelegates: constants.activeDelegates }).then(function (rows) { + var rewards = []; + + rows[0].rewards.forEach(function (reward) { + rewards.push(Math.floor(reward)); + }); + + __private.feesByRound[round] = Math.floor(rows[0].fees); + __private.rewardsByRound[round] = rewards; + __private.delegatesByRound[round] = rows[0].delegates; + + return setImmediate(cb); + }).catch(function (err) { + library.logger.error('Failed to sum round', round); + library.logger.error(err.stack); + return setImmediate(cb, err); + }); +}; + // Shared // Export diff --git a/sql/loader.js b/sql/loader.js index 39085051a12..0d6d1cc94b1 100644 --- a/sql/loader.js +++ b/sql/loader.js @@ -11,7 +11,7 @@ var LoaderSql = { updateMemAccounts: 'UPDATE mem_accounts SET "u_isDelegate" = "isDelegate", "u_secondSignature" = "secondSignature", "u_username" = "username", "u_balance" = "balance", "u_delegates" = "delegates", "u_multisignatures" = "multisignatures";', - getOrphanedMemAccounts: 'SELECT a."blockId", b."id" FROM mem_accounts a LEFT OUTER JOIN blocks b ON b."id" = a."blockId" WHERE a."blockId" IS NOT NULL AND b."id" IS NULL', + getOrphanedMemAccounts: 'SELECT a."blockId", b."id" FROM mem_accounts a LEFT OUTER JOIN blocks b ON b."id" = a."blockId" WHERE a."blockId" IS NOT NULL AND a."blockId" != \'0\' AND b."id" IS NULL', getDelegates: 'SELECT ENCODE("publicKey", \'hex\') FROM mem_accounts WHERE "isDelegate" = 1' }; diff --git a/sql/rounds.js b/sql/rounds.js index ae486fe52a6..886e0f17aac 100644 --- a/sql/rounds.js +++ b/sql/rounds.js @@ -5,12 +5,20 @@ var RoundsSql = { truncateBlocks: 'DELETE FROM blocks WHERE "height" > (${height})::bigint;', - updateMissedBlocks: 'UPDATE mem_accounts SET "missedblocks" = "missedblocks" + 1 WHERE "address" IN ($1:csv);', + updateMissedBlocks: function (backwards) { + return [ + 'UPDATE mem_accounts SET "missedblocks" = "missedblocks"', + (backwards ? '- 1' : '+ 1'), + 'WHERE "address" IN ($1:csv);' + ].join(' '); + }, getVotes: 'SELECT d."delegate", d."amount" FROM (SELECT m."delegate", SUM(m."amount") AS "amount", "round" FROM mem_round m GROUP BY m."delegate", m."round") AS d WHERE "round" = (${round})::bigint', updateVotes: 'UPDATE mem_accounts SET "vote" = "vote" + (${amount})::bigint WHERE "address" = ${address};', + updateBlockId: 'UPDATE mem_accounts SET "blockId" = ${newId} WHERE "blockId" = ${oldId};', + summedRound: 'SELECT SUM(b."totalFee")::bigint AS "fees", ARRAY_AGG(b."reward") AS "rewards", ARRAY_AGG(ENCODE(b."generatorPublicKey", \'hex\')) AS "delegates" FROM blocks b WHERE (SELECT (CAST(b."height" / ${activeDelegates} AS INTEGER) + (CASE WHEN b."height" % ${activeDelegates} > 0 THEN 1 ELSE 0 END))) = ${round}' }; From 941874ab5eb3415546f00d7e749e9cf7dab6c182 Mon Sep 17 00:00:00 2001 From: Oliver Beddows Date: Sun, 27 Nov 2016 23:44:11 +0100 Subject: [PATCH 255/272] Returning error to callback. --- modules/blocks.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/modules/blocks.js b/modules/blocks.js index 713b0dcb23b..443b999ea56 100644 --- a/modules/blocks.js +++ b/modules/blocks.js @@ -985,7 +985,7 @@ Blocks.prototype.deleteLastBlock = function (cb) { library.logger.warn('Deleting last block', __private.lastBlock); if (__private.lastBlock.height === 1) { - return setImmediate(cb); + return setImmediate(cb, 'Can not delete genesis block'); } async.series({ From 1923b0c6d9be3dabd27469297632427f3aa21123 Mon Sep 17 00:00:00 2001 From: Oliver Beddows Date: Mon, 28 Nov 2016 00:33:16 +0100 Subject: [PATCH 256/272] Renaming simpleDeleteAfterBlock function. --- modules/blocks.js | 6 +++--- modules/loader.js | 2 +- sql/blocks.js | 2 +- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/modules/blocks.js b/modules/blocks.js index 443b999ea56..8ed61ac770d 100644 --- a/modules/blocks.js +++ b/modules/blocks.js @@ -1289,12 +1289,12 @@ Blocks.prototype.deleteBlocksBefore = function (block, cb) { }); }; -Blocks.prototype.simpleDeleteAfterBlock = function (blockId, cb) { - library.db.query(sql.simpleDeleteAfterBlock, {id: blockId}).then(function (res) { +Blocks.prototype.deleteAfterBlock = function (blockId, cb) { + library.db.query(sql.deleteAfterBlock, {id: blockId}).then(function (res) { return setImmediate(cb, null, res); }).catch(function (err) { library.logger.error(err.stack); - return setImmediate(cb, 'Blocks#simpleDeleteAfterBlock error'); + return setImmediate(cb, 'Blocks#deleteAfterBlock error'); }); }; diff --git a/modules/loader.js b/modules/loader.js index ba056ae2076..f204f0718df 100644 --- a/modules/loader.js +++ b/modules/loader.js @@ -253,7 +253,7 @@ __private.loadBlockChain = function () { library.logger.error(err); if (err.block) { library.logger.error('Blockchain failed at: ' + err.block.height); - modules.blocks.simpleDeleteAfterBlock(err.block.id, function (err, res) { + modules.blocks.deleteAfterBlock(err.block.id, function (err, res) { library.logger.error('Blockchain clipped'); library.bus.message('blockchainReady'); }); diff --git a/sql/blocks.js b/sql/blocks.js index eeff3e161df..f755195b7d8 100644 --- a/sql/blocks.js +++ b/sql/blocks.js @@ -73,7 +73,7 @@ var BlocksSql = { getBlockId: 'SELECT "id" FROM blocks WHERE "id" = ${id}', - simpleDeleteAfterBlock: 'DELETE FROM blocks WHERE "height" >= (SELECT "height" FROM blocks WHERE "id" = ${id});' + deleteAfterBlock: 'DELETE FROM blocks WHERE "height" >= (SELECT "height" FROM blocks WHERE "id" = ${id});' }; module.exports = BlocksSql; From b78c3b3f00432c0673c736d832463fb016949f3f Mon Sep 17 00:00:00 2001 From: Oliver Beddows Date: Mon, 28 Nov 2016 13:17:42 +0100 Subject: [PATCH 257/272] Closes #319. Fixing broadhash update reliability. --- logic/peer.js | 5 ++++- modules/loader.js | 26 ++++++++++---------------- modules/peers.js | 2 ++ modules/transport.js | 10 ++++------ 4 files changed, 20 insertions(+), 23 deletions(-) diff --git a/logic/peer.js b/logic/peer.js index 9812c3ad0f1..15761908919 100644 --- a/logic/peer.js +++ b/logic/peer.js @@ -88,7 +88,10 @@ Peer.prototype.headers = function (headers) { }; Peer.prototype.extend = function (object) { - return this.headers(extend({}, this.object(), object)); + var base = this.object(); + var extended = extend(this.object(), object); + + return this.headers(extended); }; Peer.prototype.object = function () { diff --git a/modules/loader.js b/modules/loader.js index f204f0718df..f14d8fdea5a 100644 --- a/modules/loader.js +++ b/modules/loader.js @@ -555,22 +555,16 @@ __private.getPeer = function (peer, cb) { }); }, getHeight: function (seriesCb) { - if (peer.height > modules.blocks.getLastBlock().height) { - return setImmediate(seriesCb); - } else { - modules.transport.getFromPeer(peer, { - api: '/height', - method: 'GET' - }, function (err, res) { - if (err) { - return setImmediate(seriesCb, 'Failed to get height from peer: ' + peer.string); - } else { - peer.height = res.body.height; - modules.peers.update(peer); - return setImmediate(seriesCb); - } - }); - } + modules.transport.getFromPeer(peer, { + api: '/height', + method: 'GET' + }, function (err, res) { + if (err) { + return setImmediate(seriesCb, 'Failed to get height from peer: ' + peer.string); + } else { + return setImmediate(seriesCb); + } + }); }, validateHeight: function (seriesCb) { var heightIsValid = library.schema.validate(peer, schema.getNetwork.height); diff --git a/modules/peers.js b/modules/peers.js index c5a852b89ff..7027200e81c 100644 --- a/modules/peers.js +++ b/modules/peers.js @@ -121,6 +121,8 @@ __private.updatePeersList = function (cb) { library.logger.error(['Rejecting peer', peer.string, 'with incompatible version', peer.version].join(' ')); self.remove(peer.ip, peer.port); } else { + delete peer.broadhash; + delete peer.height; self.update(peer); } diff --git a/modules/transport.js b/modules/transport.js index 7919a57126f..bd94f19b9fe 100644 --- a/modules/transport.js +++ b/modules/transport.js @@ -566,10 +566,6 @@ Transport.prototype.getFromPeer = function (peer, options, cb) { return setImmediate(cb, ['Peer is using incompatible version', headers.version, req.method, req.url].join(' ')); } - if (res.body.height) { - peer.height = res.body.height; - } - modules.peers.update(peer); return setImmediate(cb, null, {body: res.body, peer: peer}); @@ -622,11 +618,13 @@ Transport.prototype.onUnconfirmedTransaction = function (transaction, broadcast) }; Transport.prototype.onNewBlock = function (block, broadcast) { - if (broadcast && !__private.broadcaster.maxRelays(block)) { + if (broadcast) { var broadhash = modules.system.getBroadhash(); modules.system.update(function () { - __private.broadcaster.broadcast({limit: constants.maxPeers, broadhash: broadhash}, {api: '/blocks', data: {block: block}, method: 'POST', immediate: true}); + if (!__private.broadcaster.maxRelays(block)) { + __private.broadcaster.broadcast({limit: constants.maxPeers, broadhash: broadhash}, {api: '/blocks', data: {block: block}, method: 'POST', immediate: true}); + } library.network.io.sockets.emit('blocks/change', block); }); } From 309b3ffcfd370b9132a0620fc3b0c0061818fd9b Mon Sep 17 00:00:00 2001 From: Oliver Beddows Date: Mon, 28 Nov 2016 16:00:33 +0100 Subject: [PATCH 258/272] Establishing broadhash consensus before / after sync. --- modules/loader.js | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/modules/loader.js b/modules/loader.js index f14d8fdea5a..f0b974bd349 100644 --- a/modules/loader.js +++ b/modules/loader.js @@ -463,7 +463,7 @@ __private.sync = function (cb) { library.logger.debug('Undoing unconfirmed transactions before sync'); return modules.transactions.undoUnconfirmedList(seriesCb); }, - getPeers: function (seriesCb) { + getPeersBefore: function (seriesCb) { library.logger.debug('Getting peers to establish broadhash consensus'); return modules.transport.getPeers({limit: constants.maxPeers}, seriesCb); }, @@ -473,6 +473,10 @@ __private.sync = function (cb) { updateSystem: function (seriesCb) { return modules.system.update(seriesCb); }, + getPeersAfter: function (seriesCb) { + library.logger.debug('Getting peers to establish broadhash consensus'); + return modules.transport.getPeers({limit: constants.maxPeers}, seriesCb); + }, applyUnconfirmedList: function (seriesCb) { library.logger.debug('Applying unconfirmed transactions after sync'); return modules.transactions.applyUnconfirmedList(seriesCb); From ab94ea1c4673d66452467b99b57957aaff70ba21 Mon Sep 17 00:00:00 2001 From: Oliver Beddows Date: Mon, 28 Nov 2016 16:02:12 +0100 Subject: [PATCH 259/272] Changing logs. --- modules/loader.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/modules/loader.js b/modules/loader.js index f0b974bd349..e4b599a4451 100644 --- a/modules/loader.js +++ b/modules/loader.js @@ -464,7 +464,7 @@ __private.sync = function (cb) { return modules.transactions.undoUnconfirmedList(seriesCb); }, getPeersBefore: function (seriesCb) { - library.logger.debug('Getting peers to establish broadhash consensus'); + library.logger.debug('Establishling broadhash consensus before sync'); return modules.transport.getPeers({limit: constants.maxPeers}, seriesCb); }, loadBlocksFromNetwork: function (seriesCb) { @@ -474,7 +474,7 @@ __private.sync = function (cb) { return modules.system.update(seriesCb); }, getPeersAfter: function (seriesCb) { - library.logger.debug('Getting peers to establish broadhash consensus'); + library.logger.debug('Establishling broadhash consensus after sync'); return modules.transport.getPeers({limit: constants.maxPeers}, seriesCb); }, applyUnconfirmedList: function (seriesCb) { From 94688a6060c4f8be644a7c0b10ffcfb994ca4700 Mon Sep 17 00:00:00 2001 From: Oliver Beddows Date: Mon, 28 Nov 2016 16:07:23 +0100 Subject: [PATCH 260/272] Changing log --- logic/broadcaster.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/logic/broadcaster.js b/logic/broadcaster.js index c086b9d5bf9..3cb14ac42fc 100644 --- a/logic/broadcaster.js +++ b/logic/broadcaster.js @@ -66,7 +66,7 @@ Broadcaster.prototype.getPeers = function (params, cb) { } if (self.consensus !== undefined && originalLimit === constants.maxPeers) { - library.logger.info(['Broadhash consensus updated to', consensus, '%'].join(' ')); + library.logger.info(['Broadhash consensus now', consensus, '%'].join(' ')); self.consensus = consensus; } From c28e51761770b13bda2e3e53540978d7a9505da1 Mon Sep 17 00:00:00 2001 From: Oliver Beddows Date: Mon, 28 Nov 2016 16:45:14 +0100 Subject: [PATCH 261/272] Closes #323. Fixing bad setInterval strategy. --- logic/broadcaster.js | 6 ++++-- logic/peerSweeper.js | 20 ++++++++++++++------ logic/transactionPool.js | 12 ++++++++---- 3 files changed, 26 insertions(+), 12 deletions(-) diff --git a/logic/broadcaster.js b/logic/broadcaster.js index 3cb14ac42fc..1fb230f8251 100644 --- a/logic/broadcaster.js +++ b/logic/broadcaster.js @@ -38,15 +38,17 @@ function Broadcaster (scope) { }]; // Broadcaster timer - setInterval(function () { + setImmediate(function nextRelease () { async.series([ __private.releaseQueue ], function (err) { if (err) { library.logger.log('Broadcaster timer', err); } + + return setTimeout(nextRelease, self.config.broadcastInterval); }); - }, self.config.broadcastInterval); + }); } // Public methods diff --git a/logic/peerSweeper.js b/logic/peerSweeper.js index 1bcc8c80abb..bd35c18fa78 100644 --- a/logic/peerSweeper.js +++ b/logic/peerSweeper.js @@ -8,11 +8,17 @@ function PeerSweeper (scope) { this.limit = 100; this.scope = scope; - setInterval(function () { - if (this.peers.length) { - this.sweep(this.peers.splice(0, this.limit)); + var self = this; + + setImmediate(function nextSweep () { + if (self.peers.length) { + self.sweep(self.peers.splice(0, self.limit), function () { + return setTimeout(nextSweep, 1000); + }); + } else { + return setTimeout(nextSweep, 1000); } - }.bind(this), 1000); + }); } // Public methods @@ -28,7 +34,7 @@ PeerSweeper.prototype.push = function (action, peer) { this.peers.push(peer); }; -PeerSweeper.prototype.sweep = function (peers) { +PeerSweeper.prototype.sweep = function (peers, cb) { var self = this; if (!peers.length) { return; } @@ -41,10 +47,12 @@ PeerSweeper.prototype.sweep = function (peers) { return t.query(queries.join(';')); }).then(function () { self.addDapps(peers); - self.scope.library.logger.debug(['Swept', peers.length, 'peer changes'].join(' ')); + + return setImmediate(cb); }).catch(function (err) { self.scope.library.logger.error('Failed to sweep peers', err.stack); + return setImmediate(cb, err); }); }; diff --git a/logic/transactionPool.js b/logic/transactionPool.js index 005a501dcb5..11c9b032a36 100644 --- a/logic/transactionPool.js +++ b/logic/transactionPool.js @@ -22,26 +22,30 @@ function TransactionPool (scope) { self.processed = 0; // Bundled transaction timer - setInterval(function () { + setImmediate(function nextBundle () { async.series([ self.processBundled ], function (err) { if (err) { library.logger.log('Bundled transaction timer', err); } + + return setTimeout(nextBundle, self.bundledInterval); }); - }, self.bundledInterval); + }); // Transaction pool timer - setInterval(function () { + setImmediate(function nextExpiry () { async.series([ self.expireTransactions ], function (err) { if (err) { library.logger.log('Transaction pool timer', err); } + + return setTimeout(nextExpiry, self.poolInterval); }); - }, self.poolInterval); + }); } // Public methods From 8f89bb6ee999e4c6d64b1342d1c29cad7aece336 Mon Sep 17 00:00:00 2001 From: Oliver Beddows Date: Mon, 28 Nov 2016 17:24:26 +0100 Subject: [PATCH 262/272] Improving code clarity. --- logic/transactionPool.js | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/logic/transactionPool.js b/logic/transactionPool.js index 11c9b032a36..db3084e67af 100644 --- a/logic/transactionPool.js +++ b/logic/transactionPool.js @@ -16,7 +16,7 @@ function TransactionPool (scope) { self.bundled = { transactions: [], index: {} }; self.queued = { transactions: [], index: {} }; self.multisignature = { transactions: [], index: {} }; - self.poolInterval = 30000; + self.expiryInterval = 30000; self.bundledInterval = library.config.broadcasts.broadcastInterval; self.bundleLimit = library.config.broadcasts.releaseLimit; self.processed = 0; @@ -34,16 +34,16 @@ function TransactionPool (scope) { }); }); - // Transaction pool timer + // Transaction expiry timer setImmediate(function nextExpiry () { async.series([ self.expireTransactions ], function (err) { if (err) { - library.logger.log('Transaction pool timer', err); + library.logger.log('Transaction expiry timer', err); } - return setTimeout(nextExpiry, self.poolInterval); + return setTimeout(nextExpiry, self.expiryInterval); }); }); } From 79c4664eef1351438b730c892d7d9ec85d51cbf6 Mon Sep 17 00:00:00 2001 From: Oliver Beddows Date: Mon, 28 Nov 2016 17:56:24 +0100 Subject: [PATCH 263/272] Changing log level. --- logic/transactionPool.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/logic/transactionPool.js b/logic/transactionPool.js index db3084e67af..5ce22afedbf 100644 --- a/logic/transactionPool.js +++ b/logic/transactionPool.js @@ -258,13 +258,13 @@ TransactionPool.prototype.processBundled = function (cb) { __private.processVerifyTransaction(transaction, true, function (err, sender) { if (err) { - library.logger.error('Failed to process / verify bundled transaction: ' + transaction.id, err); + library.logger.debug('Failed to process / verify bundled transaction: ' + transaction.id, err); self.removeUnconfirmedTransaction(transaction); return setImmediate(eachSeriesCb); } else { self.queueTransaction(transaction, function (err) { if (err) { - library.logger.error('Failed to queue bundled transaction: ' + transaction.id, err); + library.logger.debug('Failed to queue bundled transaction: ' + transaction.id, err); } return setImmediate(eachSeriesCb); }); From 5223ca4256d3eb5b6d170f5e75754ac9504fd7f8 Mon Sep 17 00:00:00 2001 From: Oliver Beddows Date: Mon, 28 Nov 2016 19:53:40 +0100 Subject: [PATCH 264/272] Fixing getMultisignatureTransactionList arguments. Ensuring only unready transactions are returned. --- logic/transactionPool.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/logic/transactionPool.js b/logic/transactionPool.js index 5ce22afedbf..02e90ee9505 100644 --- a/logic/transactionPool.js +++ b/logic/transactionPool.js @@ -114,7 +114,7 @@ TransactionPool.prototype.getMergedTransactionList = function (reverse, limit) { var unconfirmed = modules.transactions.getUnconfirmedTransactionList(false, constants.maxTxsPerBlock); limit -= unconfirmed.length; - var multisignatures = modules.transactions.getMultisignatureTransactionList(false, constants.maxTxsPerBlock); + var multisignatures = modules.transactions.getMultisignatureTransactionList(false, false, constants.maxTxsPerBlock); limit -= multisignatures.length; var queued = modules.transactions.getQueuedTransactionList(false, limit); @@ -365,7 +365,7 @@ TransactionPool.prototype.expireTransactions = function (cb) { __private.expireTransactions(self.getQueuedTransactionList(true), ids, seriesCb); }, function (res, seriesCb) { - __private.expireTransactions(self.getMultisignatureTransactionList(true), ids, seriesCb); + __private.expireTransactions(self.getMultisignatureTransactionList(true, false), ids, seriesCb); } ], function (err, ids) { return setImmediate(cb, err, ids); From aeea9655270fa12d90d8176eb36f0700866a4bed Mon Sep 17 00:00:00 2001 From: Oliver Beddows Date: Mon, 28 Nov 2016 19:56:30 +0100 Subject: [PATCH 265/272] Adding test coverage. Using same signature again should not confirm transaction. --- test/api/multisignatures.js | 22 ++++++++++++++++++++++ 1 file changed, 22 insertions(+) diff --git a/test/api/multisignatures.js b/test/api/multisignatures.js index fa788c32465..f1906c56332 100644 --- a/test/api/multisignatures.js +++ b/test/api/multisignatures.js @@ -454,6 +454,17 @@ describe('POST /api/multisignatures/sign (group)', function () { }); }); + it('using same signature again should not confirm transaction', function (done) { + node.post('/api/multisignatures/sign', validParams, function (err, res) { + node.onNewBlock(function (err) { + node.get('/api/transactions/get?id=' + multiSigTx.txId, function (err, res) { + node.expect(res.body).to.have.property('success').to.be.not.ok; + done(); + }); + }); + }); + }); + it('using one more signature should confirm transaction', function (done) { confirmTransaction(multiSigTx.txId, passphrases.slice(-1), function () { node.onNewBlock(function (err) { @@ -512,6 +523,17 @@ describe('POST /api/multisignatures/sign (transaction)', function () { }); }); + it('using same signature again should not confirm transaction', function (done) { + node.post('/api/multisignatures/sign', validParams, function (err, res) { + node.onNewBlock(function (err) { + node.get('/api/transactions/get?id=' + multiSigTx.txId, function (err, res) { + node.expect(res.body).to.have.property('success').to.be.not.ok; + done(); + }); + }); + }); + }); + it('using one more signature should confirm transaction', function (done) { confirmTransaction(multiSigTx.txId, passphrases.slice(-1), function () { node.onNewBlock(function (err) { From 9c2015fc76578f465ec61bc459367649efa38692 Mon Sep 17 00:00:00 2001 From: Oliver Beddows Date: Tue, 29 Nov 2016 19:14:14 +0100 Subject: [PATCH 266/272] Increasing minBroadhashConsensus to 51%. --- helpers/constants.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/helpers/constants.js b/helpers/constants.js index 3b4fb5ed827..d24f8dab165 100644 --- a/helpers/constants.js +++ b/helpers/constants.js @@ -28,7 +28,7 @@ module.exports = { maxSignaturesLength: 196 * 256, maxTxsPerBlock: 25, maxTxsPerQueue: 10000, - minBroadhashConsensus: 10, + minBroadhashConsensus: 51, nethashes: [ // Mainnet 'ed14889723f24ecc54871d058d98ce91ff2f973192075c0155ba2b7b70ad2511', From 22e33bce2e3bca87044a57ad16145ec9ad826855 Mon Sep 17 00:00:00 2001 From: Oliver Beddows Date: Tue, 29 Nov 2016 19:51:40 +0100 Subject: [PATCH 267/272] Shortening ban times to 10 minutes. --- modules/blocks.js | 4 ++-- modules/loader.js | 4 ++-- modules/transport.js | 14 +++++++------- 3 files changed, 11 insertions(+), 11 deletions(-) diff --git a/modules/blocks.js b/modules/blocks.js index 8ed61ac770d..e90ab079b74 100644 --- a/modules/blocks.js +++ b/modules/blocks.js @@ -869,8 +869,8 @@ Blocks.prototype.loadBlocksFromPeer = function (peer, cb) { library.logger.debug(['Block', id].join(' '), err.toString()); if (block) { library.logger.debug('Block', block); } - library.logger.warn(['Block', id, 'is not valid, ban 60 min'].join(' '), peer.string); - modules.peers.state(peer.ip, peer.port, 0, 3600); + library.logger.warn(['Block', id, 'is not valid, ban 10 min'].join(' '), peer.string); + modules.peers.state(peer.ip, peer.port, 0, 600); } return seriesCb(err); }, true); diff --git a/modules/loader.js b/modules/loader.js index e4b599a4451..694bb409a97 100644 --- a/modules/loader.js +++ b/modules/loader.js @@ -169,8 +169,8 @@ __private.loadTransactions = function (cb) { library.logger.debug(['Transaction', id].join(' '), e.toString()); if (transaction) { library.logger.debug('Transaction', transaction); } - library.logger.warn(['Transaction', id, 'is not valid, ban 60 min'].join(' '), peer.string); - modules.peers.state(peer.ip, peer.port, 0, 3600); + library.logger.warn(['Transaction', id, 'is not valid, ban 10 min'].join(' '), peer.string); + modules.peers.state(peer.ip, peer.port, 0, 600); return setImmediate(eachSeriesCb, e); } diff --git a/modules/transport.js b/modules/transport.js index bd94f19b9fe..b9600979339 100644 --- a/modules/transport.js +++ b/modules/transport.js @@ -110,10 +110,10 @@ __private.attachApi = function () { }); if (!escapedIds.length) { - library.logger.warn('Invalid common block request, ban 60 min', req.peer.string); + library.logger.warn('Invalid common block request, ban 10 min', req.peer.string); - // Ban peer for 60 minutes - __private.banPeer({peer: req.peer, code: 'ECOMMON', req: req, clock: 3600}); + // Ban peer for 10 minutes + __private.banPeer({peer: req.peer, code: 'ECOMMON', req: req, clock: 600}); return res.json({success: false, error: 'Invalid block id sequence'}); } @@ -159,8 +159,8 @@ __private.attachApi = function () { if (block) { library.logger.debug('Block', block); } if (req.peer) { - // Ban peer for 60 minutes - __private.banPeer({peer: req.peer, code: 'EBLOCK', req: req, clock: 3600}); + // Ban peer for 10 minutes + __private.banPeer({peer: req.peer, code: 'EBLOCK', req: req, clock: 600}); } return res.status(200).json({success: false, error: e.toString()}); @@ -446,8 +446,8 @@ __private.receiveTransaction = function (transaction, req, cb) { if (transaction) { library.logger.debug('Transaction', transaction); } if (req.peer) { - // Ban peer for 60 minutes - __private.banPeer({peer: req.peer, code: 'ETRANSACTION', req: req, clock: 3600}); + // Ban peer for 10 minutes + __private.banPeer({peer: req.peer, code: 'ETRANSACTION', req: req, clock: 600}); } return setImmediate(cb, 'Invalid transaction body'); From ac4daba9409c585e08c37d4f88aefb76e95cf12c Mon Sep 17 00:00:00 2001 From: Oliver Beddows Date: Tue, 29 Nov 2016 20:18:26 +0100 Subject: [PATCH 268/272] Removing ids to reduce response time. For POST /peer/signatures and /peer/transactions. --- modules/transport.js | 22 +++++++++------------- 1 file changed, 9 insertions(+), 13 deletions(-) diff --git a/modules/transport.js b/modules/transport.js index b9600979339..e411fefde24 100644 --- a/modules/transport.js +++ b/modules/transport.js @@ -173,11 +173,11 @@ __private.attachApi = function () { router.post('/signatures', function (req, res) { if (req.body.signatures) { - __private.receiveSignatures(req, function (err, ids) { + __private.receiveSignatures(req, function (err) { if (err) { return res.status(200).json({success: false, message: err}); } else { - return res.status(200).json({success: true, signatureIds: ids}); + return res.status(200).json({success: true}); } }); } else { @@ -185,7 +185,7 @@ __private.attachApi = function () { if (err) { return res.status(200).json({success: false, message: err}); } else { - return res.status(200).json({success: true, signatureId: id}); + return res.status(200).json({success: true}); } }); } @@ -217,11 +217,11 @@ __private.attachApi = function () { router.post('/transactions', function (req, res) { if (req.body.transactions) { - __private.receiveTransactions(req, function (err, ids) { + __private.receiveTransactions(req, function (err) { if (err) { return res.status(200).json({success: false, message: err}); } else { - return res.status(200).json({success: true, transactionIds: ids}); + return res.status(200).json({success: true}); } }); } else { @@ -351,7 +351,7 @@ __private.removePeer = function (options) { }; __private.receiveSignatures = function (req, cb) { - var ids, signatures; + var signatures; async.series({ validateSchema: function (seriesCb) { @@ -365,13 +365,11 @@ __private.receiveSignatures = function (req, cb) { }, receiveSignatures: function (seriesCb) { signatures = req.body.signatures; - ids = signatures.map(function (signature) { return signature.id; }); async.eachSeries(signatures, function (signature, eachSeriesCb) { __private.receiveSignature(signature, req, function (err) { if (err) { library.logger.debug(err, signature); - ids.splice(ids.indexOf(signature.id)); } return setImmediate(eachSeriesCb); @@ -379,7 +377,7 @@ __private.receiveSignatures = function (req, cb) { }, seriesCb); } }, function (err) { - return setImmediate(cb, err, (ids || [])); + return setImmediate(cb, err); }); }; @@ -402,7 +400,7 @@ __private.receiveSignature = function (signature, req, cb) { }; __private.receiveTransactions = function (req, cb) { - var ids, transactions; + var transactions; async.series({ validateSchema: function (seriesCb) { @@ -416,7 +414,6 @@ __private.receiveTransactions = function (req, cb) { }, receiveTransactions: function (seriesCb) { transactions = req.body.transactions; - ids = transactions.map(function (transaction) { return transaction.id; }); async.eachSeries(transactions, function (transaction, eachSeriesCb) { transaction.bundled = true; @@ -424,7 +421,6 @@ __private.receiveTransactions = function (req, cb) { __private.receiveTransaction(transaction, req, function (err) { if (err) { library.logger.debug(err, transaction); - ids.splice(ids.indexOf(transaction.id)); } return setImmediate(eachSeriesCb); @@ -432,7 +428,7 @@ __private.receiveTransactions = function (req, cb) { }, seriesCb); } }, function (err) { - return setImmediate(cb, err, (ids || [])); + return setImmediate(cb, err); }); }; From b43303e5f0d52daa6d0e1961498f975465a92605 Mon Sep 17 00:00:00 2001 From: Oliver Beddows Date: Wed, 30 Nov 2016 02:43:19 +0100 Subject: [PATCH 269/272] Reducing maxTxsPerQueue. --- helpers/constants.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/helpers/constants.js b/helpers/constants.js index d24f8dab165..a709b348fc3 100644 --- a/helpers/constants.js +++ b/helpers/constants.js @@ -27,7 +27,7 @@ module.exports = { maxSharedTxs: 100, maxSignaturesLength: 196 * 256, maxTxsPerBlock: 25, - maxTxsPerQueue: 10000, + maxTxsPerQueue: 5000, minBroadhashConsensus: 51, nethashes: [ // Mainnet From a5dc2bdc9fbbd4928a0709fb58791e052b5376c1 Mon Sep 17 00:00:00 2001 From: Oliver Beddows Date: Tue, 29 Nov 2016 16:32:56 +0100 Subject: [PATCH 270/272] Closes #322. Fixing multisignature processing. - Fixing incorrect message bus arguments. - Resetting u_multimin, u_multilifetime on startup. - Removing nested balancesSequence (causing timeouts). - Updating ready state after processing signature. - Fixing max keysgroup size check. Adding ability to add exceptions for existing transactions. - Fixing erroneous check on account.multisignatures. - Determining multisignatures from sender or transaction asset. - Pushing senderPublicKey onto multisignatures. - Improving check on requester public key. - Improving error messages. - Updating lisk-js submodule. Describing POST /peer/signatures. - Creating a new multisignature account. - Sending transaction from multisignature account. --- helpers/exceptions.js | 1 + logic/multisignature.js | 12 ++- logic/transaction.js | 31 +++---- modules/multisignatures.js | 26 ++++-- modules/transactions.js | 2 +- modules/transport.js | 18 ++-- sql/loader.js | 2 +- test/api/peer.signatures.js | 180 +++++++++++++++++++++++++++++++++++- test/lisk-js | 2 +- 9 files changed, 231 insertions(+), 43 deletions(-) diff --git a/helpers/exceptions.js b/helpers/exceptions.js index 1edec554b22..f598b30414b 100644 --- a/helpers/exceptions.js +++ b/helpers/exceptions.js @@ -24,6 +24,7 @@ module.exports = { '5384302058030309746', // 869890 '9352922026980330230', // 925165 ], + multisignatures: [], votes: [ '5524930565698900323', // 20407 '11613486949732674475', // 123300 diff --git a/logic/multisignature.js b/logic/multisignature.js index 50de91feba0..691d3b47342 100644 --- a/logic/multisignature.js +++ b/logic/multisignature.js @@ -4,6 +4,7 @@ var async = require('async'); var ByteBuffer = require('bytebuffer'); var constants = require('../helpers/constants.js'); var Diff = require('../helpers/diff.js'); +var exceptions = require('../helpers/exceptions.js'); // Private fields var modules, library, __private = {}; @@ -52,8 +53,15 @@ Multisignature.prototype.verify = function (trs, sender, cb) { return setImmediate(cb, 'Invalid multisignature min. Must be between 1 and 16'); } - if (trs.asset.multisignature.min > trs.asset.multisignature.keysgroup.length + 1) { - return setImmediate(cb, 'Invalid multisignature min. Must be less than keysgroup size'); + if (trs.asset.multisignature.min > trs.asset.multisignature.keysgroup.length) { + var err = 'Invalid multisignature min. Must be less than keysgroup size'; + + if (exceptions.multisignatures.indexOf(trs.id) > -1) { + this.scope.logger.debug(err); + this.scope.logger.debug(JSON.stringify(trs)); + } else { + return setImmediate(cb, err); + } } if (trs.asset.multisignature.lifetime < 1 || trs.asset.multisignature.lifetime > 72) { diff --git a/logic/transaction.js b/logic/transaction.js index 2ae8bae5eea..6ed371685ef 100644 --- a/logic/transaction.js +++ b/logic/transaction.js @@ -329,10 +329,23 @@ Transaction.prototype.verify = function (trs, sender, requester, cb) { return setImmediate(cb, 'Invalid sender address'); } + // Determine multisignatures from sender or transaction asset + var multisignatures = sender.multisignatures || sender.u_multisignatures || []; + if (multisignatures.length === 0) { + if (trs.asset && trs.asset.multisignature && trs.asset.multisignature.keysgroup) { + + multisignatures = trs.asset.multisignature.keysgroup.map(function (key) { + return key.slice(1); + }); + } + } + // Check requester public key if (trs.requesterPublicKey) { + multisignatures.push(trs.senderPublicKey); + if (sender.multisignatures.indexOf(trs.requesterPublicKey) < 0) { - return setImmediate(cb, 'Invalid requester public key'); + return setImmediate(cb, 'Account does not belong to multisignature group'); } } @@ -384,22 +397,6 @@ Transaction.prototype.verify = function (trs, sender, requester, cb) { } } - // Determine multisignatures from sender or transaction asset - var multisignatures = sender.multisignatures || sender.u_multisignatures || []; - if (multisignatures.length === 0) { - if (trs.asset && trs.asset.multisignature && trs.asset.multisignature.keysgroup) { - - multisignatures = trs.asset.multisignature.keysgroup.map(function (key) { - return key.slice(1); - }); - } - } - - // Add sender to multisignatures - if (trs.requesterPublicKey) { - multisignatures.push(trs.senderPublicKey); - } - // Verify multisignatures if (trs.signatures) { for (var d = 0; d < trs.signatures.length; d++) { diff --git a/modules/multisignatures.js b/modules/multisignatures.js index 3abb66dab82..0a658323fc6 100644 --- a/modules/multisignatures.js +++ b/modules/multisignatures.js @@ -72,11 +72,22 @@ Multisignatures.prototype.processSignature = function (tx, cb) { return setImmediate(cb, 'Transaction not found'); } - transaction.signatures = transaction.signatures || []; - transaction.signatures.push(tx.signature); - library.bus.message('signature', transaction, true); + modules.accounts.getAccount({ + address: transaction.senderId + }, function (err, sender) { + if (err) { + return setImmediate(cb, err); + } else if (!sender) { + return setImmediate(cb, 'Sender not found'); + } else { + transaction.signatures = transaction.signatures || []; + transaction.signatures.push(tx.signature); + transaction.ready = Multisignature.prototype.ready(transaction, sender); - return setImmediate(cb); + library.bus.message('signature', {transaction: tx.transaction, signature: tx.signature}, true); + return setImmediate(cb); + } + }); }, cb); } @@ -433,12 +444,9 @@ shared.sign = function (req, cb) { transaction.signatures.push(scope.signature); transaction.ready = Multisignature.prototype.ready(transaction, scope.sender); - library.bus.message('signature', { - signature: scope.signature, - transaction: transaction.id - }, true); - + library.bus.message('signature', {transaction: transaction.id, signature: scope.signature}, true); library.network.io.sockets.emit('multisignatures/signature/change', transaction); + return setImmediate(cb, null, {transactionId: transaction.id}); }); }, cb); diff --git a/modules/transactions.js b/modules/transactions.js index e384283b12f..c1c628e97b8 100644 --- a/modules/transactions.js +++ b/modules/transactions.js @@ -446,7 +446,7 @@ shared.addTransactions = function (req, cb) { return setImmediate(cb, 'Multisignature account not found'); } - if (!account.multisignatures || !account.multisignatures) { + if (!Array.isArray(account.multisignatures)) { return setImmediate(cb, 'Account does not have multisignatures enabled'); } diff --git a/modules/transport.js b/modules/transport.js index e411fefde24..ccc645d7a09 100644 --- a/modules/transport.js +++ b/modules/transport.js @@ -384,18 +384,16 @@ __private.receiveSignatures = function (req, cb) { __private.receiveSignature = function (signature, req, cb) { library.schema.validate({signature: signature}, schema.signature, function (err) { if (err) { - return setImmediate(cb, 'Signature validation failed'); + return setImmediate(cb, 'Invalid signature body'); } - library.balancesSequence.add(function (cb) { - modules.multisignatures.processSignature(signature, function (err) { - if (err) { - return setImmediate(cb, 'Error processing signature'); - } else { - return setImmediate(cb); - } - }); - }, cb); + modules.multisignatures.processSignature(signature, function (err) { + if (err) { + return setImmediate(cb, 'Error processing signature: ' + err); + } else { + return setImmediate(cb); + } + }); }); }; diff --git a/sql/loader.js b/sql/loader.js index 0d6d1cc94b1..b777856c60f 100644 --- a/sql/loader.js +++ b/sql/loader.js @@ -9,7 +9,7 @@ var LoaderSql = { getMemRounds: 'SELECT "round" FROM mem_round GROUP BY "round"', - updateMemAccounts: 'UPDATE mem_accounts SET "u_isDelegate" = "isDelegate", "u_secondSignature" = "secondSignature", "u_username" = "username", "u_balance" = "balance", "u_delegates" = "delegates", "u_multisignatures" = "multisignatures";', + updateMemAccounts: 'UPDATE mem_accounts SET "u_isDelegate" = "isDelegate", "u_secondSignature" = "secondSignature", "u_username" = "username", "u_balance" = "balance", "u_delegates" = "delegates", "u_multisignatures" = "multisignatures", "u_multimin" = "multimin", "u_multilifetime" = "multilifetime";', getOrphanedMemAccounts: 'SELECT a."blockId", b."id" FROM mem_accounts a LEFT OUTER JOIN blocks b ON b."id" = a."blockId" WHERE a."blockId" IS NOT NULL AND a."blockId" != \'0\' AND b."id" IS NULL', diff --git a/test/api/peer.signatures.js b/test/api/peer.signatures.js index fab8a31304b..7e1b72c05e4 100644 --- a/test/api/peer.signatures.js +++ b/test/api/peer.signatures.js @@ -2,6 +2,31 @@ var node = require('./../node.js'); +var owner = node.randomAccount(); +var coSigner1 = node.randomAccount(); +var coSigner2 = node.randomAccount(); + +function postTransaction (transaction, done) { + node.post('/peer/transactions', { + transaction: transaction + }, done); +} + +function postTransactions (transactions, done) { + node.post('/peer/transactions', { + transactions: transactions + }, done); +} + +function postSignature (transaction, signature, done) { + node.post('/peer/signatures', { + signature: { + transaction: transaction.id, + signature: signature + } + }, done); +} + describe('GET /peer/signatures', function () { it('using incorrect nethash in headers should fail', function (done) { @@ -86,7 +111,7 @@ describe('POST /peer/signatures', function () { .end(function (err, res) { node.debug('> Response:'.grey, JSON.stringify(res.body)); node.expect(res.body).to.have.property('success').to.be.not.ok; - node.expect(res.body).to.have.property('message').to.equal('Signature validation failed'); + node.expect(res.body).to.have.property('message').to.equal('Invalid signature body'); done(); }); }); @@ -98,13 +123,164 @@ describe('POST /peer/signatures', function () { .end(function (err, res) { node.debug('> Response:'.grey, JSON.stringify(res.body)); node.expect(res.body).to.have.property('success').to.be.not.ok; - node.expect(res.body).to.have.property('message').to.equal('Error processing signature'); + node.expect(res.body).to.have.property('message').to.equal('Error processing signature: Transaction not found'); done(); }); }); it('using processable signature should be ok'); + describe('creating a new multisignature account', function () { + + var transaction; + + // Fund accounts + before(function (done) { + var transactions = []; + + node.async.eachSeries([owner, coSigner1, coSigner2], function (account, eachSeriesCb) { + transactions.push( + node.lisk.transaction.createTransaction(account.address, 100000000000, node.gAccount.password) + ); + eachSeriesCb(); + }, function (err) { + postTransactions(transactions, function (err, res) { + node.expect(res.body).to.have.property('success').to.be.ok; + node.onNewBlock(function (err) { + done(); + }); + }); + }); + }); + + // Create multisignature group + before(function (done) { + var keysgroup = ['+' + coSigner1.publicKey, '+' + coSigner2.publicKey]; + var lifetime = 72; + var min = 2; + + transaction = node.lisk.multisignature.createMultisignature(owner.password, null, keysgroup, lifetime, min); + + postTransactions([transaction], function (err, res) { + node.expect(res.body).to.have.property('success').to.be.ok; + node.onNewBlock(function (err) { + done(); + }); + }); + }); + + it('using processable signature for owner should fail', function (done) { + var signature = node.lisk.multisignature.signTransaction(transaction, owner.password); + + postSignature(transaction, signature, function (err, res) { + node.expect(res.body).to.have.property('success').to.be.not.ok; + node.expect(res.body).to.have.property('message').to.equal('Error processing signature: Failed to verify signature'); + done(); + }); + }); + + it('using processable signature for coSigner1 should be ok', function (done) { + var signature = node.lisk.multisignature.signTransaction(transaction, coSigner1.password); + + postSignature(transaction, signature, function (err, res) { + node.expect(res.body).to.have.property('success').to.be.ok; + done(); + }); + }); + + it('using processable signature for coSigner1 should not confirm the transaction', function (done) { + node.onNewBlock(function (err) { + node.onNewBlock(function (err) { + node.get('/api/transactions/get?id=' + transaction.id, function (err, res) { + node.expect(res.body).to.have.property('success').to.be.not.ok; + done(); + }); + }); + }); + }); + + it('using processable signature for coSigner2 should be ok', function (done) { + var signature = node.lisk.multisignature.signTransaction(transaction, coSigner2.password); + + postSignature(transaction, signature, function (err, res) { + node.expect(res.body).to.have.property('success').to.be.ok; + done(); + }); + }); + + it('using processable signature for coSigner2 should confirm the transaction', function (done) { + node.onNewBlock(function (err) { + node.get('/api/transactions/get?id=' + transaction.id, function (err, res) { + node.expect(res.body).to.have.property('success').to.be.ok; + node.expect(res.body).to.have.property('transaction'); + node.expect(res.body.transaction).to.have.property('id').to.equal(transaction.id); + done(); + }); + }); + }); + }); + + describe('sending transaction from multisignature account', function () { + + var transaction; + + before(function (done) { + node.onNewBlock(done); + }); + + // Send transaction + before(function (done) { + transaction = node.lisk.multisignature.createTransaction('1L', 1, owner.password); + + postTransaction(transaction, function (err, res) { + node.expect(res.body).to.have.property('success').to.be.ok; + node.onNewBlock(function (err) { + done(); + }); + }); + }); + + it('using processable signature for coSigner1 should be ok', function (done) { + var signature = node.lisk.multisignature.signTransaction(transaction, coSigner1.password); + + postSignature(transaction, signature, function (err, res) { + node.expect(res.body).to.have.property('success').to.be.ok; + done(); + }); + }); + + it('using processable signature for coSigner1 should not confirm the transaction', function (done) { + node.onNewBlock(function (err) { + node.onNewBlock(function (err) { + node.get('/api/transactions/get?id=' + transaction.id, function (err, res) { + node.expect(res.body).to.have.property('success').to.be.not.ok; + done(); + }); + }); + }); + }); + + it('using processable signature for coSigner2 should be ok', function (done) { + var signature = node.lisk.multisignature.signTransaction(transaction, coSigner2.password); + + postSignature(transaction, signature, function (err, res) { + node.expect(res.body).to.have.property('success').to.be.ok; + done(); + }); + }); + + it('using processable signature for coSigner2 should confirm the transaction', function (done) { + node.onNewBlock(function (err) { + node.get('/api/transactions/get?id=' + transaction.id, function (err, res) { + node.expect(res.body).to.have.property('success').to.be.ok; + node.expect(res.body).to.have.property('transaction'); + node.expect(res.body.transaction).to.have.property('id').to.equal(transaction.id); + done(); + }); + }); + }); + }); + describe('using multiple signatures', function () { it('with unprocessable signature should fail'); diff --git a/test/lisk-js b/test/lisk-js index 7ef53f0b271..da73fa32d11 160000 --- a/test/lisk-js +++ b/test/lisk-js @@ -1 +1 @@ -Subproject commit 7ef53f0b27153c6adea49ce8eb7391bf924e9498 +Subproject commit da73fa32d113379321fca83f19fb251e46b9681d From 360c73a73ec7cb0ef184a4ea6a07a80e51c10860 Mon Sep 17 00:00:00 2001 From: Oliver Beddows Date: Wed, 30 Nov 2016 05:26:02 +0100 Subject: [PATCH 271/272] Updating lisk-ui submodule. --- public | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/public b/public index 03474e641d5..3c340a808f4 160000 --- a/public +++ b/public @@ -1 +1 @@ -Subproject commit 03474e641d5eac8c27ab8af562f4c86d4dbe11df +Subproject commit 3c340a808f4900c70a8d3368f3d2520dddefb7de From 6a839dd202e051dc8ebcae8fe5db57065999a162 Mon Sep 17 00:00:00 2001 From: Oliver Beddows Date: Wed, 30 Nov 2016 06:59:21 +0100 Subject: [PATCH 272/272] Removing expectation. --- test/api/peer.transactions.stress.js | 1 - 1 file changed, 1 deletion(-) diff --git a/test/api/peer.transactions.stress.js b/test/api/peer.transactions.stress.js index a1298a3e84f..22837df2eda 100644 --- a/test/api/peer.transactions.stress.js +++ b/test/api/peer.transactions.stress.js @@ -40,7 +40,6 @@ describe('POST /peer/transactions', function () { postTransactions(bundled, function (err, res) { node.expect(res.body).to.have.property('success').to.be.ok; - node.expect(res.body).to.have.property('transactionIds').that.is.an('array').with.lengthOf(node.config.broadcasts.releaseLimit); next(); }); }, function () {