Skip to content
This repository was archived by the owner on Apr 27, 2021. It is now read-only.

Commit 7a91efa

Browse files
authored
Merge pull request #11 from Alethio/ibft2-validators
POA validators
2 parents 2df0b6a + ee5db98 commit 7a91efa

24 files changed

+633
-104
lines changed

.env.sample

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ TZ=UTC
44

55
NETWORK_ID=1
66
NETWORK_NAME=mainnet
7+
NETWORK_ALGO=ethash
78

89
APP_HOST=localhost
910
APP_PORT=3000

CHANGELOG.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,9 @@
11
# Changelog
22
All notable changes to this project will be documented in this file.
33

4+
## [1.5.2] - 2019-06-05
5+
- Add support for validators/signers on POA networks ("ibft2" and "clique" consensus algorithms)
6+
47
## [1.5.1] - 2019-05-22
58
- Add "clientTimeout" message sent to the client when inactive for more then 3 minutes
69

app/controllers/api/BlocksController.js

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import AbstractController from './AbstractController.js';
2+
import BlockUtils from '../../lib/BlockUtils.js';
23

34
export default class BlocksController extends AbstractController {
45
get(request, response) {
@@ -138,7 +139,7 @@ export default class BlocksController extends AbstractController {
138139
this.models.Usage.get({nodeName: nodeName, timestampEnd: confirmationTimestamp, order: 'desc', limit: 1})
139140
];
140141

141-
return Promise.all(allPromises).then(results => {
142+
return Promise.all(allPromises).then(async results => {
142143
let nodeData = {
143144
nodeData: (results[0] && results[0].rowLength > 0) ? results[0].rows[0] : null,
144145
firstLogin: (results[1] && results[1].rowLength > 0) ? results[1].rows[0] : null,
@@ -154,6 +155,17 @@ export default class BlocksController extends AbstractController {
154155
nodeData.lastLogin = nodeData.firstLogin;
155156
}
156157

158+
if (nodeData.lastBlock) {
159+
if (this.appConfig.NETWORK_ALGO === 'ibft2' && nodeData.lastBlock.extraData) {
160+
nodeData.lastBlock.validators = BlockUtils.getIBFT2Validators(nodeData.lastBlock.extraData);
161+
}
162+
163+
if (this.appConfig.NETWORK_ALGO === 'clique') {
164+
let validators = await this.models.Validators.get({blockNumber: nodeData.lastBlock.number, blockHash: nodeData.lastBlock.hash});
165+
nodeData.lastBlock.validators = (validators && validators.rowLength > 0) ? JSON.parse(validators.rows[0].validators) : [];
166+
}
167+
}
168+
157169
nodeData.nodeData.isActive = true;
158170
return this.result.setupNodeData(nodeData, false);
159171
});

app/controllers/consumer/BlocksController.js

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import AbstractController from './AbstractController.js';
2+
import BlockUtils from '../../lib/BlockUtils.js';
23

34
export default class BlocksController extends AbstractController {
45
add(params, callback) {
@@ -206,6 +207,10 @@ export default class BlocksController extends AbstractController {
206207
if (setAsLastBlock) {
207208
this._setLastBlock(newBlockParams);
208209
this._sendLastBlockToDeepstream(newBlockParams);
210+
211+
if (this.appConfig.NETWORK_ALGO === 'ibft2') {
212+
this.dsDataLoader.sendValidatorsToDeepstream(BlockUtils.getIBFT2Validators(params));
213+
}
209214
}
210215

211216
if (sendStatisticsToDeepstream) {

app/controllers/server/AbstractController.js

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -165,4 +165,46 @@ export default class AbstractController {
165165
}
166166
}
167167
}
168+
169+
async getConfig(spark, params) {
170+
let responseObject = this.lodash.cloneDeep(this.responseObject);
171+
let requestValidation = {
172+
request: {
173+
type: 'object',
174+
additionalProperties: false,
175+
properties: {
176+
configName: {type: 'string'}
177+
},
178+
required: ['configName']
179+
}
180+
};
181+
182+
let validParams = this.validator.validate(requestValidation.request, params);
183+
if (!validParams) {
184+
responseObject.success = false;
185+
responseObject.errors = this.validatorError.getReadableErrorMessages(this.validator.errors);
186+
187+
return responseObject;
188+
}
189+
190+
let availableConfigs = ['NETWORK_ALGO'];
191+
192+
if (!availableConfigs.includes(params.configName)) {
193+
responseObject.success = false;
194+
responseObject.errors.push('Config not found');
195+
196+
return responseObject;
197+
}
198+
199+
let session = this.session.getAll(spark.id);
200+
if (session.isLoggedIn === true) {
201+
responseObject.data.push({[params.configName]: this.appConfig[params.configName]});
202+
responseObject.dataLength = responseObject.data.length;
203+
} else {
204+
responseObject.success = false;
205+
responseObject.errors.push('Not logged in');
206+
}
207+
208+
return responseObject;
209+
}
168210
}

app/controllers/server/AuthController.js

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -179,6 +179,10 @@ export default class AuthController extends AbstractController {
179179
nodeUsage: null
180180
};
181181

182+
if (['ibft2', 'clique'].includes(this.appConfig.NETWORK_ALGO)) {
183+
dataToSend.nodeData.isValidator = false;
184+
}
185+
182186
let nodesList = this.deepstream.record.getList(`${this.appConfig.DEEPSTREAM_NAMESPACE}/nodes`);
183187
nodesList.whenReady(list => {
184188
if (list.getEntries().includes(dsNodeId)) {

app/controllers/server/BlocksController.js

Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -294,4 +294,54 @@ export default class BlocksController extends AbstractController {
294294
return responseObject;
295295
});
296296
}
297+
298+
async addValidators(spark, params) {
299+
let responseObject = this.lodash.cloneDeep(this.responseObject);
300+
let requestValidation = {
301+
request: {
302+
type: 'object',
303+
additionalProperties: false,
304+
properties: {
305+
blockNumber: {type: 'integer'},
306+
blockHash: {type: 'string'},
307+
validators: {type: 'array'}
308+
},
309+
required: ['blockNumber', 'blockHash', 'validators']
310+
}
311+
};
312+
313+
let validParams = this.validator.validate(requestValidation.request, params);
314+
if (!validParams) {
315+
responseObject.success = false;
316+
responseObject.errors = this.validatorError.getReadableErrorMessages(this.validator.errors);
317+
318+
return responseObject;
319+
}
320+
321+
let session = this.session.getAll(spark.id);
322+
if (session.isLoggedIn === true) {
323+
this.cache.getVar('lastBlockForValidators').then(lastBlockForValidators => {
324+
lastBlockForValidators = (lastBlockForValidators === null) ? null : JSON.parse(lastBlockForValidators);
325+
if (lastBlockForValidators && ((params.blockNumber - lastBlockForValidators.blockNumber < 0) || (lastBlockForValidators.blockNumber === params.blockNumber && lastBlockForValidators.blockHash === params.blockHash))) {
326+
// do nothing
327+
} else {
328+
this.log.debug(`[${spark.id}] - DB insert validators => ${JSON.stringify(params)}`);
329+
330+
this.models.Validators.add(params).then(() => {
331+
this.dsDataLoader.sendValidatorsToDeepstream(params.validators);
332+
});
333+
334+
this.cache.setVar('lastBlockForValidators', JSON.stringify({
335+
blockNumber: params.blockNumber,
336+
blockHash: params.blockHash
337+
}), this.appConfig.CACHE_LAST_BLOCK_EXPIRE);
338+
}
339+
});
340+
} else {
341+
responseObject.success = false;
342+
responseObject.errors.push('Not logged in');
343+
}
344+
345+
return responseObject;
346+
}
297347
}

app/lib/AppConfig.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,7 @@ export default class AppConfig {
4343

4444
initConfigs(config) {
4545
config.NETWORK_ID = this._convertToInt(config.NETWORK_ID || 1);
46+
config.NETWORK_ALGO = config.NETWORK_ALGO || 'ethash';
4647

4748
config.LOG_SHOW_DATETIME = this._convertToBoolean(config.LOG_SHOW_DATETIME || 1);
4849
config.LOG_SHOW_INFOS = this._convertToBoolean(this.cli.flags.verbose || config.LOG_SHOW_INFOS || 0);

app/lib/BlockUtils.js

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
import * as rlp from 'rlp';
2+
3+
export default class BlockUtils {
4+
static getIBFT2Validators(block) {
5+
let validators = [];
6+
7+
if (block.extraData) {
8+
try {
9+
let decodedExtraData = rlp.decode(block.extraData);
10+
let validatorsRaw = decodedExtraData[1] || [];
11+
12+
validatorsRaw.forEach(validator => {
13+
validators.push('0x' + validator.toString('hex'));
14+
});
15+
} catch (error) {
16+
this.log.error(`Could not decode IBFT2 extraData to get validators for block ${block.number}::${block.hash} => ${error.message}`);
17+
}
18+
}
19+
20+
return validators;
21+
}
22+
}

app/lib/DsDataLoader.js

Lines changed: 34 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,6 @@
1+
import ethonDictionary from './EthonDictionary.js';
2+
import BlockUtils from './BlockUtils.js';
3+
14
export default class DsDataLoader {
25
constructor(diContainer) {
36
this.appConfig = diContainer.appConfig;
@@ -121,8 +124,20 @@ export default class DsDataLoader {
121124
return this.result.setupNodeData(nodeData, true);
122125
}
123126

124-
return this.models.Blocks.getBlock({number: nodeData.lastConfirmations[0].blockNumber, hash: nodeData.lastConfirmations[0].blockHash}).then(lastBlock => {
127+
return this.models.Blocks.getBlock({number: nodeData.lastConfirmations[0].blockNumber, hash: nodeData.lastConfirmations[0].blockHash}).then(async lastBlock => {
125128
nodeData.lastBlock = lastBlock;
129+
130+
if (nodeData.lastBlock) {
131+
if (this.appConfig.NETWORK_ALGO === 'ibft2' && nodeData.lastBlock.extraData) {
132+
nodeData.lastBlock.validators = BlockUtils.getIBFT2Validators(nodeData.lastBlock.extraData);
133+
}
134+
135+
if (this.appConfig.NETWORK_ALGO === 'clique') {
136+
let validators = await this.models.Validators.get({blockNumber: nodeData.lastBlock.number, blockHash: nodeData.lastBlock.hash});
137+
nodeData.lastBlock.validators = (validators && validators.rowLength > 0) ? JSON.parse(validators.rows[0].validators) : [];
138+
}
139+
}
140+
126141
return this.result.setupNodeData(nodeData, true);
127142
});
128143
});
@@ -203,4 +218,22 @@ export default class DsDataLoader {
203218
this.log.debug(`Deepstream record '${recordId}' delete`);
204219
});
205220
}
221+
222+
sendValidatorsToDeepstream(validators) {
223+
this.log.debug(`Deepstream update 'validators': ${JSON.stringify(validators)}`);
224+
225+
let nodesList = this.deepstream.record.getList(`${this.appConfig.DEEPSTREAM_NAMESPACE}/nodes`);
226+
nodesList.whenReady(list => {
227+
list.getEntries().forEach(dsNodeId => {
228+
this.getRecord(`${dsNodeId}/nodeData`).whenReady(node => {
229+
let nodeData = node.get()[ethonDictionary.nodeData];
230+
if (nodeData) {
231+
if (validators.includes(nodeData[ethonDictionary.coinbase])) {
232+
this.setRecord(`${dsNodeId}/nodeData`, 'nodeData.isValidator', true);
233+
}
234+
}
235+
});
236+
});
237+
});
238+
}
206239
}

0 commit comments

Comments
 (0)