From a3d45aa6c4bd97572cb26cf6196cb5043986f4af Mon Sep 17 00:00:00 2001 From: michielmulders Date: Tue, 4 Jun 2019 17:48:10 +0200 Subject: [PATCH 01/25] Solve merge conflicts development --- .../src/components/storage/entities/block.js | 4 ++++ .../src/components/storage/sql/blocks/get.sql | 2 ++ .../components/storage/entities/block.js | 7 +++++- .../src/modules/chain/defaults/config.js | 15 ++++++++++-- framework/src/modules/chain/forger.js | 2 +- ...510143000_add_previous_prevoted_height.sql | 23 +++++++++++++++++++ 6 files changed, 49 insertions(+), 4 deletions(-) create mode 100644 framework/src/modules/chain/migrations/sql/20190510143000_add_previous_prevoted_height.sql diff --git a/framework/src/components/storage/entities/block.js b/framework/src/components/storage/entities/block.js index 3f262a9aa9b..b7f1cdefb61 100644 --- a/framework/src/components/storage/entities/block.js +++ b/framework/src/components/storage/entities/block.js @@ -35,6 +35,8 @@ const sqlFiles = { * @property {string} generatorPublicKey * @property {string} blockSignature * @property {number} height + * @property {number} maxHeightPreviouslyForged + * @property {number} prevotedConfirmedUptoHeight * @property {string} totalFee * @property {string} reward * @property {number} payloadLength @@ -167,6 +169,8 @@ class Block extends BaseEntity { this.addField('rowId', 'number'); this.addField('id', 'string', { filter: filterType.TEXT }); this.addField('height', 'number', { filter: filterType.NUMBER }); + this.addField('maxHeightPreviouslyForged', 'number'); + this.addField('prevotedConfirmedUptoHeight', 'number'); this.addField( 'blockSignature', 'string', diff --git a/framework/src/components/storage/sql/blocks/get.sql b/framework/src/components/storage/sql/blocks/get.sql index af01cc8e280..461a201733a 100644 --- a/framework/src/components/storage/sql/blocks/get.sql +++ b/framework/src/components/storage/sql/blocks/get.sql @@ -21,6 +21,8 @@ SELECT "totalFee", "reward", "payloadLength", + "maxHeightPreviouslyForged", + "prevotedConfirmedUptoHeight", "previousBlock" as "previousBlockId", "numberOfTransactions", "totalAmount", diff --git a/framework/src/modules/chain/components/storage/entities/block.js b/framework/src/modules/chain/components/storage/entities/block.js index ad8920b4296..fd405e755c5 100644 --- a/framework/src/modules/chain/components/storage/entities/block.js +++ b/framework/src/modules/chain/components/storage/entities/block.js @@ -22,10 +22,15 @@ const { errors: { NonSupportedOperationError }, } = require('../../../../../components/storage'); -const defaultCreateValues = {}; +const defaultCreateValues = { + maxHeightPreviouslyForged: null, + prevotedConfirmedUptoHeight: null, +}; const createFields = [ 'id', 'height', + 'maxHeightPreviouslyForged', + 'prevotedConfirmedUptoHeight', 'blockSignature', 'generatorPublicKey', 'payloadHash', diff --git a/framework/src/modules/chain/defaults/config.js b/framework/src/modules/chain/defaults/config.js index e763a7b59ed..2bc99f98b5b 100644 --- a/framework/src/modules/chain/defaults/config.js +++ b/framework/src/modules/chain/defaults/config.js @@ -187,8 +187,15 @@ const defaultConfig = { disableDappTransaction: { type: 'integer', }, + bftUpgradeHeight: { + type: 'integer', + }, }, - required: ['disableDappTransfer', 'disableDappTransaction'], + required: [ + 'disableDappTransfer', + 'disableDappTransaction', + 'bftUpgradeHeight', + ], }, ignoreDelegateListCacheForRounds: { type: 'array', @@ -279,7 +286,11 @@ const defaultConfig = { votes: [], inertTransactions: [], rounds: {}, - precedent: { disableDappTransfer: 0, disableDappTransaction: 0 }, + precedent: { + disableDappTransfer: 0, + disableDappTransaction: 0, + bftUpgradeHeight: 0, + }, ignoreDelegateListCacheForRounds: [], blockVersions: {}, roundVotes: [], diff --git a/framework/src/modules/chain/forger.js b/framework/src/modules/chain/forger.js index 39b8a8c577b..4c7e525dc2d 100644 --- a/framework/src/modules/chain/forger.js +++ b/framework/src/modules/chain/forger.js @@ -296,7 +296,7 @@ class Forger { ); const currentTime = new Date().getTime(); const waitThreshold = this.config.forging.waitThreshold * 1000; - const lastBlock = modules.blocks.lastBlock.get(); + const lastBlock = modules.blocks.lastBlock; // Removed .get() function - Bug or part of refactoring process still? const lastBlockSlot = this.slots.getSlotNumber(lastBlock.timestamp); if (currentSlot === lastBlockSlot) { diff --git a/framework/src/modules/chain/migrations/sql/20190510143000_add_previous_prevoted_height.sql b/framework/src/modules/chain/migrations/sql/20190510143000_add_previous_prevoted_height.sql new file mode 100644 index 00000000000..60ed69eb86f --- /dev/null +++ b/framework/src/modules/chain/migrations/sql/20190510143000_add_previous_prevoted_height.sql @@ -0,0 +1,23 @@ +/* + * Copyright © 2019 Lisk Foundation + * + * See the LICENSE file at the top-level directory of this distribution + * for licensing information. + * + * Unless otherwise agreed in a custom licensing agreement with the Lisk Foundation, + * no part of this software, including this file, may be copied, modified, + * propagated, or distributed except according to the terms contained in the + * LICENSE file. + * + * Removal or modification of this copyright notice is prohibited. + */ + + + /* + DESCRIPTION: Add maxHeightPreviouslyForged and prevotedConfirmedUptoHeight field for blocks column. Both columns are of type 32-bit unsigned integer + PARAMETERS: None +*/ + + -- Add to blocks column +ALTER TABLE "blocks" ADD COLUMN IF NOT EXISTS "maxHeightPreviouslyForged" INT; +ALTER TABLE "blocks" ADD COLUMN IF NOT EXISTS "prevotedConfirmedUptoHeight" INT; From 534414a816f58778b5c3bcb12956731b3bd8ccbb Mon Sep 17 00:00:00 2001 From: michielmulders Date: Wed, 5 Jun 2019 18:01:17 +0200 Subject: [PATCH 02/25] Working implementation for bft modifications --- framework/src/modules/chain/blocks/block.js | 313 +++++++++++++++++- framework/src/modules/chain/blocks/process.js | 3 + .../src/modules/chain/schema/definitions.js | 10 + 3 files changed, 320 insertions(+), 6 deletions(-) diff --git a/framework/src/modules/chain/blocks/block.js b/framework/src/modules/chain/blocks/block.js index e03262334cb..36b5332090d 100644 --- a/framework/src/modules/chain/blocks/block.js +++ b/framework/src/modules/chain/blocks/block.js @@ -30,9 +30,46 @@ const validator = require('../../../controller/validator'); const { validateTransactions } = require('../transactions'); const blockVersion = require('./block_version'); +const bftUpgradeHeight = 2; // TODO: define strategy to inject this variable from chain config + // TODO: remove type constraints const TRANSACTION_TYPES_MULTI = 4; +const create = data => { + console.log('\n\n\n\n\n'); + console.log(data.height); + return data.height > bftUpgradeHeight || bftUpgradeHeight === 0 + ? createBFT(data) + : createLegacy(data); +}; + +const getBytes = block => { + console.log('getBytes'); + return block.version === 2 ? getBytesBFT(block) : getBytesLegacy(block); +}; + +// getBytes { +// 1: function, +// 2; function, +// } +// Call: getBytes[block.version] + +const dbRead = raw => { + console.log('dbRead'); + console.log(raw); + return parseInt(raw.b_height) > bftUpgradeHeight || bftUpgradeHeight === 0 + ? dbReadBFT(raw) + : dbReadLegacy(raw); +}; + +const storageRead = raw => { + console.log('storageRead'); + console.log(raw); + return parseInt(raw.height) > bftUpgradeHeight || bftUpgradeHeight === 0 + ? storageReadBFT(raw) + : storageReadLegacy(raw); +}; + /** * Creates a block signature. * @@ -41,8 +78,10 @@ const TRANSACTION_TYPES_MULTI = 4; * @returns {signature} Block signature * @todo Add description for the params */ -const sign = (block, keypair) => - signDataWithPrivateKey(hash(getBytes(block)), keypair.privateKey); +const sign = (block, keypair) => { + console.log('verifysignature'); + return signDataWithPrivateKey(hash(getBytes(block)), keypair.privateKey); +}; /** * Creates hash based on block bytes. @@ -61,10 +100,84 @@ const getHash = block => hash(getBytes(block)); * @returns {!Array} Contents as an ArrayBuffer * @todo Add description for the function and the params */ -const getBytes = block => { +const getBytesLegacy = block => { + console.log('legacy'); + console.log(block); + const capacity = + 4 + // version (int) + 4 + // timestamp (int) + 8 + // previousBlock + 4 + // numberOfTransactions (int) + 8 + // totalAmount (long) + 8 + // totalFee (long) + 8 + // reward (long) + 4 + // payloadLength (int) + 32 + // payloadHash + 32 + // generatorPublicKey + 64 + // blockSignature or unused + 4; // unused + + const byteBuffer = new ByteBuffer(capacity, true); + byteBuffer.writeInt(block.version); + byteBuffer.writeInt(block.timestamp); + + if (block.previousBlock) { + const pb = new BigNum(block.previousBlock).toBuffer({ size: '8' }); + + for (let i = 0; i < 8; i++) { + byteBuffer.writeByte(pb[i]); + } + } else { + for (let i = 0; i < 8; i++) { + byteBuffer.writeByte(0); + } + } + + byteBuffer.writeInt(block.numberOfTransactions); + byteBuffer.writeLong(block.totalAmount.toString()); + byteBuffer.writeLong(block.totalFee.toString()); + byteBuffer.writeLong(block.reward.toString()); + + byteBuffer.writeInt(block.payloadLength); + + const payloadHashBuffer = hexToBuffer(block.payloadHash); + for (let i = 0; i < payloadHashBuffer.length; i++) { + byteBuffer.writeByte(payloadHashBuffer[i]); + } + + const generatorPublicKeyBuffer = hexToBuffer(block.generatorPublicKey); + for (let i = 0; i < generatorPublicKeyBuffer.length; i++) { + byteBuffer.writeByte(generatorPublicKeyBuffer[i]); + } + + if (block.blockSignature) { + const blockSignatureBuffer = hexToBuffer(block.blockSignature); + for (let i = 0; i < blockSignatureBuffer.length; i++) { + byteBuffer.writeByte(blockSignatureBuffer[i]); + } + } + + byteBuffer.flip(); + return byteBuffer.toBuffer(); +}; + +/** + * Description of the function. + * + * @param {block} block + * @throws {Error} + * @returns {!Array} Contents as an ArrayBuffer + * @todo Add description for the function and the params + */ +const getBytesBFT = block => { + console.log('legacy'); + console.log(block); const capacity = 4 + // version (int) 4 + // timestamp (int) + 4 + // height (int) + 4 + // maxHeightPreviouslyForged (int) + 4 + // prevotedConfirmedUptoHeight (int) 8 + // previousBlock 4 + // numberOfTransactions (int) 8 + // totalAmount (long) @@ -79,6 +192,9 @@ const getBytes = block => { const byteBuffer = new ByteBuffer(capacity, true); byteBuffer.writeInt(block.version); byteBuffer.writeInt(block.timestamp); + byteBuffer.writeInt(block.height); + byteBuffer.writeInt(block.maxHeightPreviouslyForged); + byteBuffer.writeInt(block.prevotedConfirmedUptoHeight); if (block.previousBlock) { const pb = new BigNum(block.previousBlock).toBuffer({ size: '8' }); @@ -160,13 +276,111 @@ const objectNormalize = (block, exceptions = {}) => { * @returns {block} block * @todo Add description for the params */ -const create = ({ +const createLegacy = ({ + blockReward, + transactions, + previousBlock, + keypair, + timestamp, + maxPayloadLength, + exceptions, +}) => { + // TODO: move to transactions module logic + const sortedTransactions = transactions.sort((a, b) => { + // Place MULTI transaction after all other transaction types + if ( + a.type === TRANSACTION_TYPES_MULTI && + b.type !== TRANSACTION_TYPES_MULTI + ) { + return 1; + } + // Place all other transaction types before MULTI transaction + if ( + a.type !== TRANSACTION_TYPES_MULTI && + b.type === TRANSACTION_TYPES_MULTI + ) { + return -1; + } + // Place depending on type (lower first) + if (a.type < b.type) { + return -1; + } + if (a.type > b.type) { + return 1; + } + // Place depending on amount (lower first) + if (a.amount.lt(b.amount)) { + return -1; + } + if (a.amount.gt(b.amount)) { + return 1; + } + return 0; + }); + + const nextHeight = previousBlock ? previousBlock.height + 1 : 1; + + const reward = blockReward.calcReward(nextHeight); + let totalFee = new BigNum(0); + let totalAmount = new BigNum(0); + let size = 0; + + const blockTransactions = []; + const payloadHash = crypto.createHash('sha256'); + + for (let i = 0; i < sortedTransactions.length; i++) { + const transaction = sortedTransactions[i]; + const bytes = transaction.getBytes(transaction); + + if (size + bytes.length > maxPayloadLength) { + break; + } + + size += bytes.length; + + totalFee = totalFee.plus(transaction.fee); + totalAmount = totalAmount.plus(transaction.amount); + + blockTransactions.push(transaction); + payloadHash.update(bytes); + } + + const block = { + version: blockVersion.currentBlockVersion, + totalAmount, + totalFee, + reward, + payloadHash: payloadHash.digest().toString('hex'), + timestamp, + numberOfTransactions: blockTransactions.length, + payloadLength: size, + previousBlock: previousBlock.id, + generatorPublicKey: keypair.publicKey.toString('hex'), + transactions: blockTransactions, + }; + + block.blockSignature = sign(block, keypair); + return objectNormalize(block, exceptions); +}; + +/** + * Sorts input data transactions. + * Calculates reward based on previous block data. + * Generates new block. + * + * @param {Object} data + * @returns {block} block + * @todo Add description for the params + */ +const createBFT = ({ blockReward, transactions, previousBlock, keypair, timestamp, maxPayloadLength, + maxHeightPreviouslyForged, + prevotedConfirmedUptoHeight, exceptions, }) => { // TODO: move to transactions module logic @@ -241,6 +455,9 @@ const create = ({ previousBlock: previousBlock.id, generatorPublicKey: keypair.publicKey.toString('hex'), transactions: blockTransactions, + height: nextHeight, + maxHeightPreviouslyForged: maxHeightPreviouslyForged, + prevotedConfirmedUptoHeight: prevotedConfirmedUptoHeight, }; block.blockSignature = sign(block, keypair); @@ -257,6 +474,7 @@ const create = ({ */ const verifySignature = block => { const signatureLength = 64; + console.log('verifysignature'); const data = getBytes(block); const dataWithoutSignature = Buffer.alloc(data.length - signatureLength); @@ -279,6 +497,7 @@ const verifySignature = block => { * @todo Add description for the params */ const getId = block => { + console.log('getId'); const hashedBlock = hash(getBytes(block)); const temp = Buffer.alloc(8); for (let i = 0; i < 8; i++) { @@ -297,7 +516,39 @@ const getId = block => { * @returns {null|block} Block object * @todo Add description for the params */ -const dbRead = raw => { +const dbReadLegacy = raw => { + if (!raw.b_id) { + return null; + } + const block = { + id: raw.b_id, + version: parseInt(raw.b_version), + timestamp: parseInt(raw.b_timestamp), + height: parseInt(raw.b_height), + previousBlock: raw.b_previousBlock, + numberOfTransactions: parseInt(raw.b_numberOfTransactions), + totalAmount: new BigNum(raw.b_totalAmount), + totalFee: new BigNum(raw.b_totalFee), + reward: new BigNum(raw.b_reward), + payloadLength: parseInt(raw.b_payloadLength), + payloadHash: raw.b_payloadHash, + generatorPublicKey: raw.b_generatorPublicKey, + generatorId: getAddressFromPublicKey(raw.b_generatorPublicKey), + blockSignature: raw.b_blockSignature, + confirmations: parseInt(raw.b_confirmations), + }; + block.totalForged = block.totalFee.plus(block.reward).toString(); + return block; +}; + +/** + * Creates block object based on raw data. + * + * @param {Object} raw + * @returns {null|block} Block object + * @todo Add description for the params + */ +const dbReadBFT = raw => { if (!raw.b_id) { return null; } @@ -306,6 +557,8 @@ const dbRead = raw => { version: parseInt(raw.b_version), timestamp: parseInt(raw.b_timestamp), height: parseInt(raw.b_height), + maxHeightPreviouslyForged: parseInt(raw.b_maxHeightPreviouslyForged), + prevotedConfirmedUptoHeight: parseInt(raw.b_prevotedConfirmedUptoHeight), previousBlock: raw.b_previousBlock, numberOfTransactions: parseInt(raw.b_numberOfTransactions), totalAmount: new BigNum(raw.b_totalAmount), @@ -328,7 +581,7 @@ const dbRead = raw => { * @param {Object} raw Raw database data block object * @returns {null|block} Block object */ -const storageRead = raw => { +const storageReadLegacy = raw => { if (!raw.id) { return null; } @@ -362,6 +615,48 @@ const storageRead = raw => { return block; }; +/** + * Creates block object based on raw database block data. + * + * @param {Object} raw Raw database data block object + * @returns {null|block} Block object + */ +const storageReadBFT = raw => { + if (!raw.id) { + return null; + } + + const block = { + id: raw.id, + version: parseInt(raw.version), + timestamp: parseInt(raw.timestamp), + height: parseInt(raw.height), + maxHeightPreviouslyForged: parseInt(raw.maxHeightPreviouslyForged), + prevotedConfirmedUptoHeight: parseInt(raw.prevotedConfirmedUptoHeight), + previousBlock: raw.previousBlockId, + numberOfTransactions: parseInt(raw.numberOfTransactions), + totalAmount: new BigNum(raw.totalAmount), + totalFee: new BigNum(raw.totalFee), + reward: new BigNum(raw.reward), + payloadLength: parseInt(raw.payloadLength), + payloadHash: raw.payloadHash, + generatorPublicKey: raw.generatorPublicKey, + generatorId: getAddressFromPublicKey(raw.generatorPublicKey), + blockSignature: raw.blockSignature, + confirmations: parseInt(raw.confirmations), + }; + + if (raw.transactions) { + block.transactions = raw.transactions + .filter(tx => !!tx.id) + .map(tx => _.omitBy(tx, _.isNull)); + } + + block.totalForged = block.totalFee.plus(block.reward).toString(); + + return block; +}; + const blockSchema = { type: 'object', properties: { @@ -374,6 +669,12 @@ const blockSchema = { height: { type: 'integer', }, + maxHeightPreviouslyForged: { + type: 'integer', + }, + prevotedConfirmedUptoHeight: { + type: 'integer', + }, blockSignature: { type: 'string', format: 'signature', diff --git a/framework/src/modules/chain/blocks/process.js b/framework/src/modules/chain/blocks/process.js index 7e3d8e74f8e..de966fdbf1b 100644 --- a/framework/src/modules/chain/blocks/process.js +++ b/framework/src/modules/chain/blocks/process.js @@ -124,6 +124,9 @@ class BlocksProcess { maxPayloadLength: this.constants.maxPayloadLength, keypair, timestamp, + prevotedConfirmedUptoHeight: 1, + maxHeightPreviouslyForged: 1, + height: context.blockHeight, }); } diff --git a/framework/src/modules/chain/schema/definitions.js b/framework/src/modules/chain/schema/definitions.js index 8abd1d4a76c..65a2357452f 100644 --- a/framework/src/modules/chain/schema/definitions.js +++ b/framework/src/modules/chain/schema/definitions.js @@ -192,6 +192,16 @@ module.exports = { example: 123, minimum: 1, }, + prevotedConfirmedUptoHeight: { + type: 'integer', + example: 123, + minimum: 0, + }, + maxHeightPreviouslyForged: { + type: 'integer', + example: 123, + minimum: 0, + }, timestamp: { description: 'Unix Timestamp', type: 'integer', From 2280b3ef78fed186dfef4f8cbfac0349796cbb38 Mon Sep 17 00:00:00 2001 From: michielmulders Date: Thu, 6 Jun 2019 17:43:14 +0200 Subject: [PATCH 03/25] Modify block version for switching --- framework/src/modules/chain/blocks/block.js | 100 +++++++----------- .../src/modules/chain/blocks/block_version.js | 40 ++++++- framework/src/modules/chain/blocks/process.js | 3 +- .../src/modules/chain/defaults/config.js | 10 +- 4 files changed, 83 insertions(+), 70 deletions(-) diff --git a/framework/src/modules/chain/blocks/block.js b/framework/src/modules/chain/blocks/block.js index 36b5332090d..9872e21588f 100644 --- a/framework/src/modules/chain/blocks/block.js +++ b/framework/src/modules/chain/blocks/block.js @@ -30,46 +30,9 @@ const validator = require('../../../controller/validator'); const { validateTransactions } = require('../transactions'); const blockVersion = require('./block_version'); -const bftUpgradeHeight = 2; // TODO: define strategy to inject this variable from chain config - // TODO: remove type constraints const TRANSACTION_TYPES_MULTI = 4; -const create = data => { - console.log('\n\n\n\n\n'); - console.log(data.height); - return data.height > bftUpgradeHeight || bftUpgradeHeight === 0 - ? createBFT(data) - : createLegacy(data); -}; - -const getBytes = block => { - console.log('getBytes'); - return block.version === 2 ? getBytesBFT(block) : getBytesLegacy(block); -}; - -// getBytes { -// 1: function, -// 2; function, -// } -// Call: getBytes[block.version] - -const dbRead = raw => { - console.log('dbRead'); - console.log(raw); - return parseInt(raw.b_height) > bftUpgradeHeight || bftUpgradeHeight === 0 - ? dbReadBFT(raw) - : dbReadLegacy(raw); -}; - -const storageRead = raw => { - console.log('storageRead'); - console.log(raw); - return parseInt(raw.height) > bftUpgradeHeight || bftUpgradeHeight === 0 - ? storageReadBFT(raw) - : storageReadLegacy(raw); -}; - /** * Creates a block signature. * @@ -78,10 +41,8 @@ const storageRead = raw => { * @returns {signature} Block signature * @todo Add description for the params */ -const sign = (block, keypair) => { - console.log('verifysignature'); - return signDataWithPrivateKey(hash(getBytes(block)), keypair.privateKey); -}; +const sign = (block, keypair) => + signDataWithPrivateKey(hash(getBytes(block)), keypair.privateKey); /** * Creates hash based on block bytes. @@ -100,9 +61,7 @@ const getHash = block => hash(getBytes(block)); * @returns {!Array} Contents as an ArrayBuffer * @todo Add description for the function and the params */ -const getBytesLegacy = block => { - console.log('legacy'); - console.log(block); +const getBytesV1 = block => { const capacity = 4 + // version (int) 4 + // timestamp (int) @@ -169,9 +128,7 @@ const getBytesLegacy = block => { * @returns {!Array} Contents as an ArrayBuffer * @todo Add description for the function and the params */ -const getBytesBFT = block => { - console.log('legacy'); - console.log(block); +const getBytesV2 = block => { const capacity = 4 + // version (int) 4 + // timestamp (int) @@ -276,7 +233,7 @@ const objectNormalize = (block, exceptions = {}) => { * @returns {block} block * @todo Add description for the params */ -const createLegacy = ({ +const createV1 = ({ blockReward, transactions, previousBlock, @@ -346,7 +303,7 @@ const createLegacy = ({ } const block = { - version: blockVersion.currentBlockVersion, + version: blockVersion.getBlockVersion(nextHeight), totalAmount, totalFee, reward, @@ -372,7 +329,7 @@ const createLegacy = ({ * @returns {block} block * @todo Add description for the params */ -const createBFT = ({ +const createV2 = ({ blockReward, transactions, previousBlock, @@ -444,7 +401,7 @@ const createBFT = ({ } const block = { - version: blockVersion.currentBlockVersion, + version: blockVersion.getBlockVersion(nextHeight), // Was blockVersion.currentBlockVersion + 1 before totalAmount, totalFee, reward, @@ -456,8 +413,8 @@ const createBFT = ({ generatorPublicKey: keypair.publicKey.toString('hex'), transactions: blockTransactions, height: nextHeight, - maxHeightPreviouslyForged: maxHeightPreviouslyForged, - prevotedConfirmedUptoHeight: prevotedConfirmedUptoHeight, + maxHeightPreviouslyForged, + prevotedConfirmedUptoHeight, }; block.blockSignature = sign(block, keypair); @@ -474,7 +431,6 @@ const createBFT = ({ */ const verifySignature = block => { const signatureLength = 64; - console.log('verifysignature'); const data = getBytes(block); const dataWithoutSignature = Buffer.alloc(data.length - signatureLength); @@ -497,7 +453,6 @@ const verifySignature = block => { * @todo Add description for the params */ const getId = block => { - console.log('getId'); const hashedBlock = hash(getBytes(block)); const temp = Buffer.alloc(8); for (let i = 0; i < 8; i++) { @@ -516,7 +471,7 @@ const getId = block => { * @returns {null|block} Block object * @todo Add description for the params */ -const dbReadLegacy = raw => { +const dbReadV1 = raw => { if (!raw.b_id) { return null; } @@ -548,7 +503,7 @@ const dbReadLegacy = raw => { * @returns {null|block} Block object * @todo Add description for the params */ -const dbReadBFT = raw => { +const dbReadV2 = raw => { if (!raw.b_id) { return null; } @@ -581,7 +536,7 @@ const dbReadBFT = raw => { * @param {Object} raw Raw database data block object * @returns {null|block} Block object */ -const storageReadLegacy = raw => { +const storageReadV0 = raw => { if (!raw.id) { return null; } @@ -615,13 +570,15 @@ const storageReadLegacy = raw => { return block; }; +const storageReadV1 = raw => storageReadV0(raw); + /** * Creates block object based on raw database block data. * * @param {Object} raw Raw database data block object * @returns {null|block} Block object */ -const storageReadBFT = raw => { +const storageReadV2 = raw => { if (!raw.id) { return null; } @@ -738,6 +695,31 @@ const blockSchema = { ], }; +const createFunc = { + 1: createV1, + 2: createV2, +}; +const create = data => createFunc[data.version](data); + +const getBytesFunc = { + 1: getBytesV1, + 2: getBytesV2, +}; +const getBytes = block => getBytesFunc[block.version](block); + +const dbReadFunc = { + 1: dbReadV1, + 2: dbReadV2, +}; +const dbRead = raw => dbReadFunc[raw.b_version](raw); + +const storageReadFunc = { + 0: storageReadV0, + 1: storageReadV1, + 2: storageReadV2, +}; +const storageRead = raw => storageReadFunc[raw.version](raw); + module.exports = { sign, getHash, diff --git a/framework/src/modules/chain/blocks/block_version.js b/framework/src/modules/chain/blocks/block_version.js index 569099ee88f..c084eb186c4 100644 --- a/framework/src/modules/chain/blocks/block_version.js +++ b/framework/src/modules/chain/blocks/block_version.js @@ -19,7 +19,20 @@ * * @property {number} currentBlockVersion - Current block version used for forging and verify */ -const currentBlockVersion = 1; +const currentBlockVersion = 2; + +const exceptions = { + blockVersions: { + '1': { + start: 1, + end: 4, + }, + '2': { + start: 5, + end: 6901027, + }, + }, +}; // TODO BFT: define strategy to inject this variable from chain config /** * Checks if block version is valid - if match current version or there is an exception for provided block height. @@ -49,7 +62,32 @@ const isValid = (version, height, exceptions = {}) => { return Number(exceptionVersion) === version; }; +const getBlockVersion = height => { + if (!exceptions.blockVersions) { + return -1; + } + + const exceptionVersion = Object.keys(exceptions.blockVersions).find( + exception => { + // Get height range of current exceptions + const heightsRange = exceptions.blockVersions[exception]; + // Check if provided height is between the range boundaries + return height >= heightsRange.start && height <= heightsRange.end + ? exception + : false; + } + ); + + if (exceptionVersion === undefined) { + // If there is no exception for provided height return currentBlockVersion + return currentBlockVersion; + } + + return Number(exceptionVersion); +}; + module.exports = { isValid, currentBlockVersion, + getBlockVersion, }; diff --git a/framework/src/modules/chain/blocks/process.js b/framework/src/modules/chain/blocks/process.js index de966fdbf1b..bd7ff340244 100644 --- a/framework/src/modules/chain/blocks/process.js +++ b/framework/src/modules/chain/blocks/process.js @@ -92,7 +92,7 @@ class BlocksProcess { const context = { blockTimestamp: timestamp, blockHeight: lastBlock.height + 1, - blockVersion: blockVersion.currentBlockVersion, + blockVersion: blockVersion.getBlockVersion(lastBlock.height + 1), }; const allowedTransactionsIds = transactionsModule @@ -127,6 +127,7 @@ class BlocksProcess { prevotedConfirmedUptoHeight: 1, maxHeightPreviouslyForged: 1, height: context.blockHeight, + version: context.blockVersion, }); } diff --git a/framework/src/modules/chain/defaults/config.js b/framework/src/modules/chain/defaults/config.js index 2bc99f98b5b..a29cd1c7e28 100644 --- a/framework/src/modules/chain/defaults/config.js +++ b/framework/src/modules/chain/defaults/config.js @@ -187,15 +187,8 @@ const defaultConfig = { disableDappTransaction: { type: 'integer', }, - bftUpgradeHeight: { - type: 'integer', - }, }, - required: [ - 'disableDappTransfer', - 'disableDappTransaction', - 'bftUpgradeHeight', - ], + required: ['disableDappTransfer', 'disableDappTransaction'], }, ignoreDelegateListCacheForRounds: { type: 'array', @@ -289,7 +282,6 @@ const defaultConfig = { precedent: { disableDappTransfer: 0, disableDappTransaction: 0, - bftUpgradeHeight: 0, }, ignoreDelegateListCacheForRounds: [], blockVersions: {}, From 75b1b073841910cb5a0b027cd0311229b714b90c Mon Sep 17 00:00:00 2001 From: michielmulders Date: Fri, 7 Jun 2019 14:51:26 +0200 Subject: [PATCH 04/25] Add tests block_version --- .../modules/chain/blocks/block_version.js | 77 ++++++++++++++++--- 1 file changed, 66 insertions(+), 11 deletions(-) diff --git a/framework/test/mocha/unit/modules/chain/blocks/block_version.js b/framework/test/mocha/unit/modules/chain/blocks/block_version.js index e68cddf51be..77cd65f9c81 100644 --- a/framework/test/mocha/unit/modules/chain/blocks/block_version.js +++ b/framework/test/mocha/unit/modules/chain/blocks/block_version.js @@ -25,6 +25,61 @@ describe('block_version', () => { }; }); + describe('getBlockVersion', () => { + describe('when no exceptions present', () => { + it('should return 2 for height = undefined', async () => { + const height = undefined; + + return expect(blockVersion.getBlockVersion(height)).to.equal(2); + }); + + it('should return 2 for height = 101', async () => { + const height = 101; + + return expect(blockVersion.getBlockVersion(height)).to.equal(2); + }); + }); + + describe('when exceptions present', () => { + // Two tests fail because currently exceptions are hardcoded + beforeEach(async () => { + exceptions = { + blockVersions: { + 0: { start: 1, end: 101 }, + 1: { start: 102, end: 202 }, + 2: { start: 203, end: 303 }, + }, + }; + }); + + it('should return 2 for height = undefined', async () => { + const height = undefined; + + return expect(blockVersion.getBlockVersion(height)).to.equal(2); + }); + + // eslint-disable-next-line + it.skip('should return 0 for height = 1', async () => { + const height = 1; + + return expect(blockVersion.getBlockVersion(height)).to.equal(0); + }); + + // eslint-disable-next-line + it.skip('should return 1 for height = 102', async () => { + const height = 102; + + return expect(blockVersion.getBlockVersion(height)).to.equal(1); + }); + + it('should return 2 for height = 999999', async () => { + const height = 999999; + + return expect(blockVersion.getBlockVersion(height)).to.equal(2); + }); + }); + }); + describe('isValid', () => { describe('when no exceptions present', () => { // When no exceptions are present current version (1) should be always valid for all heights, @@ -36,21 +91,21 @@ describe('block_version', () => { return expect( blockVersion.isValid(version, height, exceptions) - ).to.equal(true); + ).to.equal(false); }); - it('should return true for version = 1, height = 1', async () => { + it('should return true for version = 2, height = 1', async () => { const height = 1; - const version = 1; + const version = 2; return expect( blockVersion.isValid(version, height, exceptions) ).to.equal(true); }); - it('should return true for version = 1, height = 101', async () => { + it('should return true for version = 2, height = 101', async () => { const height = 101; - const version = 1; + const version = 2; return expect( blockVersion.isValid(version, height, exceptions) @@ -159,18 +214,18 @@ describe('block_version', () => { ).to.equal(false); }); - it('should return true for version = 1, height = 102', async () => { + it('should return true for version = 2, height = 102', async () => { const height = 102; - const version = 1; + const version = 2; return expect( blockVersion.isValid(version, height, exceptions) ).to.equal(true); }); - it('should return true for version = 1, height = 202', async () => { + it('should return true for version = 2, height = 202', async () => { const height = 202; - const version = 1; + const version = 2; return expect( blockVersion.isValid(version, height, exceptions) @@ -195,9 +250,9 @@ describe('block_version', () => { ).to.equal(false); }); - it('should return false for version = 2, height = 102', async () => { + it('should return false for version = 1, height = 102', async () => { const height = 102; - const version = 2; + const version = 1; return expect( blockVersion.isValid(version, height, exceptions) From dd2282ae099aa73367aae43dc1053d398f8a7a29 Mon Sep 17 00:00:00 2001 From: michielmulders Date: Tue, 11 Jun 2019 15:53:35 +0200 Subject: [PATCH 05/25] Update tests blocks/block.js --- framework/src/modules/chain/blocks/block.js | 10 +- .../mocha/unit/modules/chain/blocks/block.js | 218 +++++++++++++----- 2 files changed, 162 insertions(+), 66 deletions(-) diff --git a/framework/src/modules/chain/blocks/block.js b/framework/src/modules/chain/blocks/block.js index 9872e21588f..108ade59860 100644 --- a/framework/src/modules/chain/blocks/block.js +++ b/framework/src/modules/chain/blocks/block.js @@ -61,7 +61,7 @@ const getHash = block => hash(getBytes(block)); * @returns {!Array} Contents as an ArrayBuffer * @todo Add description for the function and the params */ -const getBytesV1 = block => { +const getBytesV0 = block => { const capacity = 4 + // version (int) 4 + // timestamp (int) @@ -120,6 +120,8 @@ const getBytesV1 = block => { return byteBuffer.toBuffer(); }; +const getBytesV1 = block => getBytesV0(block); + /** * Description of the function. * @@ -471,7 +473,7 @@ const getId = block => { * @returns {null|block} Block object * @todo Add description for the params */ -const dbReadV1 = raw => { +const dbReadV0 = raw => { if (!raw.b_id) { return null; } @@ -496,6 +498,8 @@ const dbReadV1 = raw => { return block; }; +const dbReadV1 = raw => dbReadV0(raw); + /** * Creates block object based on raw data. * @@ -702,12 +706,14 @@ const createFunc = { const create = data => createFunc[data.version](data); const getBytesFunc = { + 0: getBytesV0, 1: getBytesV1, 2: getBytesV2, }; const getBytes = block => getBytesFunc[block.version](block); const dbReadFunc = { + 0: dbReadV0, 1: dbReadV1, 2: dbReadV2, }; diff --git a/framework/test/mocha/unit/modules/chain/blocks/block.js b/framework/test/mocha/unit/modules/chain/blocks/block.js index 58b7cd6edc1..fa0dab520e2 100644 --- a/framework/test/mocha/unit/modules/chain/blocks/block.js +++ b/framework/test/mocha/unit/modules/chain/blocks/block.js @@ -100,6 +100,11 @@ describe('block', () => { }; const blockData = validDataForBlock.previousBlock; + const blockDataV2 = { + ...blockData, + maxHeightPreviouslyForged: 1, + prevotedConfirmedUptoHeight: 1, + }; const transactionsByTypes = {}; transactionsByTypes[TRANSACTION_TYPES_MULTI] = { @@ -449,28 +454,57 @@ describe('block', () => { }); describe('getBytes', () => { - it('should throw error for invalid block', async () => - expect(() => { - block.getBytes(invalidBlock); - }).to.throw()); - - it('should return a buffer for a given block', async () => - expect(block.getBytes(blockData)).to.be.an.instanceof(Buffer)); + describe('getBytesV0-1', () => { + it('should throw error for invalid block', async () => + expect(() => { + block.getBytes(invalidBlock); + }).to.throw()); + + it('should return a buffer for a given block', async () => + expect(block.getBytes(blockData)).to.be.an.instanceof(Buffer)); + + it('should return same bytes for a given block', async () => { + const bytes1 = block.getBytes(blockData); + const bytes2 = block.getBytes(blockData); + return expect(bytes1).to.deep.equal(bytes2); + }); - it('should return same bytes for a given block', async () => { - const bytes1 = block.getBytes(blockData); - const bytes2 = block.getBytes(blockData); - return expect(bytes1).to.deep.equal(bytes2); + it('should return different bytes for different blocks', async () => { + const bytes1 = block.getBytes(blockData); + const blockDataCopy = Object.assign({}, blockData); + blockDataCopy.height = 100; + blockDataCopy.generatorPublicKey = + '7e632b62d6230bfc15763f06bf82f7e20cf06a2d8a356850e0bdab30db3506cc'; + const bytes2 = block.getBytes(blockDataCopy); + return expect(bytes1).to.not.deep.equal(bytes2); + }); }); - it('should return different bytes for different blocks', async () => { - const bytes1 = block.getBytes(blockData); - const blockDataCopy = Object.assign({}, blockData); - blockDataCopy.height = 100; - blockDataCopy.generatorPublicKey = - '7e632b62d6230bfc15763f06bf82f7e20cf06a2d8a356850e0bdab30db3506cc'; - const bytes2 = block.getBytes(blockDataCopy); - return expect(bytes1).to.not.deep.equal(bytes2); + describe('getBytesV2', () => { + it('should throw error for invalid block', async () => + expect(() => { + const invalidBlockV2 = { ...invalidBlock, version: 2 }; + block.getBytes(invalidBlockV2); + }).to.throw()); + + it('should return a buffer for a given block', async () => + expect(block.getBytes(blockDataV2)).to.be.an.instanceof(Buffer)); + + it('should return same bytes for a given block', async () => { + const bytes1 = block.getBytes(blockDataV2); + const bytes2 = block.getBytes(blockDataV2); + return expect(bytes1).to.deep.equal(bytes2); + }); + + it('should return different bytes for different blocks', async () => { + const bytes1 = block.getBytes(blockDataV2); + const blockDataCopy = Object.assign({}, blockDataV2); + blockDataCopy.height = 100; + blockDataCopy.generatorPublicKey = + '7e632b62d6230bfc15763f06bf82f7e20cf06a2d8a356850e0bdab30db3506cc'; + const bytes2 = block.getBytes(blockDataCopy); + return expect(bytes1).to.not.deep.equal(bytes2); + }); }); }); @@ -576,52 +610,108 @@ describe('block', () => { }); describe('dbRead', () => { - it('should throw error for null values', async () => - expect(() => { - block.dbRead(null); - }).to.throw()); - - it('should return raw block data', async () => { - const rawBlock = { - b_version: 0, - b_totalAmount: 0, - b_totalFee: 0, - b_reward: 0, - b_payloadHash: - 'e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855', - b_timestamp: 41898490, - b_numberOfTransactions: 0, - b_payloadLength: 0, - b_previousBlock: '1087874036928524397', - b_generatorPublicKey: - '1cc68fa0b12521158e09779fd5978ccc0ac26bf99320e00a9549b542dd9ada16', - b_transactions: [], - b_blockSignature: - '8a727cc77864b6fc81755a1f4eb4796b68f4a943d69c74a043b5ca422f3b05608a22da4a916ca7b721d096129938b6eb3381d75f1a116484d1ce2be4904d9a0e', - b_height: 69, - b_id: '3920300554926889269', - b_relays: 1, - b_confirmations: 0, - }; + describe('dbRead V1', () => { + it('should throw error for null values', async () => + expect(() => { + block.dbRead(null); + }).to.throw("Cannot read property 'b_version' of null")); + + it('should return raw block data', async () => { + const rawBlock = { + b_version: 0, + b_totalAmount: 0, + b_totalFee: 0, + b_reward: 0, + b_payloadHash: + 'e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855', + b_timestamp: 41898490, + b_numberOfTransactions: 0, + b_payloadLength: 0, + b_previousBlock: '1087874036928524397', + b_generatorPublicKey: + '1cc68fa0b12521158e09779fd5978ccc0ac26bf99320e00a9549b542dd9ada16', + b_transactions: [], + b_blockSignature: + '8a727cc77864b6fc81755a1f4eb4796b68f4a943d69c74a043b5ca422f3b05608a22da4a916ca7b721d096129938b6eb3381d75f1a116484d1ce2be4904d9a0e', + b_height: 69, + b_id: '3920300554926889269', + b_relays: 1, + b_confirmations: 0, + }; + + return expect(block.dbRead(rawBlock)).to.contain.keys( + 'id', + 'version', + 'timestamp', + 'height', + 'previousBlock', + 'numberOfTransactions', + 'totalAmount', + 'totalFee', + 'reward', + 'payloadLength', + 'payloadHash', + 'generatorPublicKey', + 'generatorId', + 'blockSignature', + 'confirmations', + 'totalForged' + ); + }); + }); - return expect(block.dbRead(rawBlock)).to.contain.keys( - 'id', - 'version', - 'timestamp', - 'height', - 'previousBlock', - 'numberOfTransactions', - 'totalAmount', - 'totalFee', - 'reward', - 'payloadLength', - 'payloadHash', - 'generatorPublicKey', - 'generatorId', - 'blockSignature', - 'confirmations', - 'totalForged' - ); + describe('dbRead V2', () => { + it('should throw error for null values', async () => + expect(() => { + block.dbRead(null); + }).to.throw("Cannot read property 'b_version' of null")); + + it('should return raw block data', async () => { + const rawBlock = { + b_version: 2, + b_totalAmount: 0, + b_totalFee: 0, + b_reward: 0, + b_payloadHash: + 'e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855', + b_timestamp: 41898490, + b_numberOfTransactions: 0, + b_payloadLength: 0, + b_previousBlock: '1087874036928524397', + b_generatorPublicKey: + '1cc68fa0b12521158e09779fd5978ccc0ac26bf99320e00a9549b542dd9ada16', + b_transactions: [], + b_blockSignature: + '8a727cc77864b6fc81755a1f4eb4796b68f4a943d69c74a043b5ca422f3b05608a22da4a916ca7b721d096129938b6eb3381d75f1a116484d1ce2be4904d9a0e', + b_height: 69, + b_maxHeightPreviouslyForged: 1, + b_prevotedConfirmedUptoHeight: 1, + b_id: '3920300554926889269', + b_relays: 1, + b_confirmations: 0, + }; + + return expect(block.dbRead(rawBlock)).to.contain.keys( + 'id', + 'version', + 'timestamp', + 'height', + 'maxHeightPreviouslyForged', + 'prevotedConfirmedUptoHeight', + 'previousBlock', + 'numberOfTransactions', + 'totalAmount', + 'totalFee', + 'reward', + 'payloadLength', + 'payloadHash', + 'generatorPublicKey', + 'generatorId', + 'blockSignature', + 'confirmations', + 'totalForged' + ); + }); }); }); }); From 5a74c37d6fab5dd7ede1e57c7d56d93a12034b59 Mon Sep 17 00:00:00 2001 From: michielmulders Date: Wed, 12 Jun 2019 10:16:54 +0200 Subject: [PATCH 06/25] Finish tests for create block --- framework/src/modules/chain/blocks/block.js | 2 +- .../src/modules/chain/blocks/block_version.js | 4 +- .../mocha/unit/modules/chain/blocks/block.js | 71 ++++++++++++++++++- 3 files changed, 71 insertions(+), 6 deletions(-) diff --git a/framework/src/modules/chain/blocks/block.js b/framework/src/modules/chain/blocks/block.js index 108ade59860..9ae9803190a 100644 --- a/framework/src/modules/chain/blocks/block.js +++ b/framework/src/modules/chain/blocks/block.js @@ -403,7 +403,7 @@ const createV2 = ({ } const block = { - version: blockVersion.getBlockVersion(nextHeight), // Was blockVersion.currentBlockVersion + 1 before + version: blockVersion.getBlockVersion(nextHeight), totalAmount, totalFee, reward, diff --git a/framework/src/modules/chain/blocks/block_version.js b/framework/src/modules/chain/blocks/block_version.js index c084eb186c4..b52161c3161 100644 --- a/framework/src/modules/chain/blocks/block_version.js +++ b/framework/src/modules/chain/blocks/block_version.js @@ -25,10 +25,10 @@ const exceptions = { blockVersions: { '1': { start: 1, - end: 4, + end: 7, }, '2': { - start: 5, + start: 8, end: 6901027, }, }, diff --git a/framework/test/mocha/unit/modules/chain/blocks/block.js b/framework/test/mocha/unit/modules/chain/blocks/block.js index fa0dab520e2..bfbaefeee4f 100644 --- a/framework/test/mocha/unit/modules/chain/blocks/block.js +++ b/framework/test/mocha/unit/modules/chain/blocks/block.js @@ -85,7 +85,35 @@ describe('block', () => { transactions: [], blockSignature: '8a727cc77864b6fc81755a1f4eb4796b68f4a943d69c74a043b5ca422f3b05608a22da4a916ca7b721d096129938b6eb3381d75f1a116484d1ce2be4904d9a0e', - height: 69, + height: 6, + id: '3920300554926889269', + relays: 1, + }, + transactions: [], + }; + + const validDataForBlockv2 = { + maxPayloadLength, + blockReward, + keypair: validKeypair, + timestamp: 41898500, + previousBlock: { + version: 2, + totalAmount: '0', + totalFee: '0', + reward: '0', + payloadHash: + 'e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855', + timestamp: 41898490, + numberOfTransactions: 0, + payloadLength: 0, + previousBlock: '1087874036928524397', + generatorPublicKey: + '1cc68fa0b12521158e09779fd5978ccc0ac26bf99320e00a9549b542dd9ada16', + transactions: [], + blockSignature: + '8a727cc77864b6fc81755a1f4eb4796b68f4a943d69c74a043b5ca422f3b05608a22da4a916ca7b721d096129938b6eb3381d75f1a116484d1ce2be4904d9a0e', + height: 99, id: '3920300554926889269', relays: 1, }, @@ -291,6 +319,38 @@ describe('block', () => { }); describe('create', () => { + describe('createV1', () => { + it("shouldn't have maxHeightPreviouslyForged and prevotedConfirmedUptoHeight properties", async () => { + const generatedBlock = block.create({ + ...data, + height: 99, + transactions, + version: 2, + }); + + expect(generatedBlock).to.not.have.property( + 'maxHeightPreviouslyForged' + ); + return expect(generatedBlock).to.not.have.property( + 'prevotedConfirmedUptoHeight' + ); + }); + }); + + describe('createV2', () => { + it('should have maxHeightPreviouslyForged and prevotedConfirmedUptoHeight properties', async () => { + const generatedBlock = block.create({ + ..._.cloneDeep(validDataForBlockv2), + transactions, + version: 2, + maxHeightPreviouslyForged: 1, + prevotedConfirmedUptoHeight: 1, + }); + expect(generatedBlock.maxHeightPreviouslyForged).to.eql(1); + return expect(generatedBlock.prevotedConfirmedUptoHeight).to.eql(1); + }); + }); + describe('when each of all supported', () => { let generatedBlock; let transactionsOrder; @@ -300,6 +360,7 @@ describe('block', () => { generatedBlock = block.create({ ...data, transactions, + version: 1, }); transactionsOrder = generatedBlock.transactions.map(trs => trs.type); done(); @@ -328,6 +389,7 @@ describe('block', () => { generatedBlock = block.create({ ...data, transactions: multipleMultisigTx.concat(transactions), + version: 1, }); transactionsOrder = generatedBlock.transactions.map(trs => trs.type); }); @@ -356,6 +418,7 @@ describe('block', () => { generatedBlock = block.create({ ...data, transactions, + version: 1, }); transactionsOrder = generatedBlock.transactions.map(trs => trs.type); }); @@ -382,6 +445,7 @@ describe('block', () => { generatedBlock = block.create({ ...data, transactions: transactions.concat(multipleMultisigTx), + version: 1, }); transactionsOrder = generatedBlock.transactions.map(trs => trs.type); }); @@ -409,6 +473,7 @@ describe('block', () => { generatedBlock = block.create({ ...data, transactions: _.shuffle(transactions.concat(multipleMultisigTx)), + version: 1, }); transactionsOrder = generatedBlock.transactions.map(trs => trs.type); }); @@ -633,7 +698,7 @@ describe('block', () => { b_transactions: [], b_blockSignature: '8a727cc77864b6fc81755a1f4eb4796b68f4a943d69c74a043b5ca422f3b05608a22da4a916ca7b721d096129938b6eb3381d75f1a116484d1ce2be4904d9a0e', - b_height: 69, + b_height: 6, b_id: '3920300554926889269', b_relays: 1, b_confirmations: 0, @@ -683,7 +748,7 @@ describe('block', () => { b_transactions: [], b_blockSignature: '8a727cc77864b6fc81755a1f4eb4796b68f4a943d69c74a043b5ca422f3b05608a22da4a916ca7b721d096129938b6eb3381d75f1a116484d1ce2be4904d9a0e', - b_height: 69, + b_height: 6, b_maxHeightPreviouslyForged: 1, b_prevotedConfirmedUptoHeight: 1, b_id: '3920300554926889269', From 5508bca6b49def3bb2da1faa87be63855b61f5c5 Mon Sep 17 00:00:00 2001 From: michielmulders Date: Thu, 13 Jun 2019 16:42:21 +0200 Subject: [PATCH 07/25] Insert exceptions and modify tests accordingly for block and block_version --- framework/src/modules/chain/blocks/block.js | 6 ++--- .../src/modules/chain/blocks/block_version.js | 19 +++----------- framework/src/modules/chain/blocks/process.js | 5 +++- .../mocha/unit/modules/chain/blocks/block.js | 25 ++++++++++++++++++- .../modules/chain/blocks/block_version.js | 23 ++++++++++------- 5 files changed, 48 insertions(+), 30 deletions(-) diff --git a/framework/src/modules/chain/blocks/block.js b/framework/src/modules/chain/blocks/block.js index 40328a8a8b4..6fbe242e363 100644 --- a/framework/src/modules/chain/blocks/block.js +++ b/framework/src/modules/chain/blocks/block.js @@ -305,7 +305,7 @@ const createV1 = ({ } const block = { - version: blockVersion.getBlockVersion(nextHeight), + version: blockVersion.getBlockVersion(nextHeight, exceptions), totalAmount, totalFee, reward, @@ -377,7 +377,7 @@ const createV2 = ({ const nextHeight = previousBlock ? previousBlock.height + 1 : 1; - const reward = blockReward.calcReward(nextHeight); + const reward = blockReward.calculateReward(nextHeight); let totalFee = new BigNum(0); let totalAmount = new BigNum(0); let size = 0; @@ -403,7 +403,7 @@ const createV2 = ({ } const block = { - version: blockVersion.getBlockVersion(nextHeight), + version: blockVersion.getBlockVersion(nextHeight, exceptions), totalAmount, totalFee, reward, diff --git a/framework/src/modules/chain/blocks/block_version.js b/framework/src/modules/chain/blocks/block_version.js index b52161c3161..7e66a847b06 100644 --- a/framework/src/modules/chain/blocks/block_version.js +++ b/framework/src/modules/chain/blocks/block_version.js @@ -21,19 +21,6 @@ */ const currentBlockVersion = 2; -const exceptions = { - blockVersions: { - '1': { - start: 1, - end: 7, - }, - '2': { - start: 8, - end: 6901027, - }, - }, -}; // TODO BFT: define strategy to inject this variable from chain config - /** * Checks if block version is valid - if match current version or there is an exception for provided block height. * @@ -62,9 +49,9 @@ const isValid = (version, height, exceptions = {}) => { return Number(exceptionVersion) === version; }; -const getBlockVersion = height => { - if (!exceptions.blockVersions) { - return -1; +const getBlockVersion = (height, exceptions = {}) => { + if (height === undefined || Object.entries(exceptions).length === 0) { + return currentBlockVersion; } const exceptionVersion = Object.keys(exceptions.blockVersions).find( diff --git a/framework/src/modules/chain/blocks/process.js b/framework/src/modules/chain/blocks/process.js index bd7ff340244..4620aae72d5 100644 --- a/framework/src/modules/chain/blocks/process.js +++ b/framework/src/modules/chain/blocks/process.js @@ -92,7 +92,10 @@ class BlocksProcess { const context = { blockTimestamp: timestamp, blockHeight: lastBlock.height + 1, - blockVersion: blockVersion.getBlockVersion(lastBlock.height + 1), + blockVersion: blockVersion.getBlockVersion( + lastBlock.height + 1, + this.exceptions + ), }; const allowedTransactionsIds = transactionsModule diff --git a/framework/test/mocha/unit/modules/chain/blocks/block.js b/framework/test/mocha/unit/modules/chain/blocks/block.js index 74f4e5108fe..dc099ba4678 100644 --- a/framework/test/mocha/unit/modules/chain/blocks/block.js +++ b/framework/test/mocha/unit/modules/chain/blocks/block.js @@ -98,6 +98,18 @@ describe('block', () => { relays: 1, }, transactions: [], + exceptions: { + blockVersions: { + 1: { + start: 1, + end: 7, + }, + 2: { + start: 8, + end: 6901027, + }, + }, + }, }; const validDataForBlockv2 = { @@ -126,6 +138,18 @@ describe('block', () => { relays: 1, }, transactions: [], + exceptions: { + blockVersions: { + 1: { + start: 1, + end: 7, + }, + 2: { + start: 8, + end: 6901027, + }, + }, + }, }; const invalidBlock = { @@ -331,7 +355,6 @@ describe('block', () => { it("shouldn't have maxHeightPreviouslyForged and prevotedConfirmedUptoHeight properties", async () => { const generatedBlock = block.create({ ...data, - height: 99, transactions, version: 2, }); diff --git a/framework/test/mocha/unit/modules/chain/blocks/block_version.js b/framework/test/mocha/unit/modules/chain/blocks/block_version.js index 77cd65f9c81..07163170f64 100644 --- a/framework/test/mocha/unit/modules/chain/blocks/block_version.js +++ b/framework/test/mocha/unit/modules/chain/blocks/block_version.js @@ -41,7 +41,6 @@ describe('block_version', () => { }); describe('when exceptions present', () => { - // Two tests fail because currently exceptions are hardcoded beforeEach(async () => { exceptions = { blockVersions: { @@ -55,27 +54,33 @@ describe('block_version', () => { it('should return 2 for height = undefined', async () => { const height = undefined; - return expect(blockVersion.getBlockVersion(height)).to.equal(2); + return expect( + blockVersion.getBlockVersion(height, exceptions) + ).to.equal(2); }); - // eslint-disable-next-line - it.skip('should return 0 for height = 1', async () => { + it('should return 0 for height = 1', async () => { const height = 1; - return expect(blockVersion.getBlockVersion(height)).to.equal(0); + return expect( + blockVersion.getBlockVersion(height, exceptions) + ).to.equal(0); }); - // eslint-disable-next-line - it.skip('should return 1 for height = 102', async () => { + it('should return 1 for height = 102', async () => { const height = 102; - return expect(blockVersion.getBlockVersion(height)).to.equal(1); + return expect( + blockVersion.getBlockVersion(height, exceptions) + ).to.equal(1); }); it('should return 2 for height = 999999', async () => { const height = 999999; - return expect(blockVersion.getBlockVersion(height)).to.equal(2); + return expect( + blockVersion.getBlockVersion(height, exceptions) + ).to.equal(2); }); }); }); From 5e9911ad0eb69df15f89790c461ddbf3fb66a5f7 Mon Sep 17 00:00:00 2001 From: michielmulders Date: Thu, 13 Jun 2019 16:46:01 +0200 Subject: [PATCH 08/25] Add filter for heightPrevoted and previous - change order getBytes --- framework/src/components/storage/entities/block.js | 8 ++++++-- framework/src/modules/chain/blocks/block.js | 2 +- 2 files changed, 7 insertions(+), 3 deletions(-) diff --git a/framework/src/components/storage/entities/block.js b/framework/src/components/storage/entities/block.js index b7f1cdefb61..5c0c0d1e9fd 100644 --- a/framework/src/components/storage/entities/block.js +++ b/framework/src/components/storage/entities/block.js @@ -169,8 +169,12 @@ class Block extends BaseEntity { this.addField('rowId', 'number'); this.addField('id', 'string', { filter: filterType.TEXT }); this.addField('height', 'number', { filter: filterType.NUMBER }); - this.addField('maxHeightPreviouslyForged', 'number'); - this.addField('prevotedConfirmedUptoHeight', 'number'); + this.addField('maxHeightPreviouslyForged', 'number', { + filter: filterType.NUMBER, + }); + this.addField('prevotedConfirmedUptoHeight', 'number', { + filter: filterType.NUMBER, + }); this.addField( 'blockSignature', 'string', diff --git a/framework/src/modules/chain/blocks/block.js b/framework/src/modules/chain/blocks/block.js index 6fbe242e363..e48848e5a2c 100644 --- a/framework/src/modules/chain/blocks/block.js +++ b/framework/src/modules/chain/blocks/block.js @@ -134,10 +134,10 @@ const getBytesV2 = block => { const capacity = 4 + // version (int) 4 + // timestamp (int) + 8 + // previousBlock 4 + // height (int) 4 + // maxHeightPreviouslyForged (int) 4 + // prevotedConfirmedUptoHeight (int) - 8 + // previousBlock 4 + // numberOfTransactions (int) 8 + // totalAmount (long) 8 + // totalFee (long) From e546e03fd19ff1e9f5873269319c05b88e06534b Mon Sep 17 00:00:00 2001 From: michielmulders Date: Thu, 13 Jun 2019 17:05:14 +0200 Subject: [PATCH 09/25] Split blockV1 and blockV2 --- framework/src/modules/chain/blocks/block.js | 697 +----------------- framework/src/modules/chain/blocks/blockV1.js | 465 ++++++++++++ framework/src/modules/chain/blocks/blockV2.js | 471 ++++++++++++ 3 files changed, 950 insertions(+), 683 deletions(-) create mode 100644 framework/src/modules/chain/blocks/blockV1.js create mode 100644 framework/src/modules/chain/blocks/blockV2.js diff --git a/framework/src/modules/chain/blocks/block.js b/framework/src/modules/chain/blocks/block.js index e48848e5a2c..22ed0ab3a0f 100644 --- a/framework/src/modules/chain/blocks/block.js +++ b/framework/src/modules/chain/blocks/block.js @@ -14,690 +14,21 @@ 'use strict'; -const { Status: TransactionStatus } = require('@liskhq/lisk-transactions'); const { - getAddressFromPublicKey, - hexToBuffer, - signDataWithPrivateKey, - hash, - verifyData, -} = require('@liskhq/lisk-cryptography'); -const _ = require('lodash'); -const crypto = require('crypto'); -const ByteBuffer = require('bytebuffer'); -const BigNum = require('@liskhq/bignum'); -const validator = require('../../../controller/validator'); -const { validateTransactions } = require('../transactions'); -const blockVersion = require('./block_version'); - -// TODO: remove type constraints -const TRANSACTION_TYPES_MULTI = 4; - -/** - * Creates a block signature. - * - * @param {block} block - * @param {Object} keypair - * @returns {signature} Block signature - * @todo Add description for the params - */ -const sign = (block, keypair) => - signDataWithPrivateKey(hash(getBytes(block)), keypair.privateKey); - -/** - * Creates hash based on block bytes. - * - * @param {block} block - * @returns {Buffer} SHA256 hash - * @todo Add description for the params - */ -const getHash = block => hash(getBytes(block)); - -/** - * Description of the function. - * - * @param {block} block - * @throws {Error} - * @returns {!Array} Contents as an ArrayBuffer - * @todo Add description for the function and the params - */ -const getBytesV0 = block => { - const capacity = - 4 + // version (int) - 4 + // timestamp (int) - 8 + // previousBlock - 4 + // numberOfTransactions (int) - 8 + // totalAmount (long) - 8 + // totalFee (long) - 8 + // reward (long) - 4 + // payloadLength (int) - 32 + // payloadHash - 32 + // generatorPublicKey - 64 + // blockSignature or unused - 4; // unused - - const byteBuffer = new ByteBuffer(capacity, true); - byteBuffer.writeInt(block.version); - byteBuffer.writeInt(block.timestamp); - - if (block.previousBlock) { - const pb = new BigNum(block.previousBlock).toBuffer({ size: '8' }); - - for (let i = 0; i < 8; i++) { - byteBuffer.writeByte(pb[i]); - } - } else { - for (let i = 0; i < 8; i++) { - byteBuffer.writeByte(0); - } - } - - byteBuffer.writeInt(block.numberOfTransactions); - byteBuffer.writeLong(block.totalAmount.toString()); - byteBuffer.writeLong(block.totalFee.toString()); - byteBuffer.writeLong(block.reward.toString()); - - byteBuffer.writeInt(block.payloadLength); - - const payloadHashBuffer = hexToBuffer(block.payloadHash); - for (let i = 0; i < payloadHashBuffer.length; i++) { - byteBuffer.writeByte(payloadHashBuffer[i]); - } - - const generatorPublicKeyBuffer = hexToBuffer(block.generatorPublicKey); - for (let i = 0; i < generatorPublicKeyBuffer.length; i++) { - byteBuffer.writeByte(generatorPublicKeyBuffer[i]); - } - - if (block.blockSignature) { - const blockSignatureBuffer = hexToBuffer(block.blockSignature); - for (let i = 0; i < blockSignatureBuffer.length; i++) { - byteBuffer.writeByte(blockSignatureBuffer[i]); - } - } - - byteBuffer.flip(); - return byteBuffer.toBuffer(); -}; - -const getBytesV1 = block => getBytesV0(block); - -/** - * Description of the function. - * - * @param {block} block - * @throws {Error} - * @returns {!Array} Contents as an ArrayBuffer - * @todo Add description for the function and the params - */ -const getBytesV2 = block => { - const capacity = - 4 + // version (int) - 4 + // timestamp (int) - 8 + // previousBlock - 4 + // height (int) - 4 + // maxHeightPreviouslyForged (int) - 4 + // prevotedConfirmedUptoHeight (int) - 4 + // numberOfTransactions (int) - 8 + // totalAmount (long) - 8 + // totalFee (long) - 8 + // reward (long) - 4 + // payloadLength (int) - 32 + // payloadHash - 32 + // generatorPublicKey - 64 + // blockSignature or unused - 4; // unused - - const byteBuffer = new ByteBuffer(capacity, true); - byteBuffer.writeInt(block.version); - byteBuffer.writeInt(block.timestamp); - byteBuffer.writeInt(block.height); - byteBuffer.writeInt(block.maxHeightPreviouslyForged); - byteBuffer.writeInt(block.prevotedConfirmedUptoHeight); - - if (block.previousBlock) { - const pb = new BigNum(block.previousBlock).toBuffer({ size: '8' }); - - for (let i = 0; i < 8; i++) { - byteBuffer.writeByte(pb[i]); - } - } else { - for (let i = 0; i < 8; i++) { - byteBuffer.writeByte(0); - } - } - - byteBuffer.writeInt(block.numberOfTransactions); - byteBuffer.writeLong(block.totalAmount.toString()); - byteBuffer.writeLong(block.totalFee.toString()); - byteBuffer.writeLong(block.reward.toString()); - - byteBuffer.writeInt(block.payloadLength); - - const payloadHashBuffer = hexToBuffer(block.payloadHash); - for (let i = 0; i < payloadHashBuffer.length; i++) { - byteBuffer.writeByte(payloadHashBuffer[i]); - } - - const generatorPublicKeyBuffer = hexToBuffer(block.generatorPublicKey); - for (let i = 0; i < generatorPublicKeyBuffer.length; i++) { - byteBuffer.writeByte(generatorPublicKeyBuffer[i]); - } - - if (block.blockSignature) { - const blockSignatureBuffer = hexToBuffer(block.blockSignature); - for (let i = 0; i < blockSignatureBuffer.length; i++) { - byteBuffer.writeByte(blockSignatureBuffer[i]); - } - } - - byteBuffer.flip(); - return byteBuffer.toBuffer(); -}; - -/** - * Description of the function. - * - * @param {block} block - * @throws {string|Error} - * @returns {Object} Normalized block - * @todo Add description for the function and the params - */ -const objectNormalize = (block, exceptions = {}) => { - Object.keys(block).forEach(key => { - if (block[key] === null || typeof block[key] === 'undefined') { - delete block[key]; - } - }); - try { - validator.validate(blockSchema, block); - } catch (schemaError) { - throw schemaError.errors; - } - const { transactionsResponses } = validateTransactions(exceptions)( - block.transactions - ); - const invalidTransactionResponse = transactionsResponses.find( - transactionResponse => transactionResponse.status !== TransactionStatus.OK - ); - if (invalidTransactionResponse) { - throw invalidTransactionResponse.errors; - } - return block; -}; - -/** - * Sorts input data transactions. - * Calculates reward based on previous block data. - * Generates new block. - * - * @param {Object} data - * @returns {block} block - * @todo Add description for the params - */ -const createV1 = ({ - blockReward, - transactions, - previousBlock, - keypair, - timestamp, - maxPayloadLength, - exceptions, -}) => { - // TODO: move to transactions module logic - const sortedTransactions = transactions.sort((a, b) => { - // Place MULTI transaction after all other transaction types - if ( - a.type === TRANSACTION_TYPES_MULTI && - b.type !== TRANSACTION_TYPES_MULTI - ) { - return 1; - } - // Place all other transaction types before MULTI transaction - if ( - a.type !== TRANSACTION_TYPES_MULTI && - b.type === TRANSACTION_TYPES_MULTI - ) { - return -1; - } - // Place depending on type (lower first) - if (a.type < b.type) { - return -1; - } - if (a.type > b.type) { - return 1; - } - // Place depending on amount (lower first) - if (a.amount.lt(b.amount)) { - return -1; - } - if (a.amount.gt(b.amount)) { - return 1; - } - return 0; - }); - - const nextHeight = previousBlock ? previousBlock.height + 1 : 1; - - const reward = blockReward.calculateReward(nextHeight); - let totalFee = new BigNum(0); - let totalAmount = new BigNum(0); - let size = 0; - - const blockTransactions = []; - const payloadHash = crypto.createHash('sha256'); - - for (let i = 0; i < sortedTransactions.length; i++) { - const transaction = sortedTransactions[i]; - const bytes = transaction.getBytes(transaction); - - if (size + bytes.length > maxPayloadLength) { - break; - } - - size += bytes.length; - - totalFee = totalFee.plus(transaction.fee); - totalAmount = totalAmount.plus(transaction.amount); - - blockTransactions.push(transaction); - payloadHash.update(bytes); - } - - const block = { - version: blockVersion.getBlockVersion(nextHeight, exceptions), - totalAmount, - totalFee, - reward, - payloadHash: payloadHash.digest().toString('hex'), - timestamp, - numberOfTransactions: blockTransactions.length, - payloadLength: size, - previousBlock: previousBlock.id, - generatorPublicKey: keypair.publicKey.toString('hex'), - transactions: blockTransactions, - }; - - block.blockSignature = sign(block, keypair); - return objectNormalize(block, exceptions); -}; - -/** - * Sorts input data transactions. - * Calculates reward based on previous block data. - * Generates new block. - * - * @param {Object} data - * @returns {block} block - * @todo Add description for the params - */ -const createV2 = ({ - blockReward, - transactions, - previousBlock, - keypair, - timestamp, - maxPayloadLength, - maxHeightPreviouslyForged, - prevotedConfirmedUptoHeight, - exceptions, -}) => { - // TODO: move to transactions module logic - const sortedTransactions = transactions.sort((a, b) => { - // Place MULTI transaction after all other transaction types - if ( - a.type === TRANSACTION_TYPES_MULTI && - b.type !== TRANSACTION_TYPES_MULTI - ) { - return 1; - } - // Place all other transaction types before MULTI transaction - if ( - a.type !== TRANSACTION_TYPES_MULTI && - b.type === TRANSACTION_TYPES_MULTI - ) { - return -1; - } - // Place depending on type (lower first) - if (a.type < b.type) { - return -1; - } - if (a.type > b.type) { - return 1; - } - // Place depending on amount (lower first) - if (a.amount.lt(b.amount)) { - return -1; - } - if (a.amount.gt(b.amount)) { - return 1; - } - return 0; - }); - - const nextHeight = previousBlock ? previousBlock.height + 1 : 1; - - const reward = blockReward.calculateReward(nextHeight); - let totalFee = new BigNum(0); - let totalAmount = new BigNum(0); - let size = 0; - - const blockTransactions = []; - const payloadHash = crypto.createHash('sha256'); - - for (let i = 0; i < sortedTransactions.length; i++) { - const transaction = sortedTransactions[i]; - const bytes = transaction.getBytes(transaction); - - if (size + bytes.length > maxPayloadLength) { - break; - } - - size += bytes.length; - - totalFee = totalFee.plus(transaction.fee); - totalAmount = totalAmount.plus(transaction.amount); - - blockTransactions.push(transaction); - payloadHash.update(bytes); - } - - const block = { - version: blockVersion.getBlockVersion(nextHeight, exceptions), - totalAmount, - totalFee, - reward, - payloadHash: payloadHash.digest().toString('hex'), - timestamp, - numberOfTransactions: blockTransactions.length, - payloadLength: size, - previousBlock: previousBlock.id, - generatorPublicKey: keypair.publicKey.toString('hex'), - transactions: blockTransactions, - height: nextHeight, - maxHeightPreviouslyForged, - prevotedConfirmedUptoHeight, - }; - - block.blockSignature = sign(block, keypair); - return objectNormalize(block, exceptions); -}; - -/** - * Verifies block hash, generator block publicKey and block signature. - * - * @param {block} block - * @throws {Error} - * @returns {boolean} Verified hash, signature and publicKey - * @todo Add description for the params - */ -const verifySignature = block => { - const signatureLength = 64; - const data = getBytes(block); - const dataWithoutSignature = Buffer.alloc(data.length - signatureLength); - - for (let i = 0; i < dataWithoutSignature.length; i++) { - dataWithoutSignature[i] = data[i]; - } - const hashedBlock = hash(dataWithoutSignature); - return verifyData( - hashedBlock, - block.blockSignature, - block.generatorPublicKey - ); -}; - -/** - * Calculates block id based on block. - * - * @param {block} block - * @returns {string} Block id - * @todo Add description for the params - */ -const getId = block => { - const hashedBlock = hash(getBytes(block)); - const temp = Buffer.alloc(8); - for (let i = 0; i < 8; i++) { - temp[i] = hashedBlock[7 - i]; - } - - // eslint-disable-next-line new-cap - const id = new BigNum.fromBuffer(temp).toString(); - return id; -}; - -/** - * Creates block object based on raw data. - * - * @param {Object} raw - * @returns {null|block} Block object - * @todo Add description for the params - */ -const dbReadV0 = raw => { - if (!raw.b_id) { - return null; - } - const block = { - id: raw.b_id, - version: parseInt(raw.b_version), - timestamp: parseInt(raw.b_timestamp), - height: parseInt(raw.b_height), - previousBlock: raw.b_previousBlock, - numberOfTransactions: parseInt(raw.b_numberOfTransactions), - totalAmount: new BigNum(raw.b_totalAmount), - totalFee: new BigNum(raw.b_totalFee), - reward: new BigNum(raw.b_reward), - payloadLength: parseInt(raw.b_payloadLength), - payloadHash: raw.b_payloadHash, - generatorPublicKey: raw.b_generatorPublicKey, - generatorId: getAddressFromPublicKey(raw.b_generatorPublicKey), - blockSignature: raw.b_blockSignature, - confirmations: parseInt(raw.b_confirmations), - }; - block.totalForged = block.totalFee.plus(block.reward).toString(); - return block; -}; - -const dbReadV1 = raw => dbReadV0(raw); - -/** - * Creates block object based on raw data. - * - * @param {Object} raw - * @returns {null|block} Block object - * @todo Add description for the params - */ -const dbReadV2 = raw => { - if (!raw.b_id) { - return null; - } - const block = { - id: raw.b_id, - version: parseInt(raw.b_version), - timestamp: parseInt(raw.b_timestamp), - height: parseInt(raw.b_height), - maxHeightPreviouslyForged: parseInt(raw.b_maxHeightPreviouslyForged), - prevotedConfirmedUptoHeight: parseInt(raw.b_prevotedConfirmedUptoHeight), - previousBlock: raw.b_previousBlock, - numberOfTransactions: parseInt(raw.b_numberOfTransactions), - totalAmount: new BigNum(raw.b_totalAmount), - totalFee: new BigNum(raw.b_totalFee), - reward: new BigNum(raw.b_reward), - payloadLength: parseInt(raw.b_payloadLength), - payloadHash: raw.b_payloadHash, - generatorPublicKey: raw.b_generatorPublicKey, - generatorId: getAddressFromPublicKey(raw.b_generatorPublicKey), - blockSignature: raw.b_blockSignature, - confirmations: parseInt(raw.b_confirmations), - }; - block.totalForged = block.totalFee.plus(block.reward).toString(); - return block; -}; - -/** - * Creates block object based on raw database block data. - * - * @param {Object} raw Raw database data block object - * @returns {null|block} Block object - */ -const storageReadV0 = raw => { - if (!raw.id) { - return null; - } - - const block = { - id: raw.id, - version: parseInt(raw.version), - timestamp: parseInt(raw.timestamp), - height: parseInt(raw.height), - previousBlock: raw.previousBlockId, - numberOfTransactions: parseInt(raw.numberOfTransactions), - totalAmount: new BigNum(raw.totalAmount), - totalFee: new BigNum(raw.totalFee), - reward: new BigNum(raw.reward), - payloadLength: parseInt(raw.payloadLength), - payloadHash: raw.payloadHash, - generatorPublicKey: raw.generatorPublicKey, - generatorId: getAddressFromPublicKey(raw.generatorPublicKey), - blockSignature: raw.blockSignature, - confirmations: parseInt(raw.confirmations), - }; - - if (raw.transactions) { - block.transactions = raw.transactions - .filter(tx => !!tx.id) - .map(tx => _.omitBy(tx, _.isNull)); - } - - block.totalForged = block.totalFee.plus(block.reward).toString(); - - return block; -}; - -const storageReadV1 = raw => storageReadV0(raw); - -/** - * Creates block object based on raw database block data. - * - * @param {Object} raw Raw database data block object - * @returns {null|block} Block object - */ -const storageReadV2 = raw => { - if (!raw.id) { - return null; - } - - const block = { - id: raw.id, - version: parseInt(raw.version), - timestamp: parseInt(raw.timestamp), - height: parseInt(raw.height), - maxHeightPreviouslyForged: parseInt(raw.maxHeightPreviouslyForged), - prevotedConfirmedUptoHeight: parseInt(raw.prevotedConfirmedUptoHeight), - previousBlock: raw.previousBlockId, - numberOfTransactions: parseInt(raw.numberOfTransactions), - totalAmount: new BigNum(raw.totalAmount), - totalFee: new BigNum(raw.totalFee), - reward: new BigNum(raw.reward), - payloadLength: parseInt(raw.payloadLength), - payloadHash: raw.payloadHash, - generatorPublicKey: raw.generatorPublicKey, - generatorId: getAddressFromPublicKey(raw.generatorPublicKey), - blockSignature: raw.blockSignature, - confirmations: parseInt(raw.confirmations), - }; - - if (raw.transactions) { - block.transactions = raw.transactions - .filter(tx => !!tx.id) - .map(tx => _.omitBy(tx, _.isNull)); - } - - block.totalForged = block.totalFee.plus(block.reward).toString(); - - return block; -}; - -const blockSchema = { - type: 'object', - properties: { - id: { - type: 'string', - format: 'id', - minLength: 1, - maxLength: 20, - }, - height: { - type: 'integer', - }, - maxHeightPreviouslyForged: { - type: 'integer', - }, - prevotedConfirmedUptoHeight: { - type: 'integer', - }, - blockSignature: { - type: 'string', - format: 'signature', - }, - generatorPublicKey: { - type: 'string', - format: 'publicKey', - }, - numberOfTransactions: { - type: 'integer', - }, - payloadHash: { - type: 'string', - format: 'hex', - }, - payloadLength: { - type: 'integer', - }, - previousBlock: { - type: 'string', - format: 'id', - minLength: 1, - maxLength: 20, - }, - timestamp: { - type: 'integer', - }, - totalAmount: { - type: 'object', - format: 'amount', - }, - totalFee: { - type: 'object', - format: 'amount', - }, - reward: { - type: 'object', - format: 'amount', - }, - transactions: { - type: 'array', - uniqueItems: true, - }, - version: { - type: 'integer', - minimum: 0, - }, - }, - required: [ - 'blockSignature', - 'generatorPublicKey', - 'numberOfTransactions', - 'payloadHash', - 'payloadLength', - 'timestamp', - 'totalAmount', - 'totalFee', - 'reward', - 'transactions', - 'version', - ], -}; + createV1, + getBytesV0, + getBytesV1, + dbReadV0, + dbReadV1, + storageReadV0, + storageReadV1, + verifySignature, + getId, + getHash, + objectNormalize, + sign, +} = require('./blockV1'); +const { createV2, getBytesV2, dbReadV2, storageReadV2 } = require('./blockV2'); const createFunc = { 1: createV1, diff --git a/framework/src/modules/chain/blocks/blockV1.js b/framework/src/modules/chain/blocks/blockV1.js new file mode 100644 index 00000000000..395968fc339 --- /dev/null +++ b/framework/src/modules/chain/blocks/blockV1.js @@ -0,0 +1,465 @@ +/* + * Copyright © 2019 Lisk Foundation + * + * See the LICENSE file at the top-level directory of this distribution + * for licensing information. + * + * Unless otherwise agreed in a custom licensing agreement with the Lisk Foundation, + * no part of this software, including this file, may be copied, modified, + * propagated, or distributed except according to the terms contained in the + * LICENSE file. + * + * Removal or modification of this copyright notice is prohibited. + */ + +'use strict'; + +const { Status: TransactionStatus } = require('@liskhq/lisk-transactions'); +const { + getAddressFromPublicKey, + hexToBuffer, + signDataWithPrivateKey, + hash, + verifyData, +} = require('@liskhq/lisk-cryptography'); +const _ = require('lodash'); +const crypto = require('crypto'); +const ByteBuffer = require('bytebuffer'); +const BigNum = require('@liskhq/bignum'); +const validator = require('../../../controller/validator'); +const { validateTransactions } = require('../transactions'); +const blockVersion = require('./block_version'); + +// TODO: remove type constraints +const TRANSACTION_TYPES_MULTI = 4; + +/** + * Creates bytebuffer out of block data used for signatures. + * + * @param {block} block + * @throws {Error} + * @returns {!Array} Contents as an ArrayBuffer + * @todo Add description for the function and the params + */ +const getBytesV0 = block => { + const capacity = + 4 + // version (int) + 4 + // timestamp (int) + 8 + // previousBlock + 4 + // numberOfTransactions (int) + 8 + // totalAmount (long) + 8 + // totalFee (long) + 8 + // reward (long) + 4 + // payloadLength (int) + 32 + // payloadHash + 32 + // generatorPublicKey + 64 + // blockSignature or unused + 4; // unused + + const byteBuffer = new ByteBuffer(capacity, true); + byteBuffer.writeInt(block.version); + byteBuffer.writeInt(block.timestamp); + + if (block.previousBlock) { + const pb = new BigNum(block.previousBlock).toBuffer({ size: '8' }); + + for (let i = 0; i < 8; i++) { + byteBuffer.writeByte(pb[i]); + } + } else { + for (let i = 0; i < 8; i++) { + byteBuffer.writeByte(0); + } + } + + byteBuffer.writeInt(block.numberOfTransactions); + byteBuffer.writeLong(block.totalAmount.toString()); + byteBuffer.writeLong(block.totalFee.toString()); + byteBuffer.writeLong(block.reward.toString()); + + byteBuffer.writeInt(block.payloadLength); + + const payloadHashBuffer = hexToBuffer(block.payloadHash); + for (let i = 0; i < payloadHashBuffer.length; i++) { + byteBuffer.writeByte(payloadHashBuffer[i]); + } + + const generatorPublicKeyBuffer = hexToBuffer(block.generatorPublicKey); + for (let i = 0; i < generatorPublicKeyBuffer.length; i++) { + byteBuffer.writeByte(generatorPublicKeyBuffer[i]); + } + + if (block.blockSignature) { + const blockSignatureBuffer = hexToBuffer(block.blockSignature); + for (let i = 0; i < blockSignatureBuffer.length; i++) { + byteBuffer.writeByte(blockSignatureBuffer[i]); + } + } + + byteBuffer.flip(); + return byteBuffer.toBuffer(); +}; + +const getBytesV1 = block => getBytesV0(block); + +/** + * Sorts input data transactions. + * Calculates reward based on previous block data. + * Generates new block. + * + * @param {Object} data + * @returns {block} block + * @todo Add description for the params + */ +const createV1 = ({ + blockReward, + transactions, + previousBlock, + keypair, + timestamp, + maxPayloadLength, + exceptions, +}) => { + // TODO: move to transactions module logic + const sortedTransactions = transactions.sort((a, b) => { + // Place MULTI transaction after all other transaction types + if ( + a.type === TRANSACTION_TYPES_MULTI && + b.type !== TRANSACTION_TYPES_MULTI + ) { + return 1; + } + // Place all other transaction types before MULTI transaction + if ( + a.type !== TRANSACTION_TYPES_MULTI && + b.type === TRANSACTION_TYPES_MULTI + ) { + return -1; + } + // Place depending on type (lower first) + if (a.type < b.type) { + return -1; + } + if (a.type > b.type) { + return 1; + } + // Place depending on amount (lower first) + if (a.amount.lt(b.amount)) { + return -1; + } + if (a.amount.gt(b.amount)) { + return 1; + } + return 0; + }); + + const nextHeight = previousBlock ? previousBlock.height + 1 : 1; + + const reward = blockReward.calculateReward(nextHeight); + let totalFee = new BigNum(0); + let totalAmount = new BigNum(0); + let size = 0; + + const blockTransactions = []; + const payloadHash = crypto.createHash('sha256'); + + for (let i = 0; i < sortedTransactions.length; i++) { + const transaction = sortedTransactions[i]; + const bytes = transaction.getBytes(transaction); + + if (size + bytes.length > maxPayloadLength) { + break; + } + + size += bytes.length; + + totalFee = totalFee.plus(transaction.fee); + totalAmount = totalAmount.plus(transaction.amount); + + blockTransactions.push(transaction); + payloadHash.update(bytes); + } + + const block = { + version: blockVersion.getBlockVersion(nextHeight, exceptions), + totalAmount, + totalFee, + reward, + payloadHash: payloadHash.digest().toString('hex'), + timestamp, + numberOfTransactions: blockTransactions.length, + payloadLength: size, + previousBlock: previousBlock.id, + generatorPublicKey: keypair.publicKey.toString('hex'), + transactions: blockTransactions, + }; + + block.blockSignature = sign(block, keypair); + return objectNormalize(block, exceptions); +}; + +/** + * Creates block object based on raw data. + * + * @param {Object} raw + * @returns {null|block} Block object + * @todo Add description for the params + */ +const dbReadV0 = raw => { + if (!raw.b_id) { + return null; + } + const block = { + id: raw.b_id, + version: parseInt(raw.b_version), + timestamp: parseInt(raw.b_timestamp), + height: parseInt(raw.b_height), + previousBlock: raw.b_previousBlock, + numberOfTransactions: parseInt(raw.b_numberOfTransactions), + totalAmount: new BigNum(raw.b_totalAmount), + totalFee: new BigNum(raw.b_totalFee), + reward: new BigNum(raw.b_reward), + payloadLength: parseInt(raw.b_payloadLength), + payloadHash: raw.b_payloadHash, + generatorPublicKey: raw.b_generatorPublicKey, + generatorId: getAddressFromPublicKey(raw.b_generatorPublicKey), + blockSignature: raw.b_blockSignature, + confirmations: parseInt(raw.b_confirmations), + }; + block.totalForged = block.totalFee.plus(block.reward).toString(); + return block; +}; + +const dbReadV1 = raw => dbReadV0(raw); + +/** + * Creates block object based on raw database block data. + * + * @param {Object} raw Raw database data block object + * @returns {null|block} Block object + */ +const storageReadV0 = raw => { + if (!raw.id) { + return null; + } + + const block = { + id: raw.id, + version: parseInt(raw.version), + timestamp: parseInt(raw.timestamp), + height: parseInt(raw.height), + previousBlock: raw.previousBlockId, + numberOfTransactions: parseInt(raw.numberOfTransactions), + totalAmount: new BigNum(raw.totalAmount), + totalFee: new BigNum(raw.totalFee), + reward: new BigNum(raw.reward), + payloadLength: parseInt(raw.payloadLength), + payloadHash: raw.payloadHash, + generatorPublicKey: raw.generatorPublicKey, + generatorId: getAddressFromPublicKey(raw.generatorPublicKey), + blockSignature: raw.blockSignature, + confirmations: parseInt(raw.confirmations), + }; + + if (raw.transactions) { + block.transactions = raw.transactions + .filter(tx => !!tx.id) + .map(tx => _.omitBy(tx, _.isNull)); + } + + block.totalForged = block.totalFee.plus(block.reward).toString(); + + return block; +}; + +const storageReadV1 = raw => storageReadV0(raw); + +/** + * Creates a block signature. + * + * @param {block} block + * @param {Object} keypair + * @returns {signature} Block signature + * @todo Add description for the params + */ +const sign = (block, keypair) => + signDataWithPrivateKey(hash(getBytesV1(block)), keypair.privateKey); + +/** + * Creates hash based on block bytes. + * + * @param {block} block + * @returns {Buffer} SHA256 hash + * @todo Add description for the params + */ +const getHash = block => hash(getBytesV1(block)); + +/** + * Description of the function. + * + * @param {block} block + * @throws {string|Error} + * @returns {Object} Normalized block + * @todo Add description for the function and the params + */ +const objectNormalize = (block, exceptions = {}) => { + Object.keys(block).forEach(key => { + if (block[key] === null || typeof block[key] === 'undefined') { + delete block[key]; + } + }); + try { + validator.validate(blockSchema, block); + } catch (schemaError) { + throw schemaError.errors; + } + const { transactionsResponses } = validateTransactions(exceptions)( + block.transactions + ); + const invalidTransactionResponse = transactionsResponses.find( + transactionResponse => transactionResponse.status !== TransactionStatus.OK + ); + if (invalidTransactionResponse) { + throw invalidTransactionResponse.errors; + } + return block; +}; + +/** + * Verifies block hash, generator block publicKey and block signature. + * + * @param {block} block + * @throws {Error} + * @returns {boolean} Verified hash, signature and publicKey + * @todo Add description for the params + */ +const verifySignature = block => { + const signatureLength = 64; + const data = getBytesV1(block); + const dataWithoutSignature = Buffer.alloc(data.length - signatureLength); + + for (let i = 0; i < dataWithoutSignature.length; i++) { + dataWithoutSignature[i] = data[i]; + } + const hashedBlock = hash(dataWithoutSignature); + return verifyData( + hashedBlock, + block.blockSignature, + block.generatorPublicKey + ); +}; + +/** + * Calculates block id based on block. + * + * @param {block} block + * @returns {string} Block id + * @todo Add description for the params + */ +const getId = block => { + const hashedBlock = hash(getBytesV1(block)); + const temp = Buffer.alloc(8); + for (let i = 0; i < 8; i++) { + temp[i] = hashedBlock[7 - i]; + } + + // eslint-disable-next-line new-cap + const id = new BigNum.fromBuffer(temp).toString(); + return id; +}; + +const blockSchema = { + type: 'object', + properties: { + id: { + type: 'string', + format: 'id', + minLength: 1, + maxLength: 20, + }, + height: { + type: 'integer', + }, + maxHeightPreviouslyForged: { + type: 'integer', + }, + prevotedConfirmedUptoHeight: { + type: 'integer', + }, + blockSignature: { + type: 'string', + format: 'signature', + }, + generatorPublicKey: { + type: 'string', + format: 'publicKey', + }, + numberOfTransactions: { + type: 'integer', + }, + payloadHash: { + type: 'string', + format: 'hex', + }, + payloadLength: { + type: 'integer', + }, + previousBlock: { + type: 'string', + format: 'id', + minLength: 1, + maxLength: 20, + }, + timestamp: { + type: 'integer', + }, + totalAmount: { + type: 'object', + format: 'amount', + }, + totalFee: { + type: 'object', + format: 'amount', + }, + reward: { + type: 'object', + format: 'amount', + }, + transactions: { + type: 'array', + uniqueItems: true, + }, + version: { + type: 'integer', + minimum: 0, + }, + }, + required: [ + 'blockSignature', + 'generatorPublicKey', + 'numberOfTransactions', + 'payloadHash', + 'payloadLength', + 'timestamp', + 'totalAmount', + 'totalFee', + 'reward', + 'transactions', + 'version', + ], +}; + +module.exports = { + getBytesV0, + getBytesV1, + createV1, + dbReadV0, + dbReadV1, + storageReadV0, + storageReadV1, + verifySignature, + getId, + getHash, + objectNormalize, + sign, +}; diff --git a/framework/src/modules/chain/blocks/blockV2.js b/framework/src/modules/chain/blocks/blockV2.js new file mode 100644 index 00000000000..a643b98d15d --- /dev/null +++ b/framework/src/modules/chain/blocks/blockV2.js @@ -0,0 +1,471 @@ +/* + * Copyright © 2019 Lisk Foundation + * + * See the LICENSE file at the top-level directory of this distribution + * for licensing information. + * + * Unless otherwise agreed in a custom licensing agreement with the Lisk Foundation, + * no part of this software, including this file, may be copied, modified, + * propagated, or distributed except according to the terms contained in the + * LICENSE file. + * + * Removal or modification of this copyright notice is prohibited. + */ + +'use strict'; + +const { Status: TransactionStatus } = require('@liskhq/lisk-transactions'); +const { + getAddressFromPublicKey, + hexToBuffer, + signDataWithPrivateKey, + hash, + verifyData, +} = require('@liskhq/lisk-cryptography'); +const _ = require('lodash'); +const crypto = require('crypto'); +const ByteBuffer = require('bytebuffer'); +const BigNum = require('@liskhq/bignum'); +const validator = require('../../../controller/validator'); +const { validateTransactions } = require('../transactions'); +const blockVersion = require('./block_version'); + +// TODO: remove type constraints +const TRANSACTION_TYPES_MULTI = 4; + +/** + * Creates bytebuffer out of block data used for signatures. + * + * @param {block} block + * @throws {Error} + * @returns {!Array} Contents as an ArrayBuffer + * @todo Add description for the function and the params + */ +const getBytesV2 = block => { + const capacity = + 4 + // version (int) + 4 + // timestamp (int) + 8 + // previousBlock + 4 + // height (int) + 4 + // maxHeightPreviouslyForged (int) + 4 + // prevotedConfirmedUptoHeight (int) + 4 + // numberOfTransactions (int) + 8 + // totalAmount (long) + 8 + // totalFee (long) + 8 + // reward (long) + 4 + // payloadLength (int) + 32 + // payloadHash + 32 + // generatorPublicKey + 64 + // blockSignature or unused + 4; // unused + + const byteBuffer = new ByteBuffer(capacity, true); + byteBuffer.writeInt(block.version); + byteBuffer.writeInt(block.timestamp); + byteBuffer.writeInt(block.height); + byteBuffer.writeInt(block.maxHeightPreviouslyForged); + byteBuffer.writeInt(block.prevotedConfirmedUptoHeight); + + if (block.previousBlock) { + const pb = new BigNum(block.previousBlock).toBuffer({ size: '8' }); + + for (let i = 0; i < 8; i++) { + byteBuffer.writeByte(pb[i]); + } + } else { + for (let i = 0; i < 8; i++) { + byteBuffer.writeByte(0); + } + } + + byteBuffer.writeInt(block.numberOfTransactions); + byteBuffer.writeLong(block.totalAmount.toString()); + byteBuffer.writeLong(block.totalFee.toString()); + byteBuffer.writeLong(block.reward.toString()); + + byteBuffer.writeInt(block.payloadLength); + + const payloadHashBuffer = hexToBuffer(block.payloadHash); + for (let i = 0; i < payloadHashBuffer.length; i++) { + byteBuffer.writeByte(payloadHashBuffer[i]); + } + + const generatorPublicKeyBuffer = hexToBuffer(block.generatorPublicKey); + for (let i = 0; i < generatorPublicKeyBuffer.length; i++) { + byteBuffer.writeByte(generatorPublicKeyBuffer[i]); + } + + if (block.blockSignature) { + const blockSignatureBuffer = hexToBuffer(block.blockSignature); + for (let i = 0; i < blockSignatureBuffer.length; i++) { + byteBuffer.writeByte(blockSignatureBuffer[i]); + } + } + + byteBuffer.flip(); + return byteBuffer.toBuffer(); +}; + +/** + * Sorts input data transactions. + * Calculates reward based on previous block data. + * Generates new block. + * + * @param {Object} data + * @returns {block} block + * @todo Add description for the params + */ +const createV2 = ({ + blockReward, + transactions, + previousBlock, + keypair, + timestamp, + maxPayloadLength, + maxHeightPreviouslyForged, + prevotedConfirmedUptoHeight, + exceptions, +}) => { + // TODO: move to transactions module logic + const sortedTransactions = transactions.sort((a, b) => { + // Place MULTI transaction after all other transaction types + if ( + a.type === TRANSACTION_TYPES_MULTI && + b.type !== TRANSACTION_TYPES_MULTI + ) { + return 1; + } + // Place all other transaction types before MULTI transaction + if ( + a.type !== TRANSACTION_TYPES_MULTI && + b.type === TRANSACTION_TYPES_MULTI + ) { + return -1; + } + // Place depending on type (lower first) + if (a.type < b.type) { + return -1; + } + if (a.type > b.type) { + return 1; + } + // Place depending on amount (lower first) + if (a.amount.lt(b.amount)) { + return -1; + } + if (a.amount.gt(b.amount)) { + return 1; + } + return 0; + }); + + const nextHeight = previousBlock ? previousBlock.height + 1 : 1; + + const reward = blockReward.calculateReward(nextHeight); + let totalFee = new BigNum(0); + let totalAmount = new BigNum(0); + let size = 0; + + const blockTransactions = []; + const payloadHash = crypto.createHash('sha256'); + + for (let i = 0; i < sortedTransactions.length; i++) { + const transaction = sortedTransactions[i]; + const bytes = transaction.getBytes(transaction); + + if (size + bytes.length > maxPayloadLength) { + break; + } + + size += bytes.length; + + totalFee = totalFee.plus(transaction.fee); + totalAmount = totalAmount.plus(transaction.amount); + + blockTransactions.push(transaction); + payloadHash.update(bytes); + } + + const block = { + version: blockVersion.getBlockVersion(nextHeight, exceptions), + totalAmount, + totalFee, + reward, + payloadHash: payloadHash.digest().toString('hex'), + timestamp, + numberOfTransactions: blockTransactions.length, + payloadLength: size, + previousBlock: previousBlock.id, + generatorPublicKey: keypair.publicKey.toString('hex'), + transactions: blockTransactions, + height: nextHeight, + maxHeightPreviouslyForged, + prevotedConfirmedUptoHeight, + }; + + block.blockSignature = sign(block, keypair); + return objectNormalize(block, exceptions); +}; + +/** + * Creates block object based on raw data. + * + * @param {Object} raw + * @returns {null|block} Block object + * @todo Add description for the params + */ +const dbReadV2 = raw => { + if (!raw.b_id) { + return null; + } + const block = { + id: raw.b_id, + version: parseInt(raw.b_version), + timestamp: parseInt(raw.b_timestamp), + height: parseInt(raw.b_height), + maxHeightPreviouslyForged: parseInt(raw.b_maxHeightPreviouslyForged), + prevotedConfirmedUptoHeight: parseInt(raw.b_prevotedConfirmedUptoHeight), + previousBlock: raw.b_previousBlock, + numberOfTransactions: parseInt(raw.b_numberOfTransactions), + totalAmount: new BigNum(raw.b_totalAmount), + totalFee: new BigNum(raw.b_totalFee), + reward: new BigNum(raw.b_reward), + payloadLength: parseInt(raw.b_payloadLength), + payloadHash: raw.b_payloadHash, + generatorPublicKey: raw.b_generatorPublicKey, + generatorId: getAddressFromPublicKey(raw.b_generatorPublicKey), + blockSignature: raw.b_blockSignature, + confirmations: parseInt(raw.b_confirmations), + }; + block.totalForged = block.totalFee.plus(block.reward).toString(); + return block; +}; + +/** + * Creates block object based on raw database block data. + * + * @param {Object} raw Raw database data block object + * @returns {null|block} Block object + */ +const storageReadV2 = raw => { + if (!raw.id) { + return null; + } + + const block = { + id: raw.id, + version: parseInt(raw.version), + timestamp: parseInt(raw.timestamp), + height: parseInt(raw.height), + maxHeightPreviouslyForged: parseInt(raw.maxHeightPreviouslyForged), + prevotedConfirmedUptoHeight: parseInt(raw.prevotedConfirmedUptoHeight), + previousBlock: raw.previousBlockId, + numberOfTransactions: parseInt(raw.numberOfTransactions), + totalAmount: new BigNum(raw.totalAmount), + totalFee: new BigNum(raw.totalFee), + reward: new BigNum(raw.reward), + payloadLength: parseInt(raw.payloadLength), + payloadHash: raw.payloadHash, + generatorPublicKey: raw.generatorPublicKey, + generatorId: getAddressFromPublicKey(raw.generatorPublicKey), + blockSignature: raw.blockSignature, + confirmations: parseInt(raw.confirmations), + }; + + if (raw.transactions) { + block.transactions = raw.transactions + .filter(tx => !!tx.id) + .map(tx => _.omitBy(tx, _.isNull)); + } + + block.totalForged = block.totalFee.plus(block.reward).toString(); + + return block; +}; + +/** + * Creates a block signature. + * + * @param {block} block + * @param {Object} keypair + * @returns {signature} Block signature + * @todo Add description for the params + */ +const sign = (block, keypair) => + signDataWithPrivateKey(hash(getBytesV2(block)), keypair.privateKey); + +/** + * Creates hash based on block bytes. + * + * @param {block} block + * @returns {Buffer} SHA256 hash + * @todo Add description for the params + */ +const getHash = block => hash(getBytesV2(block)); + +/** + * Description of the function. + * + * @param {block} block + * @throws {string|Error} + * @returns {Object} Normalized block + * @todo Add description for the function and the params + */ +const objectNormalize = (block, exceptions = {}) => { + Object.keys(block).forEach(key => { + if (block[key] === null || typeof block[key] === 'undefined') { + delete block[key]; + } + }); + try { + validator.validate(blockSchema, block); + } catch (schemaError) { + throw schemaError.errors; + } + const { transactionsResponses } = validateTransactions(exceptions)( + block.transactions + ); + const invalidTransactionResponse = transactionsResponses.find( + transactionResponse => transactionResponse.status !== TransactionStatus.OK + ); + if (invalidTransactionResponse) { + throw invalidTransactionResponse.errors; + } + return block; +}; + +/** + * Verifies block hash, generator block publicKey and block signature. + * + * @param {block} block + * @throws {Error} + * @returns {boolean} Verified hash, signature and publicKey + * @todo Add description for the params + */ +const verifySignature = block => { + const signatureLength = 64; + const data = getBytesV2(block); + const dataWithoutSignature = Buffer.alloc(data.length - signatureLength); + + for (let i = 0; i < dataWithoutSignature.length; i++) { + dataWithoutSignature[i] = data[i]; + } + const hashedBlock = hash(dataWithoutSignature); + return verifyData( + hashedBlock, + block.blockSignature, + block.generatorPublicKey + ); +}; + +/** + * Calculates block id based on block. + * + * @param {block} block + * @returns {string} Block id + * @todo Add description for the params + */ +const getId = block => { + const hashedBlock = hash(getBytesV2(block)); + const temp = Buffer.alloc(8); + for (let i = 0; i < 8; i++) { + temp[i] = hashedBlock[7 - i]; + } + + // eslint-disable-next-line new-cap + const id = new BigNum.fromBuffer(temp).toString(); + return id; +}; + +const blockSchema = { + type: 'object', + properties: { + id: { + type: 'string', + format: 'id', + minLength: 1, + maxLength: 20, + }, + height: { + type: 'integer', + }, + maxHeightPreviouslyForged: { + type: 'integer', + }, + prevotedConfirmedUptoHeight: { + type: 'integer', + }, + blockSignature: { + type: 'string', + format: 'signature', + }, + generatorPublicKey: { + type: 'string', + format: 'publicKey', + }, + numberOfTransactions: { + type: 'integer', + }, + payloadHash: { + type: 'string', + format: 'hex', + }, + payloadLength: { + type: 'integer', + }, + previousBlock: { + type: 'string', + format: 'id', + minLength: 1, + maxLength: 20, + }, + timestamp: { + type: 'integer', + }, + totalAmount: { + type: 'object', + format: 'amount', + }, + totalFee: { + type: 'object', + format: 'amount', + }, + reward: { + type: 'object', + format: 'amount', + }, + transactions: { + type: 'array', + uniqueItems: true, + }, + version: { + type: 'integer', + minimum: 0, + }, + }, + required: [ + 'blockSignature', + 'generatorPublicKey', + 'numberOfTransactions', + 'payloadHash', + 'payloadLength', + 'timestamp', + 'totalAmount', + 'totalFee', + 'reward', + 'transactions', + 'version', + ], +}; + +module.exports = { + getBytesV2, + createV2, + dbReadV2, + storageReadV2, + verifySignature, + getId, + getHash, + objectNormalize, + sign, +}; From e631389b4c307875230ea0aa6ac3a85076109a44 Mon Sep 17 00:00:00 2001 From: michielmulders Date: Thu, 13 Jun 2019 17:07:42 +0200 Subject: [PATCH 10/25] Modify block tests with correct version --- framework/src/modules/chain/blocks/blockV2.js | 6 +++--- framework/test/mocha/unit/modules/chain/blocks/block.js | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/framework/src/modules/chain/blocks/blockV2.js b/framework/src/modules/chain/blocks/blockV2.js index a643b98d15d..b5e2c24abd4 100644 --- a/framework/src/modules/chain/blocks/blockV2.js +++ b/framework/src/modules/chain/blocks/blockV2.js @@ -62,9 +62,6 @@ const getBytesV2 = block => { const byteBuffer = new ByteBuffer(capacity, true); byteBuffer.writeInt(block.version); byteBuffer.writeInt(block.timestamp); - byteBuffer.writeInt(block.height); - byteBuffer.writeInt(block.maxHeightPreviouslyForged); - byteBuffer.writeInt(block.prevotedConfirmedUptoHeight); if (block.previousBlock) { const pb = new BigNum(block.previousBlock).toBuffer({ size: '8' }); @@ -78,6 +75,9 @@ const getBytesV2 = block => { } } + byteBuffer.writeInt(block.height); + byteBuffer.writeInt(block.maxHeightPreviouslyForged); + byteBuffer.writeInt(block.prevotedConfirmedUptoHeight); byteBuffer.writeInt(block.numberOfTransactions); byteBuffer.writeLong(block.totalAmount.toString()); byteBuffer.writeLong(block.totalFee.toString()); diff --git a/framework/test/mocha/unit/modules/chain/blocks/block.js b/framework/test/mocha/unit/modules/chain/blocks/block.js index dc099ba4678..eed6ab8f067 100644 --- a/framework/test/mocha/unit/modules/chain/blocks/block.js +++ b/framework/test/mocha/unit/modules/chain/blocks/block.js @@ -356,7 +356,7 @@ describe('block', () => { const generatedBlock = block.create({ ...data, transactions, - version: 2, + version: 1, }); expect(generatedBlock).to.not.have.property( From 3bbdc78bd9612db1c3e9852bd1df9175f2c265a9 Mon Sep 17 00:00:00 2001 From: michielmulders Date: Thu, 13 Jun 2019 18:14:03 +0200 Subject: [PATCH 11/25] Fix all blocks related tests --- .../unit/components/storage/entities/block.js | 16 ++++++++++++ .../unit/modules/chain/blocks/process.js | 16 ++++++++++++ .../mocha/unit/modules/chain/blocks/utils.js | 8 ++++++ .../mocha/unit/modules/chain/blocks/verify.js | 20 +++++++------- .../components/storage/entities/block.js | 26 +++++++++++++++++-- 5 files changed, 74 insertions(+), 12 deletions(-) diff --git a/framework/test/mocha/unit/components/storage/entities/block.js b/framework/test/mocha/unit/components/storage/entities/block.js index 2dce86451e5..f5fbe736897 100644 --- a/framework/test/mocha/unit/components/storage/entities/block.js +++ b/framework/test/mocha/unit/components/storage/entities/block.js @@ -95,6 +95,22 @@ describe('Block', () => { 'height_lt', 'height_lte', 'height_ne', + 'maxHeightPreviouslyForged', + 'maxHeightPreviouslyForged_eql', + 'maxHeightPreviouslyForged_ne', + 'maxHeightPreviouslyForged_gt', + 'maxHeightPreviouslyForged_gte', + 'maxHeightPreviouslyForged_lt', + 'maxHeightPreviouslyForged_lte', + 'maxHeightPreviouslyForged_in', + 'prevotedConfirmedUptoHeight', + 'prevotedConfirmedUptoHeight_eql', + 'prevotedConfirmedUptoHeight_ne', + 'prevotedConfirmedUptoHeight_gt', + 'prevotedConfirmedUptoHeight_gte', + 'prevotedConfirmedUptoHeight_lt', + 'prevotedConfirmedUptoHeight_lte', + 'prevotedConfirmedUptoHeight_in', 'id', 'id_eql', 'id_in', diff --git a/framework/test/mocha/unit/modules/chain/blocks/process.js b/framework/test/mocha/unit/modules/chain/blocks/process.js index cc091e44c43..74ef2c00a8d 100644 --- a/framework/test/mocha/unit/modules/chain/blocks/process.js +++ b/framework/test/mocha/unit/modules/chain/blocks/process.js @@ -301,6 +301,10 @@ describe('blocks/process', () => { maxPayloadLength: constants.maxPayloadLength, keypair, timestamp, + version: blockVersion.getBlockVersion(lastBlock.height + 1), + maxHeightPreviouslyForged: 1, + prevotedConfirmedUptoHeight: 1, + height: lastBlock.height + 1, }); }); }); @@ -372,6 +376,10 @@ describe('blocks/process', () => { maxPayloadLength: constants.maxPayloadLength, keypair, timestamp, + version: blockVersion.getBlockVersion(lastBlock.height + 1), + maxHeightPreviouslyForged: 1, + prevotedConfirmedUptoHeight: 1, + height: lastBlock.height + 1, }); expect(block).to.equal(dummyBlock); }); @@ -401,6 +409,10 @@ describe('blocks/process', () => { maxPayloadLength: constants.maxPayloadLength, keypair, timestamp, + version: blockVersion.getBlockVersion(lastBlock.height + 1), + maxHeightPreviouslyForged: 1, + prevotedConfirmedUptoHeight: 1, + height: lastBlock.height + 1, }); expect(block).to.equal(dummyBlock); }); @@ -427,6 +439,10 @@ describe('blocks/process', () => { maxPayloadLength: constants.maxPayloadLength, keypair, timestamp, + version: blockVersion.getBlockVersion(lastBlock.height + 1), + maxHeightPreviouslyForged: 1, + prevotedConfirmedUptoHeight: 1, + height: lastBlock.height + 1, }); expect(block).to.equal(dummyBlock); }); diff --git a/framework/test/mocha/unit/modules/chain/blocks/utils.js b/framework/test/mocha/unit/modules/chain/blocks/utils.js index b1c93e034ef..0f2dc80c7ee 100644 --- a/framework/test/mocha/unit/modules/chain/blocks/utils.js +++ b/framework/test/mocha/unit/modules/chain/blocks/utils.js @@ -36,6 +36,7 @@ describe('blocks/utils', () => { b_totalAmount: 100, b_totalFee: 10, b_reward: 1, + b_version: 1, }, { b_id: '13068833527549895884', @@ -47,6 +48,7 @@ describe('blocks/utils', () => { b_totalAmount: 100, b_totalFee: 10, b_reward: 1, + b_version: 1, }, { b_id: '7018883617995376402', @@ -58,6 +60,7 @@ describe('blocks/utils', () => { b_totalAmount: 100, b_totalFee: 10, b_reward: 1, + b_version: 1, }, { b_id: '7018883617995376402', @@ -69,6 +72,7 @@ describe('blocks/utils', () => { b_totalAmount: 100, b_totalFee: 10, b_reward: 1, + b_version: 1, }, ]; @@ -256,6 +260,7 @@ describe('blocks/utils', () => { b_totalAmount: 100, b_totalFee: 10, b_reward: 1, + b_version: 1, }, { b_id: '6524861224470851795', @@ -267,6 +272,7 @@ describe('blocks/utils', () => { b_totalAmount: 100, b_totalFee: 10, b_reward: 1, + b_version: 1, }, ]; @@ -888,6 +894,7 @@ describe('blocks/utils', () => { totalAmount: 100, totalFee: 10, reward: 1, + version: 1, }, { id: 3, @@ -897,6 +904,7 @@ describe('blocks/utils', () => { totalAmount: 100, totalFee: 10, reward: 1, + version: 1, }, ]); }); diff --git a/framework/test/mocha/unit/modules/chain/blocks/verify.js b/framework/test/mocha/unit/modules/chain/blocks/verify.js index 1ad29561b0b..ec65950e297 100644 --- a/framework/test/mocha/unit/modules/chain/blocks/verify.js +++ b/framework/test/mocha/unit/modules/chain/blocks/verify.js @@ -316,9 +316,9 @@ describe('blocks/verify', () => { describe('verifyVersion', () => { describe('when there are no exceptions for block versions', () => { describe('when block height provided', () => { - it('should return no error when block version = 1', async () => { + it('should return no error when block version = 2', async () => { const verifyVersion = blocksVerifyModule.verifyVersion( - { version: 1, height: 1 }, + { version: 2, height: 1 }, {}, { errors: [] } ); @@ -336,9 +336,9 @@ describe('blocks/verify', () => { ); }); - it('should return error when block version 2', async () => { + it('should return error when block version 1', async () => { const verifyVersion = blocksVerifyModule.verifyVersion( - { version: 2, height: 1 }, + { version: 1, height: 1 }, {}, { errors: [] } ); @@ -349,18 +349,18 @@ describe('blocks/verify', () => { }); describe('when block height is missing', () => { - it('should return no error when block version = 1', async () => { + it('should return no error when block version = 2', async () => { const verifyVersion = blocksVerifyModule.verifyVersion( - { version: 1 }, + { version: 2 }, {}, { errors: [] } ); return expect(verifyVersion.errors.length).to.equal(0); }); - it('should return error when block version = 2', async () => { + it('should return error when block version = 1', async () => { const verifyVersion = blocksVerifyModule.verifyVersion( - { version: 2 }, + { version: 1 }, {}, { errors: [] } ); @@ -425,7 +425,7 @@ describe('blocks/verify', () => { describe('when block height is missing', () => { it('should return no error when block version = 1', async () => { const verifyVersion = blocksVerifyModule.verifyVersion( - { version: 1 }, + { version: 2 }, blocksVersionException, { errors: [] } ); @@ -434,7 +434,7 @@ describe('blocks/verify', () => { it('should return error when block version = 2', async () => { const verifyVersion = blocksVerifyModule.verifyVersion( - { version: 2 }, + { version: 1 }, blocksVersionException, { errors: [] } ); diff --git a/framework/test/mocha/unit/modules/chain/components/storage/entities/block.js b/framework/test/mocha/unit/modules/chain/components/storage/entities/block.js index 5d795611711..0578c46d13e 100644 --- a/framework/test/mocha/unit/modules/chain/components/storage/entities/block.js +++ b/framework/test/mocha/unit/modules/chain/components/storage/entities/block.js @@ -31,6 +31,7 @@ describe('Block', () => { let invalidFilter; let validFilter; let validBlock; + let validReturnedBlock; let invalidBlock; let storage; @@ -71,6 +72,27 @@ describe('Block', () => { version: '0', }; + validReturnedBlock = { + id: '7807109686729042739', + height: 1, + maxHeightPreviouslyForged: null, + prevotedConfirmedUptoHeight: null, + blockSignature: + 'a47d07d3a8d8024eb44672bc6d07cdcd1cd03803d9612b7b10c10d5a844fb8f6ed11fab5159b6d9826b7302c3d3f5d7d29d13b40e6fe59c9374f4ec94af4eb0f', + generatorPublicKey: + '73ec4adbd8f99f0d46794aeda3c3d86b245bd9d27be2b282cdd38ad21988556b', + payloadHash: + 'da3ed6a45429278bac2666961289ca17ad86595d33b31037615d4b8e8f158bba', + payloadLength: 19619, + numberOfTransactions: 103, + previousBlockId: null, + timestamp: 0, + totalAmount: '10000000000000000', + totalFee: '0', + reward: '0', + version: '0', + }; + invalidBlock = { id: null, height: '1', @@ -143,7 +165,7 @@ describe('Block', () => { const block = new Block(localAdapter); block.getValuesSet = sinonSandbox.stub(); block.create(validBlock); - expect(block.getValuesSet.calledWith([validBlock])).to.be.true; + expect(block.getValuesSet.calledWith([validReturnedBlock])).to.be.true; }); it('should create a block object successfully', async () => { @@ -152,7 +174,7 @@ describe('Block', () => { id: validBlock.id, }); expect(result).to.be.eql({ - ...validBlock, + ...validReturnedBlock, confirmations: 1, }); }); From fa1bb6907955c12297336f80f59951ba4754da7f Mon Sep 17 00:00:00 2001 From: michielmulders Date: Fri, 14 Jun 2019 13:56:20 +0200 Subject: [PATCH 12/25] Remove context usage block creation --- framework/src/modules/chain/blocks/process.js | 9 ++------- 1 file changed, 2 insertions(+), 7 deletions(-) diff --git a/framework/src/modules/chain/blocks/process.js b/framework/src/modules/chain/blocks/process.js index 4620aae72d5..ca54b864fc4 100644 --- a/framework/src/modules/chain/blocks/process.js +++ b/framework/src/modules/chain/blocks/process.js @@ -91,11 +91,6 @@ class BlocksProcess { async generateBlock(lastBlock, keypair, timestamp, transactions) { const context = { blockTimestamp: timestamp, - blockHeight: lastBlock.height + 1, - blockVersion: blockVersion.getBlockVersion( - lastBlock.height + 1, - this.exceptions - ), }; const allowedTransactionsIds = transactionsModule @@ -129,8 +124,8 @@ class BlocksProcess { timestamp, prevotedConfirmedUptoHeight: 1, maxHeightPreviouslyForged: 1, - height: context.blockHeight, - version: context.blockVersion, + height: lastBlock.height + 1, + version: blockVersion.getBlockVersion(lastBlock.height + 1), }); } From 31576a935da5085425eab2e1e43bf1fb5fc30ac6 Mon Sep 17 00:00:00 2001 From: michielmulders Date: Mon, 17 Jun 2019 10:31:09 +0200 Subject: [PATCH 13/25] Fix naming conventions files and interface --- framework/src/modules/chain/blocks/block.js | 50 +++++++------------ .../chain/blocks/{blockV1.js => block_v1.js} | 33 +++++------- .../chain/blocks/{blockV2.js => block_v2.js} | 24 ++++----- 3 files changed, 43 insertions(+), 64 deletions(-) rename framework/src/modules/chain/blocks/{blockV1.js => block_v1.js} (95%) rename framework/src/modules/chain/blocks/{blockV2.js => block_v2.js} (96%) diff --git a/framework/src/modules/chain/blocks/block.js b/framework/src/modules/chain/blocks/block.js index 22ed0ab3a0f..2e83cb2ad8b 100644 --- a/framework/src/modules/chain/blocks/block.js +++ b/framework/src/modules/chain/blocks/block.js @@ -14,57 +14,45 @@ 'use strict'; -const { - createV1, - getBytesV0, - getBytesV1, - dbReadV0, - dbReadV1, - storageReadV0, - storageReadV1, - verifySignature, - getId, - getHash, - objectNormalize, - sign, -} = require('./blockV1'); -const { createV2, getBytesV2, dbReadV2, storageReadV2 } = require('./blockV2'); +const blockV1 = require('./block_v1'); +const blockV2 = require('./block_v2'); const createFunc = { - 1: createV1, - 2: createV2, + 0: blockV1.create, + 1: blockV1.create, + 2: blockV2.create, }; const create = data => createFunc[data.version](data); const getBytesFunc = { - 0: getBytesV0, - 1: getBytesV1, - 2: getBytesV2, + 0: blockV1.getBytes, + 1: blockV1.getBytes, + 2: blockV2.getBytes, }; const getBytes = block => getBytesFunc[block.version](block); const dbReadFunc = { - 0: dbReadV0, - 1: dbReadV1, - 2: dbReadV2, + 0: blockV1.dbRead, + 1: blockV1.dbRead, + 2: blockV2.dbRead, }; const dbRead = raw => dbReadFunc[raw.b_version](raw); const storageReadFunc = { - 0: storageReadV0, - 1: storageReadV1, - 2: storageReadV2, + 0: blockV1.storageRead, + 1: blockV1.storageRead, + 2: blockV2.storageRead, }; const storageRead = raw => storageReadFunc[raw.version](raw); module.exports = { - sign, - getHash, - getId, + sign: blockV1.sign, + getHash: blockV1.getHash, + getId: blockV1.getId, create, dbRead, storageRead, getBytes, - verifySignature, - objectNormalize, + verifySignature: blockV1.verifySignature, + objectNormalize: blockV1.objectNormalize, }; diff --git a/framework/src/modules/chain/blocks/blockV1.js b/framework/src/modules/chain/blocks/block_v1.js similarity index 95% rename from framework/src/modules/chain/blocks/blockV1.js rename to framework/src/modules/chain/blocks/block_v1.js index 395968fc339..2501eb00045 100644 --- a/framework/src/modules/chain/blocks/blockV1.js +++ b/framework/src/modules/chain/blocks/block_v1.js @@ -41,7 +41,7 @@ const TRANSACTION_TYPES_MULTI = 4; * @returns {!Array} Contents as an ArrayBuffer * @todo Add description for the function and the params */ -const getBytesV0 = block => { +const getBytes = block => { const capacity = 4 + // version (int) 4 + // timestamp (int) @@ -100,8 +100,6 @@ const getBytesV0 = block => { return byteBuffer.toBuffer(); }; -const getBytesV1 = block => getBytesV0(block); - /** * Sorts input data transactions. * Calculates reward based on previous block data. @@ -111,7 +109,7 @@ const getBytesV1 = block => getBytesV0(block); * @returns {block} block * @todo Add description for the params */ -const createV1 = ({ +const create = ({ blockReward, transactions, previousBlock, @@ -205,7 +203,7 @@ const createV1 = ({ * @returns {null|block} Block object * @todo Add description for the params */ -const dbReadV0 = raw => { +const dbRead = raw => { if (!raw.b_id) { return null; } @@ -230,15 +228,13 @@ const dbReadV0 = raw => { return block; }; -const dbReadV1 = raw => dbReadV0(raw); - /** * Creates block object based on raw database block data. * * @param {Object} raw Raw database data block object * @returns {null|block} Block object */ -const storageReadV0 = raw => { +const storageRead = raw => { if (!raw.id) { return null; } @@ -272,8 +268,6 @@ const storageReadV0 = raw => { return block; }; -const storageReadV1 = raw => storageReadV0(raw); - /** * Creates a block signature. * @@ -283,7 +277,7 @@ const storageReadV1 = raw => storageReadV0(raw); * @todo Add description for the params */ const sign = (block, keypair) => - signDataWithPrivateKey(hash(getBytesV1(block)), keypair.privateKey); + signDataWithPrivateKey(hash(getBytes(block)), keypair.privateKey); /** * Creates hash based on block bytes. @@ -292,7 +286,7 @@ const sign = (block, keypair) => * @returns {Buffer} SHA256 hash * @todo Add description for the params */ -const getHash = block => hash(getBytesV1(block)); +const getHash = block => hash(getBytes(block)); /** * Description of the function. @@ -335,7 +329,7 @@ const objectNormalize = (block, exceptions = {}) => { */ const verifySignature = block => { const signatureLength = 64; - const data = getBytesV1(block); + const data = getBytes(block); const dataWithoutSignature = Buffer.alloc(data.length - signatureLength); for (let i = 0; i < dataWithoutSignature.length; i++) { @@ -357,7 +351,7 @@ const verifySignature = block => { * @todo Add description for the params */ const getId = block => { - const hashedBlock = hash(getBytesV1(block)); + const hashedBlock = hash(getBytes(block)); const temp = Buffer.alloc(8); for (let i = 0; i < 8; i++) { temp[i] = hashedBlock[7 - i]; @@ -450,13 +444,10 @@ const blockSchema = { }; module.exports = { - getBytesV0, - getBytesV1, - createV1, - dbReadV0, - dbReadV1, - storageReadV0, - storageReadV1, + getBytes, + create, + dbRead, + storageRead, verifySignature, getId, getHash, diff --git a/framework/src/modules/chain/blocks/blockV2.js b/framework/src/modules/chain/blocks/block_v2.js similarity index 96% rename from framework/src/modules/chain/blocks/blockV2.js rename to framework/src/modules/chain/blocks/block_v2.js index b5e2c24abd4..1b268a65991 100644 --- a/framework/src/modules/chain/blocks/blockV2.js +++ b/framework/src/modules/chain/blocks/block_v2.js @@ -41,7 +41,7 @@ const TRANSACTION_TYPES_MULTI = 4; * @returns {!Array} Contents as an ArrayBuffer * @todo Add description for the function and the params */ -const getBytesV2 = block => { +const getBytes = block => { const capacity = 4 + // version (int) 4 + // timestamp (int) @@ -115,7 +115,7 @@ const getBytesV2 = block => { * @returns {block} block * @todo Add description for the params */ -const createV2 = ({ +const create = ({ blockReward, transactions, previousBlock, @@ -214,7 +214,7 @@ const createV2 = ({ * @returns {null|block} Block object * @todo Add description for the params */ -const dbReadV2 = raw => { +const dbRead = raw => { if (!raw.b_id) { return null; } @@ -247,7 +247,7 @@ const dbReadV2 = raw => { * @param {Object} raw Raw database data block object * @returns {null|block} Block object */ -const storageReadV2 = raw => { +const storageRead = raw => { if (!raw.id) { return null; } @@ -292,7 +292,7 @@ const storageReadV2 = raw => { * @todo Add description for the params */ const sign = (block, keypair) => - signDataWithPrivateKey(hash(getBytesV2(block)), keypair.privateKey); + signDataWithPrivateKey(hash(getBytes(block)), keypair.privateKey); /** * Creates hash based on block bytes. @@ -301,7 +301,7 @@ const sign = (block, keypair) => * @returns {Buffer} SHA256 hash * @todo Add description for the params */ -const getHash = block => hash(getBytesV2(block)); +const getHash = block => hash(getBytes(block)); /** * Description of the function. @@ -344,7 +344,7 @@ const objectNormalize = (block, exceptions = {}) => { */ const verifySignature = block => { const signatureLength = 64; - const data = getBytesV2(block); + const data = getBytes(block); const dataWithoutSignature = Buffer.alloc(data.length - signatureLength); for (let i = 0; i < dataWithoutSignature.length; i++) { @@ -366,7 +366,7 @@ const verifySignature = block => { * @todo Add description for the params */ const getId = block => { - const hashedBlock = hash(getBytesV2(block)); + const hashedBlock = hash(getBytes(block)); const temp = Buffer.alloc(8); for (let i = 0; i < 8; i++) { temp[i] = hashedBlock[7 - i]; @@ -459,10 +459,10 @@ const blockSchema = { }; module.exports = { - getBytesV2, - createV2, - dbReadV2, - storageReadV2, + getBytes, + create, + dbRead, + storageRead, verifySignature, getId, getHash, From b36581339a533fbcb0dd2fa1d1543bf3ad5dc4f1 Mon Sep 17 00:00:00 2001 From: michielmulders Date: Mon, 17 Jun 2019 11:02:03 +0200 Subject: [PATCH 14/25] Import explicit lodash functions --- framework/src/modules/chain/blocks/block_v1.js | 4 ++-- framework/src/modules/chain/blocks/block_v2.js | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/framework/src/modules/chain/blocks/block_v1.js b/framework/src/modules/chain/blocks/block_v1.js index 2501eb00045..0e4be96b8a0 100644 --- a/framework/src/modules/chain/blocks/block_v1.js +++ b/framework/src/modules/chain/blocks/block_v1.js @@ -22,7 +22,7 @@ const { hash, verifyData, } = require('@liskhq/lisk-cryptography'); -const _ = require('lodash'); +const { omitBy, isNull } = require('lodash'); const crypto = require('crypto'); const ByteBuffer = require('bytebuffer'); const BigNum = require('@liskhq/bignum'); @@ -260,7 +260,7 @@ const storageRead = raw => { if (raw.transactions) { block.transactions = raw.transactions .filter(tx => !!tx.id) - .map(tx => _.omitBy(tx, _.isNull)); + .map(tx => omitBy(tx, isNull)); } block.totalForged = block.totalFee.plus(block.reward).toString(); diff --git a/framework/src/modules/chain/blocks/block_v2.js b/framework/src/modules/chain/blocks/block_v2.js index 1b268a65991..406472dd635 100644 --- a/framework/src/modules/chain/blocks/block_v2.js +++ b/framework/src/modules/chain/blocks/block_v2.js @@ -22,7 +22,7 @@ const { hash, verifyData, } = require('@liskhq/lisk-cryptography'); -const _ = require('lodash'); +const { omitBy, isNull } = require('lodash'); const crypto = require('crypto'); const ByteBuffer = require('bytebuffer'); const BigNum = require('@liskhq/bignum'); @@ -275,7 +275,7 @@ const storageRead = raw => { if (raw.transactions) { block.transactions = raw.transactions .filter(tx => !!tx.id) - .map(tx => _.omitBy(tx, _.isNull)); + .map(tx => omitBy(tx, isNull)); } block.totalForged = block.totalFee.plus(block.reward).toString(); From 002c63edf7c3b5cfe5d1da73db4e82d47515a77c Mon Sep 17 00:00:00 2001 From: michielmulders Date: Mon, 17 Jun 2019 11:20:07 +0200 Subject: [PATCH 15/25] Alter check block version to be more efficient --- framework/src/modules/chain/blocks/block_version.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/framework/src/modules/chain/blocks/block_version.js b/framework/src/modules/chain/blocks/block_version.js index 7e66a847b06..9e9a552bcb8 100644 --- a/framework/src/modules/chain/blocks/block_version.js +++ b/framework/src/modules/chain/blocks/block_version.js @@ -50,7 +50,7 @@ const isValid = (version, height, exceptions = {}) => { }; const getBlockVersion = (height, exceptions = {}) => { - if (height === undefined || Object.entries(exceptions).length === 0) { + if (height === undefined || !Object.keys(exceptions).length) { return currentBlockVersion; } From e94072f9e25deb20bd69cf7e8dd3f76cafbf62b9 Mon Sep 17 00:00:00 2001 From: michielmulders Date: Mon, 17 Jun 2019 13:50:51 +0200 Subject: [PATCH 16/25] Modify interface for other block functions --- framework/src/modules/chain/blocks/block.js | 46 +++++++++++++++++-- .../unit/modules/chain/blocks/process.js | 2 - 2 files changed, 41 insertions(+), 7 deletions(-) diff --git a/framework/src/modules/chain/blocks/block.js b/framework/src/modules/chain/blocks/block.js index 2e83cb2ad8b..4a126849467 100644 --- a/framework/src/modules/chain/blocks/block.js +++ b/framework/src/modules/chain/blocks/block.js @@ -45,14 +45,50 @@ const storageReadFunc = { }; const storageRead = raw => storageReadFunc[raw.version](raw); +const signFunc = { + 0: blockV1.sign, + 1: blockV1.sign, + 2: blockV2.sign, +}; +const sign = (block, keypair) => signFunc[block.version](block, keypair); + +const getHashFunc = { + 0: blockV1.getHash, + 1: blockV1.getHash, + 2: blockV1.getHash, +}; +const getHash = block => getHashFunc[block.version](block); + +const getIdFunc = { + 0: blockV1.getId, + 1: blockV1.getId, + 2: blockV2.getId, +}; +const getId = block => getIdFunc[block.version](block); + +const verifySignatureFunc = { + 0: blockV1.verifySignature, + 1: blockV1.verifySignature, + 2: blockV2.verifySignature, +}; +const verifySignature = block => verifySignatureFunc[block.version](block); + +const objectNormalizeFunc = { + 0: blockV1.objectNormalize, + 1: blockV1.objectNormalize, + 2: blockV2.objectNormalize, +}; +const objectNormalize = (block, exceptions) => + objectNormalizeFunc[block.version](block, exceptions); + module.exports = { - sign: blockV1.sign, - getHash: blockV1.getHash, - getId: blockV1.getId, + sign, + getHash, + getId, create, dbRead, storageRead, getBytes, - verifySignature: blockV1.verifySignature, - objectNormalize: blockV1.objectNormalize, + verifySignature, + objectNormalize, }; diff --git a/framework/test/mocha/unit/modules/chain/blocks/process.js b/framework/test/mocha/unit/modules/chain/blocks/process.js index 74ef2c00a8d..059af9465d5 100644 --- a/framework/test/mocha/unit/modules/chain/blocks/process.js +++ b/framework/test/mocha/unit/modules/chain/blocks/process.js @@ -460,8 +460,6 @@ describe('blocks/process', () => { ]; const state = { blockTimestamp: timestamp, - blockHeight: lastBlock.height + 1, - blockVersion: blockVersion.currentBlockVersion, }; checkAllowedTransactionsStub.returns({ From 6073d0fca1d1e983750fa9cf327742a88513f971 Mon Sep 17 00:00:00 2001 From: michielmulders Date: Mon, 17 Jun 2019 14:49:45 +0200 Subject: [PATCH 17/25] Fix test integration --- framework/test/mocha/integration/rounds.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/framework/test/mocha/integration/rounds.js b/framework/test/mocha/integration/rounds.js index 1b92c2746af..55e3ce1f2ec 100644 --- a/framework/test/mocha/integration/rounds.js +++ b/framework/test/mocha/integration/rounds.js @@ -411,8 +411,8 @@ describe('rounds', () => { return expect(tick.after.block.id).to.not.equal(tick.before.block.id); }); - it('block version should be 1', async () => { - return expect(tick.after.block.version).to.equal(1); + it('block version should be 2', async () => { + return expect(tick.after.block.version).to.equal(2); }); it('height should be greather by 1', async () => { From 56b5fade03cdcdfb1babbdc4e182fab5b51db02c Mon Sep 17 00:00:00 2001 From: michielmulders Date: Mon, 17 Jun 2019 18:13:29 +0200 Subject: [PATCH 18/25] Add improved switching logic --- framework/src/modules/chain/blocks/block.js | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/framework/src/modules/chain/blocks/block.js b/framework/src/modules/chain/blocks/block.js index 4a126849467..b91bf1cdfa1 100644 --- a/framework/src/modules/chain/blocks/block.js +++ b/framework/src/modules/chain/blocks/block.js @@ -16,13 +16,19 @@ const blockV1 = require('./block_v1'); const blockV2 = require('./block_v2'); +const { getBlockVersion } = require('./block_version'); const createFunc = { 0: blockV1.create, 1: blockV1.create, 2: blockV2.create, }; -const create = data => createFunc[data.version](data); +const create = data => { + const version = + data.version || + getBlockVersion(data.previousBlock.height + 1, data.exceptions); + return createFunc[version](data); +}; const getBytesFunc = { 0: blockV1.getBytes, From 770fae375957905ad695ad72b6ebaff37b2ea5c5 Mon Sep 17 00:00:00 2001 From: michielmulders Date: Wed, 19 Jun 2019 16:49:17 +0200 Subject: [PATCH 19/25] Fixed integration tests and modified block fixture --- framework/src/modules/chain/blocks/block.js | 2 +- .../src/modules/chain/blocks/block_v1.js | 1 - framework/test/mocha/integration/common.js | 3 + .../chain/blocks/{block.js => block_v1.js} | 294 ++------ .../unit/modules/chain/blocks/block_v2.js | 667 ++++++++++++++++++ 5 files changed, 744 insertions(+), 223 deletions(-) rename framework/test/mocha/unit/modules/chain/blocks/{block.js => block_v1.js} (74%) create mode 100644 framework/test/mocha/unit/modules/chain/blocks/block_v2.js diff --git a/framework/src/modules/chain/blocks/block.js b/framework/src/modules/chain/blocks/block.js index b91bf1cdfa1..de52da7fdaf 100644 --- a/framework/src/modules/chain/blocks/block.js +++ b/framework/src/modules/chain/blocks/block.js @@ -61,7 +61,7 @@ const sign = (block, keypair) => signFunc[block.version](block, keypair); const getHashFunc = { 0: blockV1.getHash, 1: blockV1.getHash, - 2: blockV1.getHash, + 2: blockV2.getHash, }; const getHash = block => getHashFunc[block.version](block); diff --git a/framework/src/modules/chain/blocks/block_v1.js b/framework/src/modules/chain/blocks/block_v1.js index 0e4be96b8a0..823b29d8837 100644 --- a/framework/src/modules/chain/blocks/block_v1.js +++ b/framework/src/modules/chain/blocks/block_v1.js @@ -39,7 +39,6 @@ const TRANSACTION_TYPES_MULTI = 4; * @param {block} block * @throws {Error} * @returns {!Array} Contents as an ArrayBuffer - * @todo Add description for the function and the params */ const getBytes = block => { const capacity = diff --git a/framework/test/mocha/integration/common.js b/framework/test/mocha/integration/common.js index 0e7f471a62e..7348e94fe11 100644 --- a/framework/test/mocha/integration/common.js +++ b/framework/test/mocha/integration/common.js @@ -75,6 +75,7 @@ function createBlock( transactions = transactions.map(transaction => library.modules.interfaceAdapters.transactions.fromJson(transaction) ); + // TODO Remove hardcoded values and use from BFT class const block = blocksLogic.create({ blockReward: library.modules.blocks.blockReward, keypair, @@ -83,6 +84,8 @@ function createBlock( transactions, maxPayloadLength: __testContext.config.constants.MAX_PAYLOAD_LENGTH, exceptions, + maxHeightPreviouslyForged: 1, + prevotedConfirmedUptoHeight: 1, }); block.id = blocksLogic.getId(block); diff --git a/framework/test/mocha/unit/modules/chain/blocks/block.js b/framework/test/mocha/unit/modules/chain/blocks/block_v1.js similarity index 74% rename from framework/test/mocha/unit/modules/chain/blocks/block.js rename to framework/test/mocha/unit/modules/chain/blocks/block_v1.js index eed6ab8f067..f8cc4dd938f 100644 --- a/framework/test/mocha/unit/modules/chain/blocks/block.js +++ b/framework/test/mocha/unit/modules/chain/blocks/block_v1.js @@ -112,46 +112,6 @@ describe('block', () => { }, }; - const validDataForBlockv2 = { - maxPayloadLength, - blockReward, - keypair: validKeypair, - timestamp: 41898500, - previousBlock: { - version: 2, - totalAmount: '0', - totalFee: '0', - reward: '0', - payloadHash: - 'e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855', - timestamp: 41898490, - numberOfTransactions: 0, - payloadLength: 0, - previousBlock: '1087874036928524397', - generatorPublicKey: - '1cc68fa0b12521158e09779fd5978ccc0ac26bf99320e00a9549b542dd9ada16', - transactions: [], - blockSignature: - '8a727cc77864b6fc81755a1f4eb4796b68f4a943d69c74a043b5ca422f3b05608a22da4a916ca7b721d096129938b6eb3381d75f1a116484d1ce2be4904d9a0e', - height: 99, - id: '3920300554926889269', - relays: 1, - }, - transactions: [], - exceptions: { - blockVersions: { - 1: { - start: 1, - end: 7, - }, - 2: { - start: 8, - end: 6901027, - }, - }, - }, - }; - const invalidBlock = { version: '0', totalAmount: 'qwer', @@ -160,11 +120,6 @@ describe('block', () => { }; const blockData = validDataForBlock.previousBlock; - const blockDataV2 = { - ...blockData, - maxHeightPreviouslyForged: 1, - prevotedConfirmedUptoHeight: 1, - }; const transactionsByTypes = {}; transactionsByTypes[TRANSACTION_TYPES_MULTI] = { @@ -351,35 +306,17 @@ describe('block', () => { }); describe('create', () => { - describe('createV1', () => { - it("shouldn't have maxHeightPreviouslyForged and prevotedConfirmedUptoHeight properties", async () => { - const generatedBlock = block.create({ - ...data, - transactions, - version: 1, - }); - - expect(generatedBlock).to.not.have.property( - 'maxHeightPreviouslyForged' - ); - return expect(generatedBlock).to.not.have.property( - 'prevotedConfirmedUptoHeight' - ); + it("shouldn't have maxHeightPreviouslyForged and prevotedConfirmedUptoHeight properties", async () => { + const generatedBlock = block.create({ + ...data, + transactions, + version: 1, }); - }); - describe('createV2', () => { - it('should have maxHeightPreviouslyForged and prevotedConfirmedUptoHeight properties', async () => { - const generatedBlock = block.create({ - ..._.cloneDeep(validDataForBlockv2), - transactions, - version: 2, - maxHeightPreviouslyForged: 1, - prevotedConfirmedUptoHeight: 1, - }); - expect(generatedBlock.maxHeightPreviouslyForged).to.eql(1); - return expect(generatedBlock.prevotedConfirmedUptoHeight).to.eql(1); - }); + expect(generatedBlock).to.not.have.property('maxHeightPreviouslyForged'); + return expect(generatedBlock).to.not.have.property( + 'prevotedConfirmedUptoHeight' + ); }); describe('when each of all supported', () => { @@ -550,57 +487,28 @@ describe('block', () => { }); describe('getBytes', () => { - describe('getBytesV0-1', () => { - it('should throw error for invalid block', async () => - expect(() => { - block.getBytes(invalidBlock); - }).to.throw()); - - it('should return a buffer for a given block', async () => - expect(block.getBytes(blockData)).to.be.an.instanceof(Buffer)); - - it('should return same bytes for a given block', async () => { - const bytes1 = block.getBytes(blockData); - const bytes2 = block.getBytes(blockData); - return expect(bytes1).to.deep.equal(bytes2); - }); - - it('should return different bytes for different blocks', async () => { - const bytes1 = block.getBytes(blockData); - const blockDataCopy = Object.assign({}, blockData); - blockDataCopy.height = 100; - blockDataCopy.generatorPublicKey = - '7e632b62d6230bfc15763f06bf82f7e20cf06a2d8a356850e0bdab30db3506cc'; - const bytes2 = block.getBytes(blockDataCopy); - return expect(bytes1).to.not.deep.equal(bytes2); - }); - }); - - describe('getBytesV2', () => { - it('should throw error for invalid block', async () => - expect(() => { - const invalidBlockV2 = { ...invalidBlock, version: 2 }; - block.getBytes(invalidBlockV2); - }).to.throw()); + it('should throw error for invalid block', async () => + expect(() => { + block.getBytes(invalidBlock); + }).to.throw()); - it('should return a buffer for a given block', async () => - expect(block.getBytes(blockDataV2)).to.be.an.instanceof(Buffer)); + it('should return a buffer for a given block', async () => + expect(block.getBytes(blockData)).to.be.an.instanceof(Buffer)); - it('should return same bytes for a given block', async () => { - const bytes1 = block.getBytes(blockDataV2); - const bytes2 = block.getBytes(blockDataV2); - return expect(bytes1).to.deep.equal(bytes2); - }); + it('should return same bytes for a given block', async () => { + const bytes1 = block.getBytes(blockData); + const bytes2 = block.getBytes(blockData); + return expect(bytes1).to.deep.equal(bytes2); + }); - it('should return different bytes for different blocks', async () => { - const bytes1 = block.getBytes(blockDataV2); - const blockDataCopy = Object.assign({}, blockDataV2); - blockDataCopy.height = 100; - blockDataCopy.generatorPublicKey = - '7e632b62d6230bfc15763f06bf82f7e20cf06a2d8a356850e0bdab30db3506cc'; - const bytes2 = block.getBytes(blockDataCopy); - return expect(bytes1).to.not.deep.equal(bytes2); - }); + it('should return different bytes for different blocks', async () => { + const bytes1 = block.getBytes(blockData); + const blockDataCopy = Object.assign({}, blockData); + blockDataCopy.height = 100; + blockDataCopy.generatorPublicKey = + '7e632b62d6230bfc15763f06bf82f7e20cf06a2d8a356850e0bdab30db3506cc'; + const bytes2 = block.getBytes(blockDataCopy); + return expect(bytes1).to.not.deep.equal(bytes2); }); }); @@ -706,108 +614,52 @@ describe('block', () => { }); describe('dbRead', () => { - describe('dbRead V1', () => { - it('should throw error for null values', async () => - expect(() => { - block.dbRead(null); - }).to.throw("Cannot read property 'b_version' of null")); - - it('should return raw block data', async () => { - const rawBlock = { - b_version: 0, - b_totalAmount: 0, - b_totalFee: 0, - b_reward: 0, - b_payloadHash: - 'e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855', - b_timestamp: 41898490, - b_numberOfTransactions: 0, - b_payloadLength: 0, - b_previousBlock: '1087874036928524397', - b_generatorPublicKey: - '1cc68fa0b12521158e09779fd5978ccc0ac26bf99320e00a9549b542dd9ada16', - b_transactions: [], - b_blockSignature: - '8a727cc77864b6fc81755a1f4eb4796b68f4a943d69c74a043b5ca422f3b05608a22da4a916ca7b721d096129938b6eb3381d75f1a116484d1ce2be4904d9a0e', - b_height: 6, - b_id: '3920300554926889269', - b_relays: 1, - b_confirmations: 0, - }; - - return expect(block.dbRead(rawBlock)).to.contain.keys( - 'id', - 'version', - 'timestamp', - 'height', - 'previousBlock', - 'numberOfTransactions', - 'totalAmount', - 'totalFee', - 'reward', - 'payloadLength', - 'payloadHash', - 'generatorPublicKey', - 'generatorId', - 'blockSignature', - 'confirmations', - 'totalForged' - ); - }); - }); + it('should throw error for null values', async () => + expect(() => { + block.dbRead(null); + }).to.throw("Cannot read property 'b_version' of null")); + + it('should return raw block data', async () => { + const rawBlock = { + b_version: 0, + b_totalAmount: 0, + b_totalFee: 0, + b_reward: 0, + b_payloadHash: + 'e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855', + b_timestamp: 41898490, + b_numberOfTransactions: 0, + b_payloadLength: 0, + b_previousBlock: '1087874036928524397', + b_generatorPublicKey: + '1cc68fa0b12521158e09779fd5978ccc0ac26bf99320e00a9549b542dd9ada16', + b_transactions: [], + b_blockSignature: + '8a727cc77864b6fc81755a1f4eb4796b68f4a943d69c74a043b5ca422f3b05608a22da4a916ca7b721d096129938b6eb3381d75f1a116484d1ce2be4904d9a0e', + b_height: 6, + b_id: '3920300554926889269', + b_relays: 1, + b_confirmations: 0, + }; - describe('dbRead V2', () => { - it('should throw error for null values', async () => - expect(() => { - block.dbRead(null); - }).to.throw("Cannot read property 'b_version' of null")); - - it('should return raw block data', async () => { - const rawBlock = { - b_version: 2, - b_totalAmount: 0, - b_totalFee: 0, - b_reward: 0, - b_payloadHash: - 'e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855', - b_timestamp: 41898490, - b_numberOfTransactions: 0, - b_payloadLength: 0, - b_previousBlock: '1087874036928524397', - b_generatorPublicKey: - '1cc68fa0b12521158e09779fd5978ccc0ac26bf99320e00a9549b542dd9ada16', - b_transactions: [], - b_blockSignature: - '8a727cc77864b6fc81755a1f4eb4796b68f4a943d69c74a043b5ca422f3b05608a22da4a916ca7b721d096129938b6eb3381d75f1a116484d1ce2be4904d9a0e', - b_height: 6, - b_maxHeightPreviouslyForged: 1, - b_prevotedConfirmedUptoHeight: 1, - b_id: '3920300554926889269', - b_relays: 1, - b_confirmations: 0, - }; - - return expect(block.dbRead(rawBlock)).to.contain.keys( - 'id', - 'version', - 'timestamp', - 'height', - 'maxHeightPreviouslyForged', - 'prevotedConfirmedUptoHeight', - 'previousBlock', - 'numberOfTransactions', - 'totalAmount', - 'totalFee', - 'reward', - 'payloadLength', - 'payloadHash', - 'generatorPublicKey', - 'generatorId', - 'blockSignature', - 'confirmations', - 'totalForged' - ); - }); + return expect(block.dbRead(rawBlock)).to.contain.keys( + 'id', + 'version', + 'timestamp', + 'height', + 'previousBlock', + 'numberOfTransactions', + 'totalAmount', + 'totalFee', + 'reward', + 'payloadLength', + 'payloadHash', + 'generatorPublicKey', + 'generatorId', + 'blockSignature', + 'confirmations', + 'totalForged' + ); }); }); }); diff --git a/framework/test/mocha/unit/modules/chain/blocks/block_v2.js b/framework/test/mocha/unit/modules/chain/blocks/block_v2.js new file mode 100644 index 00000000000..71c7fa992bd --- /dev/null +++ b/framework/test/mocha/unit/modules/chain/blocks/block_v2.js @@ -0,0 +1,667 @@ +/* + * Copyright © 2018 Lisk Foundation + * + * See the LICENSE file at the top-level directory of this distribution + * for licensing information. + * + * Unless otherwise agreed in a custom licensing agreement with the Lisk Foundation, + * no part of this software, including this file, may be copied, modified, + * propagated, or distributed except according to the terms contained in the + * LICENSE file. + * + * Removal or modification of this copyright notice is prohibited. + */ + +'use strict'; + +const { + getPrivateAndPublicKeyBytesFromPassphrase, +} = require('@liskhq/lisk-cryptography'); +const { + registeredTransactions, +} = require('../../../../common/registered_transactions'); +const { + TransactionInterfaceAdapter, +} = require('../../../../../../src/modules/chain/interface_adapters'); +const block = require('../../../../../../src/modules/chain/blocks/block'); +const validator = require('../../../../../../src/controller/validator'); +const { + calculateSupply, + calculateReward, + calculateMilestone, +} = require('../../../../../../src/modules/chain/blocks/block_reward'); + +describe('block', () => { + const interfaceAdapters = { + transactions: new TransactionInterfaceAdapter(registeredTransactions), + }; + const TRANSACTION_TYPES_SEND = 0; + const TRANSACTION_TYPES_SIGNATURE = 1; + const TRANSACTION_TYPES_DELEGATE = 2; + const TRANSACTION_TYPES_VOTE = 3; + const TRANSACTION_TYPES_MULTI = 4; + const blockRewardArgs = { + distance: 3000000, + rewardOffset: 2160, + milestones: [ + '500000000', // Initial Reward + '400000000', // Milestone 1 + '300000000', // Milestone 2 + '200000000', // Milestone 3 + '100000000', // Milestone 4 + ], + totalAmount: '10000000000000000', + }; + + const blockReward = { + calculateMilestone: height => calculateMilestone(height, blockRewardArgs), + calculateSupply: height => calculateSupply(height, blockRewardArgs), + calculateReward: height => calculateReward(height, blockRewardArgs), + }; + const maxPayloadLength = 1024 * 1024; + + const validPassphrase = + 'robust weapon course unknown head trial pencil latin acid'; + + const validKeypairBytes = getPrivateAndPublicKeyBytesFromPassphrase( + validPassphrase + ); + + const validKeypair = { + publicKey: validKeypairBytes.publicKeyBytes, + privateKey: validKeypairBytes.privateKeyBytes, + }; + + const validDataForBlock = { + maxPayloadLength, + blockReward, + keypair: validKeypair, + timestamp: 41898500, + previousBlock: { + version: 2, + totalAmount: '0', + totalFee: '0', + reward: '0', + payloadHash: + 'e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855', + timestamp: 41898490, + numberOfTransactions: 0, + payloadLength: 0, + previousBlock: '1087874036928524397', + generatorPublicKey: + '1cc68fa0b12521158e09779fd5978ccc0ac26bf99320e00a9549b542dd9ada16', + transactions: [], + blockSignature: + '056b613a45589241b7e5c61e10b0f387a1fe9a4d7fe7c93ed9a8710725801c055f850b83e2b1484c61e42ac55d3f7c6ec7ec3ceb5e90ed44a9d8aaa8f334130c', + height: 99, + id: '3956936115999704352', + relays: 1, + maxHeightPreviouslyForged: 1, + prevotedConfirmedUptoHeight: 1, + }, + maxHeightPreviouslyForged: 1, + prevotedConfirmedUptoHeight: 1, + transactions: [], + exceptions: { + blockVersions: { + 1: { + start: 1, + end: 7, + }, + 2: { + start: 8, + end: 6901027, + }, + }, + }, + }; + + const invalidBlock = { + version: '2', + totalAmount: 'qwer', + totalFee: 'sd#$%', + reward: '0', + }; + + const blockData = { + ...validDataForBlock.previousBlock, + }; + + const transactionsByTypes = {}; + transactionsByTypes[TRANSACTION_TYPES_MULTI] = { + type: 4, + amount: '0', + senderPublicKey: + '7e632b62d6230bfc15763f06bf82f7e20cf06a2d8a356850e0bdab30db3506cc', + timestamp: 41898871, + asset: { + multisignature: { + min: 15, + keysgroup: [ + '+2f57dcb099eda355fa2577ebe994fe5d8a6a01c1926bd317dd0326c2029e47a9', + '+f0612905f5b7a7c1e3b32cdb9801df3431441d03b4b00995211114495c71949a', + '+fdf0de469127d5b49fb45c5c240cf96cac6391156908d5e92998ddf0855ed2ef', + '+b515673c1e9f2249f84456b5d79c0e283bd56ac46b1f309549eac8fa489b787a', + '+fe5101e08bbacac9eb008d4b4e4afc4ff21ea989956fd8ef0fe2a2fb600091cb', + '+5d6d1d2f86a1fbf851434c826a763261bf6019544013e526076bb689c37096ef', + '+9dbd12cea90057a3b5323c69140c61f98bc7e9e3c033264c7b2652db35cf12e6', + '+56ea0e04273e6fdd45be4e61464e6eef7e5b9a8d38127bab2c0730bf8545dbad', + '+f570825ef2cf7b058881a37df2ee1be593867e6b0ad2a4f51e435857051f0aec', + '+da2f5cbb34914590776e142d81d0614a8d8c967574de3e6e0d6c74fd2e4c73f4', + '+df727cb7f913d235040aea33a8b9d506c310aeb1afdea4d997ff4a333942a1c5', + '+b19ae6459ea5307a7ffada806a2b0fb3d69aaf8017479148847edf4518b59584', + '+b2a2045281d9db97ab130afa35aa402d208481f4d15bce804053502fe0e5f742', + '+e0e7be96e5ddd6f89026d59183bdf7fc40dcf7f524c4092d778330913eebc82f', + '+6d834168362e11d409b9e326ceea89f41d05d2742ee24066205ab1f87bbcf9c4', + ], + lifetime: 9, + }, + }, + signature: + '3b064ec3b4d21311c5ab3b6621eecf91b9c9398fafdd743e05d53b75a2a37aedb8c0f81e3f32981f7bc42b1a5d4f8aa697e1684e55d853dc2c4964538fedf101', + id: '4505208715241906348', + fee: '8000000000', + senderId: '8885132815244884080L', + relays: 1, + receivedAt: '2017-09-21T15:34:31.532Z', + signatures: [ + '2087dab4154e44e81adb2b93f2e7daab083e824af9986ae897819727dc41583f86498361efee23acbbec319b81f26ac00eebac3b5019e8f0e145937de32f5303', + 'bab64c25c72a310cd5964bdf3d4ff56d65e53b460f17990b09fdf0f63d53310edcb9a7a54b542d23efa2603f15eb52356dee7a11d7e0fd0270efdd297de3ff0e', + 'a4e3df423674aea8a62c8a3014407200d6317c95a07f34606a83a911bfe9f1cdc1c2fac3c946655a1781bc4496542ef1b3e4e791ea4ac7d6229b29e56b673102', + 'b6a5c220421d2bc4fed8f912fa8f37a2d42762b592c92b102fc552be60a6a5a2232b065d83c0b50ef74a09e132603a9b70d71c7417b91d9cae012c292faa3901', + '820df86ab4e5c8f383cfd80990d620fa6f9d66995b5e58b76a8a02c9478d1548539c9e0fb19576f056c27daaf9a9a1ae06114a362131c01f5f7d02cfe27c080a', + 'a9fe643d6dedc1d875a3adbddceb8af94e1755db329492f4c4ff4d429a3354d6ed18e060e96cfb2b2106107649baba7b0bf4a162c4c3261540d1fbcc1351190e', + 'a351d25887e63f9e53534461aa627a8c16c8c33eeb2928ee34ba03968cc5dca8ba7831e3f396f506c903b9e3b8049729502400190cf0a82b4d53a76df7e99808', + '390559f87575c280de63099b0aa9266fe918dfb7a487f821cc16bea568116e75b0c3215634aaf6f64e7130e38fa32686573741386dcd5ef0aa84ca4ba850d405', + '0671a9a3cfdd85ecd54218468254b2f007bbaa613b0b140a5a4a727af3e3c840d952675c6dbbd3f55bee4f2f3aa4bb1331f8e4dcc925c85e0eaf2ad208ae6a06', + 'f6808ff0fb6aa387e3b2cad29d3930a7768b634eac128531c84129eddace3ab9c54316820f497cd6f50b61a83d85811cec4e94e4a50350a982dfba65aea44f00', + 'b75f4815f5ae1f55019001dc94110d7b93c1806ce2ea75a07b969fcfd9e09ac743d0d5dfff110849c675952a7030fd97fce93de9357c5fa601ca62f96e96ef03', + 'bab8ef2384253ea26d6e2b9c22301d2cb7fdabdfce7ffbdf63e0f8e6001a12e5a749cacf5ea01fbb4335a9163654ba1a41154696bb2344384298af79910c9904', + '874ff0769edb4c1078b81099b18414e66c898c6d32e507d98fe0aaa9f7d9fa2e7069745591aa7d73373ab639282b8b3c63e64d82d601ebaf73e2f02701fd4005', + '8b7ceafb67380ae4811a9db63487091c69712eea211be2ebbdaeb5fbfd99bdc9ef82db15553de172b28fc14abbbf1005cc81ba5f3740101eb63d4d65a408000b', + '430cc8ed8008d3270cc15a59b3d11143e70ac606ab4de67edb3e6ba984787bf80a1692c24bd5bbb15d1ec9d4c27dd863deda093d6ab882083b68949c4a51fa0b', + ], + ready: true, + }; + + transactionsByTypes[TRANSACTION_TYPES_VOTE] = { + type: 3, + amount: '0', + senderPublicKey: + '7e632b62d6230bfc15763f06bf82f7e20cf06a2d8a356850e0bdab30db3506cc', + timestamp: 41898871, + asset: { + votes: [ + '+addb0e15a44b0fdc6ff291be28d8c98f5551d0cd9218d749e30ddb87c6e31ca9', + ], + }, + recipientId: '8885132815244884080L', + signature: + 'e8fb21b923ed5b5d2ad0eced31b0a966d9dfb71f710ec6e745c96c9e806ac42225a81e8140614541b0b9055c5511ea8f2d82008f9ccb1bb432772960614d9602', + id: '17417762698516786715', + fee: '100000000', + senderId: '8885132815244884080L', + relays: 1, + receivedAt: '2017-09-21T15:34:31.780Z', + }; + + transactionsByTypes[TRANSACTION_TYPES_DELEGATE] = { + type: 2, + amount: '0', + senderPublicKey: + '7e632b62d6230bfc15763f06bf82f7e20cf06a2d8a356850e0bdab30db3506cc', + timestamp: 41898871, + asset: { + delegate: { + username: 'gximrm9vf2omzarpsw', + publicKey: + '7e632b62d6230bfc15763f06bf82f7e20cf06a2d8a356850e0bdab30db3506cc', + }, + }, + signature: + '77b0fcb420450e2d02e98d05af50d3577438ba19f38249ac301e9da07ec65a0889309b242642d3b4df7570d70be09adc80e56e68a0ecb9eb72b3ab5070248c0d', + id: '14164546323350881168', + fee: '2500000000', + senderId: '8885132815244884080L', + relays: 1, + receivedAt: '2017-09-21T15:34:31.752Z', + }; + + transactionsByTypes[TRANSACTION_TYPES_SIGNATURE] = { + type: 1, + amount: '0', + senderPublicKey: + '7e632b62d6230bfc15763f06bf82f7e20cf06a2d8a356850e0bdab30db3506cc', + timestamp: 41898871, + asset: { + signature: { + publicKey: + '6052f504732ffffa30311f4975d2da8f83e3089fa91aab33dc79f76da78b7c8f', + }, + }, + signature: + 'a54c6adf96879163ac0b36563f2ff701a9f033bedf7746cef79e8f4de503fbb461322b8b8ebe07c40d5dd484f073e69256fac284af5b507952e7666b693c9b07', + id: '17912996692061248739', + fee: '500000000', + senderId: '8885132815244884080L', + relays: 1, + receivedAt: '2017-09-21T15:34:31.718Z', + }; + + transactionsByTypes[TRANSACTION_TYPES_SEND] = { + type: 0, + amount: '1', + senderPublicKey: + '7e632b62d6230bfc15763f06bf82f7e20cf06a2d8a356850e0bdab30db3506cc', + timestamp: 41898871, + asset: {}, + recipientId: '10881167371402274308L', + signature: + 'ec703b28601a0aaf4141a85493dda1b00a3604fc4903513cc311dbf995b39b41b30241b17d6be2ac281c0b8b2ff5b7031b86ce9a5e0c3a545b76e935f372da06', + id: '18141417978934746512', + fee: '10000000', + senderId: '8885132815244884080L', + relays: 1, + receivedAt: '2017-09-21T15:34:31.689Z', + }; + + function expectedOrderOfTransactions(sortedTransactions) { + let sorted = true; + + for (let i = 0; i < sortedTransactions.length - 1; i++) { + // Transactions should always be in ascending order of types unless next transaction is MULTI + if ( + sortedTransactions[i].type > sortedTransactions[i + 1].type && + sortedTransactions[i + 1].type !== TRANSACTION_TYPES_MULTI + ) { + sorted = false; + return sorted; + } + + // MULTI transaction should always come after all transaction types + if ( + sortedTransactions[i].type < sortedTransactions[i + 1].type && + sortedTransactions[i].type === TRANSACTION_TYPES_MULTI + ) { + sorted = false; + return sorted; + } + + // Within transaction types, the transactions should be ordered in descending order of amount + if ( + sortedTransactions[i].type === sortedTransactions[i + 1].type && + sortedTransactions[i].amount < sortedTransactions[i + 1].amount + ) { + sorted = false; + return sorted; + } + } + + return sorted; + } + + let data; + let transactions = []; + + beforeEach(async () => { + data = _.cloneDeep(validDataForBlock); + transactions = _.values(transactionsByTypes); + transactions = transactions.map(transaction => + interfaceAdapters.transactions.fromJson(transaction) + ); + }); + + describe('create', () => { + it('should have maxHeightPreviouslyForged and prevotedConfirmedUptoHeight properties', async () => { + const generatedBlock = block.create({ + ..._.cloneDeep(validDataForBlock), + transactions, + version: 2, + }); + + expect(generatedBlock.maxHeightPreviouslyForged).to.eql(1); + return expect(generatedBlock.prevotedConfirmedUptoHeight).to.eql(1); + }); + + describe('when each of all supported', () => { + let generatedBlock; + let transactionsOrder; + const correctOrder = [0, 1, 2, 3, 4]; + + beforeEach(done => { + generatedBlock = block.create({ + ...data, + transactions, + version: 2, + }); + transactionsOrder = generatedBlock.transactions.map(trs => trs.type); + done(); + }); + + it('should sort transactions in the correct order', async () => + expect(transactionsOrder).to.eql(correctOrder)); + }); + + describe('when there are multiple multisignature transactions', () => { + const correctOrderOfTransactions = [0, 1, 2, 3, 4, 4, 4, 4, 4, 4]; + + describe('in the beginning', () => { + let multipleMultisigTx; + let generatedBlock; + let transactionsOrder; + + beforeEach(async () => { + sinonSandbox.stub(validator, 'validate'); + // Create 6 multisignature transactions + multipleMultisigTx = Array(...Array(5)).map(() => + interfaceAdapters.transactions.fromJson( + transactionsByTypes[TRANSACTION_TYPES_MULTI] + ) + ); + generatedBlock = block.create({ + ...data, + transactions: multipleMultisigTx.concat(transactions), + version: 2, + }); + transactionsOrder = generatedBlock.transactions.map(trs => trs.type); + }); + + it('should sort transactions in the correct order', async () => { + expect( + expectedOrderOfTransactions(generatedBlock.transactions) + ).to.equal(true); + return expect(transactionsOrder).to.eql(correctOrderOfTransactions); + }); + }); + + describe('at the middle', () => { + let multipleMultisigTx; + let generatedBlock; + let transactionsOrder; + + beforeEach(async () => { + multipleMultisigTx = Array(...Array(5)).map(() => + interfaceAdapters.transactions.fromJson( + transactionsByTypes[TRANSACTION_TYPES_MULTI] + ) + ); + // Add multisig transactions after the 3rd transaction in array + transactions.splice(...[3, 0].concat(multipleMultisigTx)); + generatedBlock = block.create({ + ...data, + transactions, + version: 2, + }); + transactionsOrder = generatedBlock.transactions.map(trs => trs.type); + }); + + it('should sort transactions in the correct order', async () => { + expect( + expectedOrderOfTransactions(generatedBlock.transactions) + ).to.equal(true); + return expect(transactionsOrder).to.eql(correctOrderOfTransactions); + }); + }); + + describe('at the end', () => { + let multipleMultisigTx; + let generatedBlock; + let transactionsOrder; + + beforeEach(async () => { + multipleMultisigTx = Array(...Array(5)).map(() => + interfaceAdapters.transactions.fromJson( + transactionsByTypes[TRANSACTION_TYPES_MULTI] + ) + ); + generatedBlock = block.create({ + ...data, + transactions: transactions.concat(multipleMultisigTx), + version: 2, + }); + transactionsOrder = generatedBlock.transactions.map(trs => trs.type); + }); + + it('should sort transactions in the correct order', async () => { + expect( + expectedOrderOfTransactions(generatedBlock.transactions) + ).to.equal(true); + return expect(transactionsOrder).to.eql(correctOrderOfTransactions); + }); + }); + + describe('shuffled', () => { + let multipleMultisigTx; + let generatedBlock; + let transactionsOrder; + + beforeEach(async () => { + // Create 6 multisignature transactions + multipleMultisigTx = Array(...Array(5)).map(() => + interfaceAdapters.transactions.fromJson( + transactionsByTypes[TRANSACTION_TYPES_MULTI] + ) + ); + generatedBlock = block.create({ + ...data, + transactions: _.shuffle(transactions.concat(multipleMultisigTx)), + version: 2, + }); + transactionsOrder = generatedBlock.transactions.map(trs => trs.type); + }); + + it('should sort transactions in the correct order', async () => { + expect( + expectedOrderOfTransactions(generatedBlock.transactions) + ).to.equal(true); + return expect(transactionsOrder).to.eql(correctOrderOfTransactions); + }); + }); + }); + }); + + describe('sign', () => { + it('should throw error for empty block and validKeypair', async () => + expect(() => { + block.sign({}, validKeypair); + }).to.throw()); + + it('should throw error for invalid key pair and valid blockData', async () => + expect(() => { + block.sign(blockData, 'this is invalid key pair'); + }).to.throw()); + + it('should throw error for a block with unknown fields', async () => { + const unknownBlock = { + verson: 2, + totlFee: '&**&^&', + rewrd: 'g@n!a', + }; + + return expect(() => { + block.sign(unknownBlock, 'this is invalid key pair'); + }).to.throw(); + }); + + it('should return valid signature when sign block using valid keypair', async () => { + const signature = block.sign(blockData, validKeypair); + expect(signature).to.be.a('string'); + return expect(signature).to.have.length.of(128); + }); + }); + + describe('getBytes', () => { + it('should throw error for invalid block', async () => + expect(() => { + block.getBytes(invalidBlock); + }).to.throw()); + + it('should return a buffer for a given block', async () => + expect(block.getBytes(blockData)).to.be.an.instanceof(Buffer)); + + it('should return same bytes for a given block', async () => { + const bytes1 = block.getBytes(blockData); + const bytes2 = block.getBytes(blockData); + return expect(bytes1).to.deep.equal(bytes2); + }); + + it('should return different bytes for different blocks', async () => { + const bytes1 = block.getBytes(blockData); + const blockDataCopy = Object.assign({}, blockData); + blockDataCopy.height = 100; + blockDataCopy.generatorPublicKey = + '7e632b62d6230bfc15763f06bf82f7e20cf06a2d8a356850e0bdab30db3506cc'; + const bytes2 = block.getBytes(blockDataCopy); + return expect(bytes1).to.not.deep.equal(bytes2); + }); + }); + + describe('verifySignature', () => { + it('should throw error for invalid block', async () => + expect(() => { + block.verifySignature(invalidBlock); + }).to.throw()); + + it('should return true for a good block', async () => + expect(block.verifySignature(blockData)).to.be.true); + + it('should return false for a block with modified timestamp', async () => { + const blockCopy = Object.assign({}, blockData); + blockCopy.timestamp += 1; + return expect(block.verifySignature(blockCopy)).to.be.false; + }); + + it('should return false for a block with modified previousBlock', async () => { + const blockCopy = Object.assign({}, blockData); + blockCopy.previousBlock = '1111112222333333'; + return expect(block.verifySignature(blockCopy)).to.be.false; + }); + + it('should return false for a block with modified numberOfTransactions', async () => { + const blockCopy = Object.assign({}, blockData); + blockCopy.numberOfTransactions += 1; + return expect(block.verifySignature(blockCopy)).to.be.false; + }); + + it('should return false for a block with modified totalAmount', async () => { + const blockCopy = Object.assign({}, blockData); + blockCopy.totalAmount += 1; + return expect(block.verifySignature(blockCopy)).to.be.false; + }); + + it('should return false for a block with modified totalFee', async () => { + const blockCopy = Object.assign({}, blockData); + blockCopy.totalFee += 1; + return expect(block.verifySignature(blockCopy)).to.be.false; + }); + + it('should return false for a block with modified reward', async () => { + const blockCopy = Object.assign({}, blockData); + blockCopy.reward += 1; + return expect(block.verifySignature(blockCopy)).to.be.false; + }); + + it('should return false for a block with modified payloadLength', async () => { + const blockCopy = Object.assign({}, blockData); + blockCopy.payloadLength += 1; + return expect(block.verifySignature(blockCopy)).to.be.false; + }); + + it('should return false for a block with modified payloadHash', async () => { + const blockCopy = Object.assign({}, blockData); + blockCopy.payloadHash = + 'aabbccddaabbccddaabbccddaabbccddaabbccddaabbccddaabbccddaabbccdd'; + return expect(block.verifySignature(blockCopy)).to.be.false; + }); + + it('should return false for a block with modified generatorPublicKey', async () => { + const blockCopy = Object.assign({}, blockData); + blockCopy.generatorPublicKey = + 'aabbccddaabbccddaabbccddaabbccddaabbccddaabbccddaabbccddaabbccdd'; + return expect(block.verifySignature(blockCopy)).to.be.false; + }); + + it('should return false for a block with modified blockSignature', async () => { + const blockCopy = Object.assign({}, blockData); + blockCopy.blockSignature = + 'aabbccddaabbccddaabbccddaabbccddaabbccddaabbccddaabbccddaabbccddaabbccddaabbccddaabbccddaabbccddaabbccddaabbccddaabbccddaabbccdd'; + return expect(block.verifySignature(blockCopy)).to.be.false; + }); + }); + + describe('getId', () => { + it('should throw an error for empty block', async () => + expect(() => { + block.getId({}); + }).to.throw()); + + it('should return the id for a given block', async () => + expect(block.getId(blockData)) + .to.be.a('string') + .which.is.equal(blockData.id)); + }); + + describe('getHash', () => { + it('should throw error for invalid block', async () => + expect(() => { + block.getHash(invalidBlock); + }).to.throw()); + + it('should return a hash for a given block', async () => + expect(block.getHash(blockData)).to.be.an.instanceof(Buffer)); + }); + + describe('dbRead', () => { + it('should throw error for null values', async () => + expect(() => { + block.dbRead(null); + }).to.throw("Cannot read property 'b_version' of null")); + + it('should return raw block data', async () => { + const rawBlock = { + b_version: 2, + b_totalAmount: 0, + b_totalFee: 0, + b_reward: 0, + b_payloadHash: + 'e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855', + b_timestamp: 41898490, + b_numberOfTransactions: 0, + b_payloadLength: 0, + b_previousBlock: '1087874036928524397', + b_generatorPublicKey: + '1cc68fa0b12521158e09779fd5978ccc0ac26bf99320e00a9549b542dd9ada16', + b_transactions: [], + b_blockSignature: + '8a727cc77864b6fc81755a1f4eb4796b68f4a943d69c74a043b5ca422f3b05608a22da4a916ca7b721d096129938b6eb3381d75f1a116484d1ce2be4904d9a0e', + b_height: 6, + b_maxHeightPreviouslyForged: 1, + b_prevotedConfirmedUptoHeight: 1, + b_id: '3920300554926889269', + b_relays: 1, + b_confirmations: 0, + }; + + return expect(block.dbRead(rawBlock)).to.contain.keys( + 'id', + 'version', + 'timestamp', + 'height', + 'maxHeightPreviouslyForged', + 'prevotedConfirmedUptoHeight', + 'previousBlock', + 'numberOfTransactions', + 'totalAmount', + 'totalFee', + 'reward', + 'payloadLength', + 'payloadHash', + 'generatorPublicKey', + 'generatorId', + 'blockSignature', + 'confirmations', + 'totalForged' + ); + }); + }); +}); From 481291693c08cd2298b330995275c3a7a905a3d6 Mon Sep 17 00:00:00 2001 From: michielmulders Date: Thu, 20 Jun 2019 11:14:34 +0200 Subject: [PATCH 20/25] Cleanup code based on feedback --- .../chain/components/storage/entities/block.js | 4 ++-- .../mocha/unit/modules/chain/blocks/block_v1.js | 16 ++-------------- .../mocha/unit/modules/chain/blocks/block_v2.js | 12 ------------ .../unit/modules/chain/blocks/block_version.js | 2 +- .../mocha/unit/modules/chain/blocks/verify.js | 4 ++-- 5 files changed, 7 insertions(+), 31 deletions(-) diff --git a/framework/src/modules/chain/components/storage/entities/block.js b/framework/src/modules/chain/components/storage/entities/block.js index fd405e755c5..2bcb94270ec 100644 --- a/framework/src/modules/chain/components/storage/entities/block.js +++ b/framework/src/modules/chain/components/storage/entities/block.js @@ -23,8 +23,8 @@ const { } = require('../../../../../components/storage'); const defaultCreateValues = { - maxHeightPreviouslyForged: null, - prevotedConfirmedUptoHeight: null, + maxHeightPreviouslyForged: 0, + prevotedConfirmedUptoHeight: 0, }; const createFields = [ 'id', diff --git a/framework/test/mocha/unit/modules/chain/blocks/block_v1.js b/framework/test/mocha/unit/modules/chain/blocks/block_v1.js index f8cc4dd938f..90aa4031d57 100644 --- a/framework/test/mocha/unit/modules/chain/blocks/block_v1.js +++ b/framework/test/mocha/unit/modules/chain/blocks/block_v1.js @@ -93,23 +93,11 @@ describe('block', () => { transactions: [], blockSignature: '8a727cc77864b6fc81755a1f4eb4796b68f4a943d69c74a043b5ca422f3b05608a22da4a916ca7b721d096129938b6eb3381d75f1a116484d1ce2be4904d9a0e', - height: 6, + height: 69, id: '3920300554926889269', relays: 1, }, transactions: [], - exceptions: { - blockVersions: { - 1: { - start: 1, - end: 7, - }, - 2: { - start: 8, - end: 6901027, - }, - }, - }, }; const invalidBlock = { @@ -636,7 +624,7 @@ describe('block', () => { b_transactions: [], b_blockSignature: '8a727cc77864b6fc81755a1f4eb4796b68f4a943d69c74a043b5ca422f3b05608a22da4a916ca7b721d096129938b6eb3381d75f1a116484d1ce2be4904d9a0e', - b_height: 6, + b_height: 69, b_id: '3920300554926889269', b_relays: 1, b_confirmations: 0, diff --git a/framework/test/mocha/unit/modules/chain/blocks/block_v2.js b/framework/test/mocha/unit/modules/chain/blocks/block_v2.js index 71c7fa992bd..8236a3c71c8 100644 --- a/framework/test/mocha/unit/modules/chain/blocks/block_v2.js +++ b/framework/test/mocha/unit/modules/chain/blocks/block_v2.js @@ -102,18 +102,6 @@ describe('block', () => { maxHeightPreviouslyForged: 1, prevotedConfirmedUptoHeight: 1, transactions: [], - exceptions: { - blockVersions: { - 1: { - start: 1, - end: 7, - }, - 2: { - start: 8, - end: 6901027, - }, - }, - }, }; const invalidBlock = { diff --git a/framework/test/mocha/unit/modules/chain/blocks/block_version.js b/framework/test/mocha/unit/modules/chain/blocks/block_version.js index 07163170f64..693835e040a 100644 --- a/framework/test/mocha/unit/modules/chain/blocks/block_version.js +++ b/framework/test/mocha/unit/modules/chain/blocks/block_version.js @@ -173,7 +173,7 @@ describe('block_version', () => { }); describe('when 1 exception present', () => { - // When 1 exception is present current version (1) should be valid only if height is not + // When 1 exception is present current version (2) should be valid only if height is not // in exception's range, exception's version should be valid for its height range beforeEach(async () => { exceptions = { diff --git a/framework/test/mocha/unit/modules/chain/blocks/verify.js b/framework/test/mocha/unit/modules/chain/blocks/verify.js index ec65950e297..abdd3cb3d74 100644 --- a/framework/test/mocha/unit/modules/chain/blocks/verify.js +++ b/framework/test/mocha/unit/modules/chain/blocks/verify.js @@ -423,7 +423,7 @@ describe('blocks/verify', () => { }); describe('when block height is missing', () => { - it('should return no error when block version = 1', async () => { + it('should return no error when block version = 2', async () => { const verifyVersion = blocksVerifyModule.verifyVersion( { version: 2 }, blocksVersionException, @@ -432,7 +432,7 @@ describe('blocks/verify', () => { return expect(verifyVersion.errors.length).to.equal(0); }); - it('should return error when block version = 2', async () => { + it('should return error when block version = 1', async () => { const verifyVersion = blocksVerifyModule.verifyVersion( { version: 1 }, blocksVersionException, From f8d093e4a70bf118db903421d0e25f526a45822c Mon Sep 17 00:00:00 2001 From: michielmulders Date: Thu, 20 Jun 2019 13:30:51 +0200 Subject: [PATCH 21/25] Fixed bug stubbing currentBlockVersion --- framework/src/modules/chain/blocks/block_version.js | 12 +++++++----- .../integration/blocks/process/on_receive_block.js | 2 ++ .../test/mocha/integration/blocks/verify/verify.js | 4 ++++ 3 files changed, 13 insertions(+), 5 deletions(-) diff --git a/framework/src/modules/chain/blocks/block_version.js b/framework/src/modules/chain/blocks/block_version.js index 9e9a552bcb8..4f10414637a 100644 --- a/framework/src/modules/chain/blocks/block_version.js +++ b/framework/src/modules/chain/blocks/block_version.js @@ -42,7 +42,7 @@ const isValid = (version, height, exceptions = {}) => { if (exceptionVersion === undefined) { // If there is no exception for provided height - check against current block version - return version === currentBlockVersion; + return version === blockVersionInterface.currentBlockVersion; } // If there is an exception - check if version match @@ -50,8 +50,8 @@ const isValid = (version, height, exceptions = {}) => { }; const getBlockVersion = (height, exceptions = {}) => { - if (height === undefined || !Object.keys(exceptions).length) { - return currentBlockVersion; + if (height === undefined || !exceptions.blockVersions) { + return blockVersionInterface.currentBlockVersion; } const exceptionVersion = Object.keys(exceptions.blockVersions).find( @@ -67,14 +67,16 @@ const getBlockVersion = (height, exceptions = {}) => { if (exceptionVersion === undefined) { // If there is no exception for provided height return currentBlockVersion - return currentBlockVersion; + return blockVersionInterface.currentBlockVersion; } return Number(exceptionVersion); }; -module.exports = { +const blockVersionInterface = { isValid, currentBlockVersion, getBlockVersion, }; + +module.exports = blockVersionInterface; diff --git a/framework/test/mocha/integration/blocks/process/on_receive_block.js b/framework/test/mocha/integration/blocks/process/on_receive_block.js index afc2f57eb62..0a03c77e806 100644 --- a/framework/test/mocha/integration/blocks/process/on_receive_block.js +++ b/framework/test/mocha/integration/blocks/process/on_receive_block.js @@ -91,6 +91,8 @@ describe('integration test (blocks) - process receiveBlockFromNetwork()', () => transactions, blockReward, maxPayloadLength, + maxHeightPreviouslyForged: 0, + prevotedConfirmedUptoHeight: 0, }); block.id = blocksUtils.getId(block); diff --git a/framework/test/mocha/integration/blocks/verify/verify.js b/framework/test/mocha/integration/blocks/verify/verify.js index a4eb64b3f35..346b4cfe1a9 100644 --- a/framework/test/mocha/integration/blocks/verify/verify.js +++ b/framework/test/mocha/integration/blocks/verify/verify.js @@ -78,6 +78,8 @@ function createBlock( previousBlock: blocksModule.lastBlock, transactions, maxTransactionPerBlock: blocksModule.constants.maxTransactionPerBlock, + maxHeightPreviouslyForged: 1, + prevotedConfirmedUptoHeight: 1, }); newBlock.id = blocksLogic.getId(newBlock); @@ -319,6 +321,8 @@ describe('blocks/verify', () => { expect(block1.payloadLength).to.equal(0); expect(block1.transactions).to.deep.equal([]); expect(block1.previousBlock).to.equal(genesisBlock.id); + expect(block1.maxHeightPreviouslyForged).to.equal(1); + expect(block1.prevotedConfirmedUptoHeight).to.equal(1); done(); }) .catch(err => { From 1f30cb174b8bc20daafc20057cfd2712eeb89899 Mon Sep 17 00:00:00 2001 From: michielmulders Date: Thu, 20 Jun 2019 13:54:17 +0200 Subject: [PATCH 22/25] Fixed bug stubbing currentBlockVersion --- .../test/mocha/integration/blocks/process/on_receive_block.js | 4 ++-- framework/test/mocha/integration/blocks/verify/verify.js | 2 -- framework/test/mocha/integration/matcher.js | 2 ++ 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/framework/test/mocha/integration/blocks/process/on_receive_block.js b/framework/test/mocha/integration/blocks/process/on_receive_block.js index 0a03c77e806..6c72eee2e69 100644 --- a/framework/test/mocha/integration/blocks/process/on_receive_block.js +++ b/framework/test/mocha/integration/blocks/process/on_receive_block.js @@ -91,8 +91,8 @@ describe('integration test (blocks) - process receiveBlockFromNetwork()', () => transactions, blockReward, maxPayloadLength, - maxHeightPreviouslyForged: 0, - prevotedConfirmedUptoHeight: 0, + maxHeightPreviouslyForged: 1, + prevotedConfirmedUptoHeight: 1, }); block.id = blocksUtils.getId(block); diff --git a/framework/test/mocha/integration/blocks/verify/verify.js b/framework/test/mocha/integration/blocks/verify/verify.js index 346b4cfe1a9..13cb72cd704 100644 --- a/framework/test/mocha/integration/blocks/verify/verify.js +++ b/framework/test/mocha/integration/blocks/verify/verify.js @@ -321,8 +321,6 @@ describe('blocks/verify', () => { expect(block1.payloadLength).to.equal(0); expect(block1.transactions).to.deep.equal([]); expect(block1.previousBlock).to.equal(genesisBlock.id); - expect(block1.maxHeightPreviouslyForged).to.equal(1); - expect(block1.prevotedConfirmedUptoHeight).to.equal(1); done(); }) .catch(err => { diff --git a/framework/test/mocha/integration/matcher.js b/framework/test/mocha/integration/matcher.js index cad0b204070..9a98ff615bf 100644 --- a/framework/test/mocha/integration/matcher.js +++ b/framework/test/mocha/integration/matcher.js @@ -136,6 +136,8 @@ function createRawBlock(library, rawTransactions, callback) { transactions, maxTransactionPerBlock: library.modules.blocks.constants.maxTransactionPerBlock, + maxHeightPreviouslyForged: 1, + prevotedConfirmedUptoHeight: 1, }); block.id = blocksLogic.getId(block); From cd949e86fff624b3e30e2a1a4a4ced619fb1e6a6 Mon Sep 17 00:00:00 2001 From: michielmulders Date: Thu, 20 Jun 2019 15:17:55 +0200 Subject: [PATCH 23/25] Blocks test values null to 0 --- .../unit/modules/chain/components/storage/entities/block.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/framework/test/mocha/unit/modules/chain/components/storage/entities/block.js b/framework/test/mocha/unit/modules/chain/components/storage/entities/block.js index 0578c46d13e..96e87c992cf 100644 --- a/framework/test/mocha/unit/modules/chain/components/storage/entities/block.js +++ b/framework/test/mocha/unit/modules/chain/components/storage/entities/block.js @@ -75,8 +75,8 @@ describe('Block', () => { validReturnedBlock = { id: '7807109686729042739', height: 1, - maxHeightPreviouslyForged: null, - prevotedConfirmedUptoHeight: null, + maxHeightPreviouslyForged: 0, + prevotedConfirmedUptoHeight: 0, blockSignature: 'a47d07d3a8d8024eb44672bc6d07cdcd1cd03803d9612b7b10c10d5a844fb8f6ed11fab5159b6d9826b7302c3d3f5d7d29d13b40e6fe59c9374f4ec94af4eb0f', generatorPublicKey: From aa9750483b8696aa1b184d0c6a2a6c9e6096a8c8 Mon Sep 17 00:00:00 2001 From: michielmulders Date: Fri, 21 Jun 2019 12:00:32 +0200 Subject: [PATCH 24/25] Fix failing integration test signature invalid --- framework/src/modules/chain/blocks/verify.js | 3 - .../mocha/unit/modules/chain/blocks/verify.js | 70 +++++++++++++++++++ 2 files changed, 70 insertions(+), 3 deletions(-) diff --git a/framework/src/modules/chain/blocks/verify.js b/framework/src/modules/chain/blocks/verify.js index 043ae923c83..9160d9b89d3 100644 --- a/framework/src/modules/chain/blocks/verify.js +++ b/framework/src/modules/chain/blocks/verify.js @@ -595,10 +595,7 @@ const verifyReceipt = ({ lastNBlockIds, exceptions, block, - lastBlock, }) => { - block = blocksUtils.setHeight(block, lastBlock); - let result = { verified: false, errors: [] }; result = verifySignature(block, result); diff --git a/framework/test/mocha/unit/modules/chain/blocks/verify.js b/framework/test/mocha/unit/modules/chain/blocks/verify.js index abdd3cb3d74..1684c94c8eb 100644 --- a/framework/test/mocha/unit/modules/chain/blocks/verify.js +++ b/framework/test/mocha/unit/modules/chain/blocks/verify.js @@ -389,6 +389,14 @@ describe('blocks/verify', () => { }, }; + const blocksVersionExceptionExtended = { + blockVersions: { + 0: { start: 1, end: 101 }, + 1: { start: 102, end: 202 }, + 2: { start: 203, end: 303 }, + }, + }; + describe('when block height provided', () => { it('should return no error when block version = 0', async () => { const verifyVersion = blocksVerifyModule.verifyVersion( @@ -399,6 +407,37 @@ describe('blocks/verify', () => { return expect(verifyVersion.errors.length).to.equal(0); }); + it('should return no error when block version = 0 with extended exceptions', async () => { + const verifyVersion = blocksVerifyModule.verifyVersion( + { version: 0, height: 1 }, + blocksVersionExceptionExtended, + { errors: [] } + ); + return expect(verifyVersion.errors.length).to.equal(0); + }); + + it('should return error when block version = 1 and height = 1 with extended exceptions', async () => { + const verifyVersion = blocksVerifyModule.verifyVersion( + { version: 1, height: 1 }, + blocksVersionExceptionExtended, + { errors: [] } + ); + return expect(verifyVersion.errors[0].message).to.equal( + 'Invalid block version' + ); + }); + + it('should return error when block version = 2 and height = 1 with extended exceptions', async () => { + const verifyVersion = blocksVerifyModule.verifyVersion( + { version: 2, height: 1 }, + blocksVersionExceptionExtended, + { errors: [] } + ); + return expect(verifyVersion.errors[0].message).to.equal( + 'Invalid block version' + ); + }); + it('should return error when block version = 1', async () => { const verifyVersion = blocksVerifyModule.verifyVersion( { version: 1, height: 1 }, @@ -453,6 +492,37 @@ describe('blocks/verify', () => { 'Invalid block version' ); }); + + it('should return no error when block version = 2 with extended exceptions', async () => { + const verifyVersion = blocksVerifyModule.verifyVersion( + { version: 2 }, + blocksVersionExceptionExtended, + { errors: [] } + ); + return expect(verifyVersion.errors.length).to.equal(0); + }); + + it('should return error when block version = 1 with extended exceptions', async () => { + const verifyVersion = blocksVerifyModule.verifyVersion( + { version: 1 }, + blocksVersionExceptionExtended, + { errors: [] } + ); + return expect(verifyVersion.errors[0].message).to.equal( + 'Invalid block version' + ); + }); + + it('should return error when block version = 3 with extended exceptions', async () => { + const verifyVersion = blocksVerifyModule.verifyVersion( + { version: 3 }, + blocksVersionExceptionExtended, + { errors: [] } + ); + return expect(verifyVersion.errors[0].message).to.equal( + 'Invalid block version' + ); + }); }); }); }); From b9bef84910e350515efa72bf752087d0ac9c895e Mon Sep 17 00:00:00 2001 From: michielmulders Date: Mon, 24 Jun 2019 13:28:28 +0200 Subject: [PATCH 25/25] Fix unit block_v2 stub and lisk-validator --- .../mocha/unit/modules/chain/blocks/block_v2.js | 4 ++-- .../mocha/unit/modules/chain/blocks/blocks.js | 16 +++++++++++++--- 2 files changed, 15 insertions(+), 5 deletions(-) diff --git a/framework/test/mocha/unit/modules/chain/blocks/block_v2.js b/framework/test/mocha/unit/modules/chain/blocks/block_v2.js index 8236a3c71c8..2999272a6c4 100644 --- a/framework/test/mocha/unit/modules/chain/blocks/block_v2.js +++ b/framework/test/mocha/unit/modules/chain/blocks/block_v2.js @@ -17,6 +17,7 @@ const { getPrivateAndPublicKeyBytesFromPassphrase, } = require('@liskhq/lisk-cryptography'); +const { validator } = require('@liskhq/lisk-validator'); const { registeredTransactions, } = require('../../../../common/registered_transactions'); @@ -24,7 +25,6 @@ const { TransactionInterfaceAdapter, } = require('../../../../../../src/modules/chain/interface_adapters'); const block = require('../../../../../../src/modules/chain/blocks/block'); -const validator = require('../../../../../../src/controller/validator'); const { calculateSupply, calculateReward, @@ -339,7 +339,7 @@ describe('block', () => { let transactionsOrder; beforeEach(async () => { - sinonSandbox.stub(validator, 'validate'); + sinonSandbox.stub(validator, 'validate').returns(true); // Create 6 multisignature transactions multipleMultisigTx = Array(...Array(5)).map(() => interfaceAdapters.transactions.fromJson( diff --git a/framework/test/mocha/unit/modules/chain/blocks/blocks.js b/framework/test/mocha/unit/modules/chain/blocks/blocks.js index c4dc96138c6..01c127b78a9 100644 --- a/framework/test/mocha/unit/modules/chain/blocks/blocks.js +++ b/framework/test/mocha/unit/modules/chain/blocks/blocks.js @@ -49,10 +49,8 @@ describe('blocks', () => { let sequenceStub; let roundsModuleStub; let slots; - let exceptions; beforeEach(async () => { - exceptions = __testContext.config.modules.chain.exceptions; loggerStub = { trace: sinonSandbox.stub(), info: sinonSandbox.stub(), @@ -95,7 +93,18 @@ describe('blocks', () => { // Unique requirements genesisBlock: __testContext.config.genesisBlock, slots, - exceptions, + exceptions: { + blockVersions: { + 1: { + start: 0, + end: 101, + }, + 2: { + start: 102, + end: 202, + }, + }, + }, // Modules roundsModule: roundsModuleStub, interfaceAdapters, @@ -240,6 +249,7 @@ describe('blocks', () => { it('should call _receiveBlockFromNetworkV2 when block version is 2', async () => { const blockv2 = { ...block, + height: 102, version: 2, };