Skip to content
This repository has been archived by the owner on Jun 11, 2024. It is now read-only.

Refactor rounds logic - Closes #543 #597

Merged
merged 98 commits into from
Aug 8, 2017
Merged
Show file tree
Hide file tree
Changes from 38 commits
Commits
Show all changes
98 commits
Select commit Hold shift + click to select a range
ddb983f
Merge with development
4miners May 7, 2017
742445e
Add missing bracket
4miners May 7, 2017
d676cdd
Drop not needed 'async.series'
4miners May 7, 2017
7d90dc8
Replace table 'rounds_fees' with 'rounds_rewards', add support for ex…
4miners May 7, 2017
3ae9b59
Create SQL function for validating memory balances, validate at node …
4miners May 8, 2017
897c5ad
Improve SQL query formatting
4miners May 8, 2017
ad0b94c
Create SQL function generateDelegatesList, added tests
4miners May 8, 2017
fd6527a
Merge branch 'development' into 543_refactor-rounds-logic
4miners May 17, 2017
dd7c450
Port exceptions for rounds to db layer on node start
4miners Jun 8, 2017
26b01be
Check if rounds exceptions match database instead of force-update
4miners Jun 8, 2017
a7a8789
Initial implementation of rounds management in postgres
4miners Jun 8, 2017
d50f559
Improve comments for SQL stuff
4miners Jun 8, 2017
638ccb9
Adjust delegates endpoint to use new table, add rank update trigger
4miners Jun 8, 2017
796aa2f
Merge branch 'development' into 543_refactor-rounds-logic
4miners Jun 8, 2017
b8dbf90
Merge remote-tracking branch 'LiskHQ/development' into 543_refactor-r…
4miners Jul 6, 2017
1614640
Fix error from merge
4miners Jul 6, 2017
9fcf8e0
Not use destructuring assignment
4miners Jul 6, 2017
ea5479d
Fix intend, add 'grunt release' test
4miners Jul 6, 2017
652730b
Delete deprecated migration
4miners Jul 6, 2017
768b1db
Remove test for duplicated delegates
4miners Jul 6, 2017
a564b1c
Initial implementation for postgres LISTEN/NOTIFY feature
4miners Jul 7, 2017
b94582f
Fix intend and missing bracket in Jenkinsfile
4miners Jul 7, 2017
6a72081
Adjust DelegatesListSQL tests to new deps versions
4miners Jul 7, 2017
d1db361
Notify from postgres when round changes
4miners Jul 7, 2017
ab64820
Replaced 'round' notification with 'round-closed' and 'round-reopened'
4miners Jul 10, 2017
036107b
Listen/unlisten to all supported channels properly
4miners Jul 10, 2017
4ecff42
Update 'pg-promise' dependency to latest varsion
4miners Jul 10, 2017
5eba226
Change log level for logging fail of initial connection
4miners Jul 10, 2017
8aafed5
Add 'init' tests for pg-notify helper
4miners Jul 10, 2017
8ddfeeb
Merge branch 'development' into 543_refactor-rounds-logic
Jul 10, 2017
a6ea671
Refactor visibility, don't call process.exit on test env
4miners Jul 10, 2017
8bbf0a2
Added more tests for pg-notify helper
4miners Jul 10, 2017
2c4d001
Better error handling for pg-notify helper
4miners Jul 10, 2017
d257664
Added/refactored tests for pg-notify helper
4miners Jul 10, 2017
e1f45b0
Fix eslint errors
4miners Jul 10, 2017
2770d93
Improved error handling, remove isTestEnv function
4miners Jul 10, 2017
df30d35
Add triggerNotify SQL query for pg-notify tests
4miners Jul 10, 2017
33be545
Add tests for 'onNotification', removed tests for 'isTestEnv'
4miners Jul 10, 2017
a153365
Upgrade 'pg-promise' dependency to 6.3.5
4miners Jul 11, 2017
9c80dcd
Fix indexes bug in pg-notify tests
4miners Jul 11, 2017
b548cc8
Removed unused property, improve comment
4miners Jul 11, 2017
96ae6eb
Merge remote-tracking branch 'LiskHQ/development' into 543_refactor-r…
4miners Jul 11, 2017
1000078
Merge remote-tracking branch 'LiskHQ/development' into 543_refactor-r…
4miners Jul 12, 2017
e398391
Fix bug in jobsQueue, added unit tests for it
4miners Jul 13, 2017
f47acf9
Merge remote-tracking branch 'LiskHQ/development' into 543_refactor-r…
4miners Jul 13, 2017
19cf5e5
Move variables and functions to proper scope for jobsQueue test
4miners Jul 13, 2017
c929941
Reverted jobsQueue to exec functions immediatelly when added to queue
4miners Jul 13, 2017
471a579
Chenged expectations for jobsQueue tests
4miners Jul 13, 2017
2ba556e
Create SQL function getDelegatesList
4miners Jul 14, 2017
b4fa22a
Trigger delegates_update_on_block also on height 1
4miners Jul 14, 2017
fac2661
Maintain delegates list in memory, changed finishRound event to round…
4miners Jul 14, 2017
95ce3b1
Remove old rounds logic
4miners Jul 14, 2017
65ab982
Removed rounds unit test from Jenkinsfile
4miners Jul 14, 2017
af41657
Remove dapp name and link from unconfirmed tracking objects when apply
4miners Jul 14, 2017
a846cc3
Allow pg-notify helper to receive JSON in notifications
4miners Jul 14, 2017
6c3086b
Changes expectations for pg-notify unit test, add test cases
4miners Jul 14, 2017
34a0c94
Return round number and delegates list via notifications
4miners Jul 14, 2017
269c880
Support new data fromat in onRoundChanged events
4miners Jul 14, 2017
300e55e
Added note about pgcrypto extension to readme
4miners Jul 14, 2017
726feaf
Added test for SQL function getDelegatesList
4miners Jul 14, 2017
6ac28a5
Reorganise pg-notify tests to prevent interrupt on working node
4miners Jul 14, 2017
bfb0e70
Fix DApps module initialisation
4miners Jul 16, 2017
6150efb
Fix spelling and code standards
4miners Jul 22, 2017
5af4103
Change vote_insert_delete trigger to deffered
4miners Jul 22, 2017
11eecab
Add tests for rounds-related things - genesisBlock
4miners Jul 22, 2017
29eb954
Improve code readability, standards in tests
4miners Jul 22, 2017
e77c4cb
Add manual forging control for rounds-related tests
4miners Jul 23, 2017
e48dd79
Fix wrong trigger name
4miners Jul 23, 2017
70974e0
Do not calculate rewards for forger of genesis block
4miners Jul 23, 2017
2011949
Add first round test to rounds-related tests
4miners Jul 23, 2017
24bfe2c
Merge remote-tracking branch 'LiskHQ/1.0.0' into 543_refactor-rounds-…
4miners Jul 31, 2017
a89f367
Run SQL tests in sequence
4miners Jul 31, 2017
4e60dd6
Reorder unit tests
4miners Jul 31, 2017
7d02046
Fix transaction unit test
4miners Jul 31, 2017
216e4e3
Init webSocket for rounds-related tests
4miners Jul 31, 2017
b431008
Wait 1 sec for network initialisation in rounds-related tests
4miners Jul 31, 2017
0fb92c4
Fix/improve comments, add test for last block of round deletion (type 0)
4miners Aug 3, 2017
22b6107
Use generateBlock instead of forge to speed up forging process
4miners Aug 4, 2017
196ad48
Base voters calculations on block height instead of round
4miners Aug 4, 2017
b33de41
Add tests for rollback when forger of last block of round is unvoted
4miners Aug 4, 2017
3898dc3
Exec validateMemBalances after every tests
4miners Aug 4, 2017
b9e694d
Add test for round rollback replace last block forger at last block
4miners Aug 4, 2017
19ff1f5
Replace setTimeout with Promise.delay, wait 20 ms for pg-notify
4miners Aug 4, 2017
d8add19
Create functions for maintain outsiders (missed blocks)
4miners Aug 4, 2017
02d4894
Added tests for outsiders
4miners Aug 4, 2017
9f8fe9e
Add tests for round rewards consistency
4miners Aug 7, 2017
8e873e3
Merge remote-tracking branch 'LiskHQ/1.0.0' into 543_refactor-rounds-…
4miners Aug 7, 2017
52562c1
Remove unused modules var and bindModules event
4miners Aug 7, 2017
0c90a8b
Fix standards
4miners Aug 7, 2017
f011aaa
Rename migrations so they can run after last applied one
4miners Aug 7, 2017
ab3e08a
Improve descriptions for tests
4miners Aug 7, 2017
45e6ec8
Better structure for delegatesList SQL tests
4miners Aug 7, 2017
dff4b7a
Remove unused bindModules call
4miners Aug 7, 2017
452f892
Remove unused bindModules call
4miners Aug 7, 2017
559ab05
Improve description in tests
4miners Aug 7, 2017
c13d919
Fix code standards in pg-notify helper
4miners Aug 8, 2017
82e03cb
Added TODO/FIXME comments for things that need to be done
4miners Aug 8, 2017
0b2365a
Remove unused rounds module references in tests
4miners Aug 8, 2017
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
828 changes: 418 additions & 410 deletions Jenkinsfile

Large diffs are not rendered by default.

4 changes: 4 additions & 0 deletions app.js
Original file line number Diff line number Diff line change
Expand Up @@ -432,6 +432,10 @@ d.run(function () {
var db = require('./helpers/database.js');
db.connect(config.db, logger, cb);
},
pg_notify: ['db', 'bus', 'logger', function (scope, cb) {
var pg_notify = require('./helpers/pg-notify.js');
pg_notify.init(scope.db, scope.bus, scope.logger, cb);
}],
/**
* It tries to connect with redis server based on config. provided in config.json file
* @param {function} cb
Expand Down
155 changes: 155 additions & 0 deletions helpers/pg-notify.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,155 @@
'use strict';

var Promise = require('bluebird');

// Init global module variables
var db, bus, logger;
// Global connection for permanent event listeners
var connection;
// Map channels to bus.message events
var channels = {
'round-closed': 'finishRound',
'round-reopened': 'finishRound'
};

function onNotification (data) {
logger.debug('pg-notify: Notification received', {channel: data.channel, data: data.payload});

if (!channels[data.channel]) {
// Channel is invalid - should never happen
logger.error('pg-notify: Invalid channel', data.channel);
return;
}

// Process round-releated things
Copy link
Contributor

@karmacoma karmacoma Jul 17, 2017

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Process round-related things

if (data.channel === 'round-closed') {
data.payload = parseInt(data.payload);
logger.info('pg-notify: Round closed', data.payload);
// Set new round
data.payload += 1;
} else if (data.channel === 'round-reopened') {
data.payload = parseInt(data.payload);
logger.warn('pg-notify: Round reopened', data.payload);
} else {
// Channel is not supported - should never happen
logger.error('pg-notify: Channel not supported', data.channel);
return;
}

// Broadcast notify via events
bus.message(channels[data.channel], data.payload);
}

// Generate list of queries for listen to every supported channels
function listenQueries (t) {
var queries = [];
Object.keys(channels).forEach(function (channel) {
queries.push(t.none('LISTEN $1~', channel));
});
return t.batch(queries);
}

function setListeners (client, cb) {
client.on('notification', onNotification);

connection.task(listenQueries)
.then(function () {
return setImmediate(cb);
})
.catch(function (err) {
logger.error('pg-notify: Failed to execute LISTEN queries', err);
return setImmediate(cb, err);
});
}

// Generate list of queries for unlisten to every supported channels
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

// Generate list of unlisten queries for every supported channel

function unlistenQueries (t) {
var queries = [];
Object.keys(channels).forEach(function (channel) {
queries.push(t.none('UNLISTEN $1~', channel));
});
return t.batch(queries);
}

function removeListeners (client, cb) {
client.removeListener('notification', onNotification);

if (connection) {
connection.task(unlistenQueries)
.then(function () {
return setImmediate(cb);
})
.catch(function (err) {
logger.error('pg-notify: Failed to execute UNLISTEN queries', err);
return setImmediate(cb, err);
});
} else {
return setImmediate(cb);
}
}

function onConnectionLost (err, e) {
logger.error('pg-notify: Connection lost', err);
// Prevent use of the connection
connection = null;
// We don't care about error here, so passing empty function as callback
removeListeners(e.client, function () {});
// Try to re-establish connection 10 times, every 5 seconds
reconnect(5000, 10)
.then(function (obj) {
logger.info('pg-notify: Reconnected successfully');
})
.catch(function () {
// Failed after 10 attempts
logger.error('pg-notify: Failed to reconnect - connection lost');
process.exit();
});
}

function reconnect (delay, maxAttempts) {
delay = delay > 0 ? delay : 0;
maxAttempts = maxAttempts > 0 ? maxAttempts : 1;
return new Promise(function (resolve, reject) {
setTimeout(function () {
db.connect({direct: true, onLost: onConnectionLost})
.then(function (obj) {
// Global connection is now available
connection = obj;
setListeners(obj.client, function (err) {
if (err) {
reject(err);
} else {
resolve(obj);
}
});
})
.catch(function (err) {
logger.error('pg-notify: Error connecting', err);
if (--maxAttempts) {
reconnect(delay, maxAttempts)
.then(resolve)
.catch(reject);
} else {
reject(err);
}
});
}, delay);
});
}

module.exports.init = function (_db, _bus, _logger, cb) {
db = _db;
bus = _bus;
logger = _logger;

reconnect ()
.then(function (obj) {
logger.info('pg-notify: Initial connection estabilished');
return setImmediate(cb);
})
.catch(function (err) {
logger.error('pg-notify: Initial connection failed', err);
// Error is passed to callback here, so node will not start in that case
return setImmediate(cb, err);
});
};
64 changes: 2 additions & 62 deletions logic/account.js
Original file line number Diff line number Diff line change
Expand Up @@ -409,7 +409,6 @@ function Account (db, schema, logger, cb) {
/**
* Creates memory tables related to accounts:
* - mem_accounts
* - mem_round
* - mem_accounts2delegates
* - mem_accounts2u_delegates
* - mem_accounts2multisignatures
Expand All @@ -430,7 +429,6 @@ Account.prototype.createTables = function (cb) {

/**
* Deletes the contents of these tables:
* - mem_round
* - mem_accounts2delegates
* - mem_accounts2u_delegates
* - mem_accounts2multisignatures
Expand All @@ -442,7 +440,6 @@ Account.prototype.removeTables = function (cb) {
var sqles = [], sql;

[this.table,
'mem_round',
'mem_accounts2delegates',
'mem_accounts2u_delegates',
'mem_accounts2multisignatures',
Expand Down Expand Up @@ -647,15 +644,13 @@ Account.prototype.set = function (address, fields, cb) {

/**
* Updates account from mem_account with diff data belonging to an editable field.
* Inserts into mem_round "address", "amount", "delegate", "blockId", "round"
* based on field balance or delegates.
* @param {address} address
* @param {Object} diff - Must contains only mem_account editable fields.
* @param {function} cb - Callback function.
* @returns {setImmediateCallback|cb|done} Multiple returns: done() or error.
*/
Account.prototype.merge = function (address, diff, cb) {
var update = {}, remove = {}, insert = {}, insert_object = {}, remove_object = {}, round = [];
var update = {}, remove = {}, insert = {}, insert_object = {}, remove_object = {};

// Verify public key
this.verifyPublicKey(diff.publicKey);
Expand All @@ -679,17 +674,6 @@ Account.prototype.merge = function (address, diff, cb) {
} else if (Math.abs(trueValue) === trueValue && trueValue !== 0) {
update.$inc = update.$inc || {};
update.$inc[value] = Math.floor(trueValue);
if (value === 'balance') {
round.push({
query: 'INSERT INTO mem_round ("address", "amount", "delegate", "blockId", "round") SELECT ${address}, (${amount})::bigint, "dependentId", ${blockId}, ${round} FROM mem_accounts2delegates WHERE "accountId" = ${address};',
values: {
address: address,
amount: trueValue,
blockId: diff.blockId,
round: diff.round
}
});
}
} else if (trueValue < 0) {
update.$dec = update.$dec || {};
update.$dec[value] = Math.floor(Math.abs(trueValue));
Expand All @@ -698,17 +682,6 @@ Account.prototype.merge = function (address, diff, cb) {
// Remove virginity and ensure marked columns become immutable
update.virgin = 0;
}
if (value === 'balance') {
round.push({
query: 'INSERT INTO mem_round ("address", "amount", "delegate", "blockId", "round") SELECT ${address}, (${amount})::bigint, "dependentId", ${blockId}, ${round} FROM mem_accounts2delegates WHERE "accountId" = ${address};',
values: {
address: address,
amount: trueValue,
blockId: diff.blockId,
round: diff.round
}
});
}
}
break;
case Array:
Expand Down Expand Up @@ -737,47 +710,14 @@ Account.prototype.merge = function (address, diff, cb) {
val = trueValue[i].slice(1);
remove[value] = remove[value] || [];
remove[value].push(val);
if (value === 'delegates') {
round.push({
query: 'INSERT INTO mem_round ("address", "amount", "delegate", "blockId", "round") SELECT ${address}, (-balance)::bigint, ${delegate}, ${blockId}, ${round} FROM mem_accounts WHERE address = ${address};',
values: {
address: address,
delegate: val,
blockId: diff.blockId,
round: diff.round
}
});
}
} else if (math === '+') {
val = trueValue[i].slice(1);
insert[value] = insert[value] || [];
insert[value].push(val);
if (value === 'delegates') {
round.push({
query: 'INSERT INTO mem_round ("address", "amount", "delegate", "blockId", "round") SELECT ${address}, (balance)::bigint, ${delegate}, ${blockId}, ${round} FROM mem_accounts WHERE address = ${address};',
values: {
address: address,
delegate: val,
blockId: diff.blockId,
round: diff.round
}
});
}
} else {
val = trueValue[i];
insert[value] = insert[value] || [];
insert[value].push(val);
if (value === 'delegates') {
round.push({
query: 'INSERT INTO mem_round ("address", "amount", "delegate", "blockId", "round") SELECT ${address}, (balance)::bigint, ${delegate}, ${blockId}, ${round} FROM mem_accounts WHERE address = ${address};',
values: {
address: address,
delegate: val,
blockId: diff.blockId,
round: diff.round
}
});
}
}
}
}
Expand Down Expand Up @@ -867,7 +807,7 @@ Account.prototype.merge = function (address, diff, cb) {
}
}

var queries = sqles.concat(round).map(function (sql) {
var queries = sqles.map(function (sql) {
return pgp.as.format(sql.query, sql.values);
}).join('');

Expand Down
12 changes: 8 additions & 4 deletions logic/delegate.js
Original file line number Diff line number Diff line change
Expand Up @@ -309,8 +309,10 @@ Delegate.prototype.dbRead = function (raw) {
Delegate.prototype.dbTable = 'delegates';

Delegate.prototype.dbFields = [
'username',
'transactionId'
'tx_id',
'name',
'pk',
'address'
];

/**
Expand All @@ -323,8 +325,10 @@ Delegate.prototype.dbSave = function (trs) {
table: this.dbTable,
fields: this.dbFields,
values: {
username: trs.asset.delegate.username,
transactionId: trs.id
tx_id: trs.id,
name: trs.asset.delegate.username,
pk: Buffer.from(trs.senderPublicKey, 'hex'),
address: trs.senderId
}
};
};
Expand Down
32 changes: 8 additions & 24 deletions modules/blocks/chain.js
Original file line number Diff line number Diff line change
Expand Up @@ -275,9 +275,7 @@ Chain.prototype.applyGenesisBlock = function (block, cb) {
} else {
// Set genesis block as last block
modules.blocks.lastBlock.set(block);
// Tick round
// WARNING: DB_WRITE
modules.rounds.tick(block, cb);
return cb();
}
});
};
Expand Down Expand Up @@ -460,14 +458,11 @@ Chain.prototype.applyBlock = function (block, broadcast, cb, saveBlock) {
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);
return seriesCb();
});
} else {
library.bus.message('newBlock', block, broadcast);

// DATABASE write. Update delegates accounts
modules.rounds.tick(block, seriesCb);
return seriesCb();
}
},
// Push back unconfirmed transactions list (minus the one that were on the block if applied correctly).
Expand Down Expand Up @@ -549,28 +544,17 @@ __private.popLastBlock = function (oldLastBlock, cb) {
return process.exit(0);
}

// Perform backward tick on rounds
// WARNING: DB_WRITE
modules.rounds.backwardTick(oldLastBlock, previousBlock, function (err) {
// Delete last block from blockchain
// WARNING: Db_WRITE
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

// WARNING: DB_WRITE

self.deleteBlock(oldLastBlock.id, function (err) {
if (err) {
// Fatal error, memory tables will be inconsistent
library.logger.error('Failed to perform backwards tick', err);
library.logger.error('Failed to delete block', err);

return process.exit(0);
}

// Delete last block from blockchain
// WARNING: Db_WRITE
self.deleteBlock(oldLastBlock.id, function (err) {
if (err) {
// Fatal error, memory tables will be inconsistent
library.logger.error('Failed to delete block', err);

return process.exit(0);
}

return setImmediate(cb, null, previousBlock);
});
return setImmediate(cb, null, previousBlock);
});
});
});
Expand Down
2 changes: 1 addition & 1 deletion modules/blocks/process.js
Original file line number Diff line number Diff line change
Expand Up @@ -367,7 +367,7 @@ Process.prototype.onReceiveBlock = function (block) {
library.sequence.add(function (cb) {
// When client is not loaded, is syncing or round is ticking
// Do not receive new blocks as client is not ready
if (!__private.loaded || modules.loader.syncing() || modules.rounds.ticking()) {
if (!__private.loaded || modules.loader.syncing()) {
library.logger.debug('Client not ready to receive block', block.id);
return;
}
Expand Down
Loading