diff --git a/elements/lisk-chain/test/fixtures/block.ts b/elements/lisk-chain/test/fixtures/block.ts deleted file mode 100644 index f9a4875b142..00000000000 --- a/elements/lisk-chain/test/fixtures/block.ts +++ /dev/null @@ -1,115 +0,0 @@ -/* - * 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. - */ - -import * as randomstring from 'randomstring'; -import * as stampit from 'stampit'; -import * as faker from 'faker'; - -export const Block = stampit.compose({ - props: { - id: '', - blockSignature: - '56d63b563e00332ec31451376f5f2665fcf7e118d45e68f8db0b00db5963b56bc6776a42d520978c1522c39545c9aff62a7d5bdcf851bf65904b2c2158870f00', - generatorPublicKey: '', - numberOfTransactions: 2, - transactionRoot: - 'be0df321b1653c203226add63ac0d13b3411c2f4caf0a213566cbd39edb7ce3b', - payloadLength: 494, - height: 489, - previousBlockId: null, - reward: '0', - timestamp: 32578370, - totalAmount: '10000000000000000', - totalFee: '0', - version: 0, - }, - init({ - id, - previousBlockId, - generatorPublicKey, - height, - version, - maxHeightPreviouslyForged, - maxHeightPrevoted, - }: { - id: string; - previousBlockId: string; - generatorPublicKey: string; - height: number; - version: number; - maxHeightPreviouslyForged: number; - maxHeightPrevoted: number; - }) { - // Must to provide - this.previousBlockId = previousBlockId; - - this.id = id || randomstring.generate({ charset: 'numeric', length: 19 }); - this.generatorPublicKey = - generatorPublicKey || - randomstring - .generate({ charset: '0123456789ABCDE', length: 64 }) - .toLowerCase(); - this.height = height || Math.floor(Math.random() * Math.floor(5000)); - - this.reward = faker.random.number({ min: 10, max: 100 }).toString(); - this.totalFee = faker.random.number({ min: 100, max: 1000 }).toString(); - this.totalAmount = faker.random - .number({ min: 1000, max: 10000 }) - .toString(); - // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition - this.version = version || 0; - - if (this.version === 2) { - // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition - this.maxHeightPreviouslyForged = maxHeightPreviouslyForged || 0; - // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition - this.maxHeightPrevoted = maxHeightPrevoted || 0; - } - }, -}); - -export const BlockHeader = stampit.compose({ - props: { - id: '', - height: 0, - maxHeightPreviouslyForged: 0, - maxHeightPrevoted: 0, - delegateMinHeightActive: 203, - delegatePublicKey: '', - }, - init({ - height = Math.floor(Math.random() * Math.floor(5000)), - id = randomstring.generate({ charset: 'numeric', length: 19 }), - delegatePublicKey = randomstring - .generate({ charset: '0123456789ABCDE', length: 64 }) - .toLowerCase(), - delegateMinHeightActive = 1, - maxHeightPreviouslyForged = 0, - maxHeightPrevoted = 0, - }: { - height: number; - id: string; - delegatePublicKey: string; - delegateMinHeightActive: number; - maxHeightPreviouslyForged: number; - maxHeightPrevoted: number; - }) { - this.id = id; - this.height = height; - this.delegatePublicKey = delegatePublicKey; - this.delegateMinHeightActive = delegateMinHeightActive; - this.maxHeightPreviouslyForged = maxHeightPreviouslyForged; - this.maxHeightPrevoted = maxHeightPrevoted; - }, -}); diff --git a/elements/lisk-chain/test/fixtures/default_account.ts b/elements/lisk-chain/test/fixtures/default_account.ts deleted file mode 100644 index 7e3b4b81a09..00000000000 --- a/elements/lisk-chain/test/fixtures/default_account.ts +++ /dev/null @@ -1,24 +0,0 @@ -/* - * 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. - */ - -export const genesisAccount = { - address: 'd04699e57c4a3846c988f3c15306796f8eae5c1c', - publicKey: '0fe9a3f1a21b5530f27f87a414b549e79a940bf24fdf2b2f05e7f22aeeecc86a', - passphrase: - 'peanut hundred pen hawk invite exclude brain chunk gadget wait wrong ready', - balance: '10000000000000000', - encryptedPassphrase: - 'iterations=1&salt=e8c7dae4c893e458e0ebb8bff9a36d84&cipherText=c0fab123d83c386ffacef9a171b6e0e0e9d913e58b7972df8e5ef358afbc65f99c9a2b6fe7716f708166ed72f59f007d2f96a91f48f0428dd51d7c9962e0c6a5fc27ca0722038f1f2cf16333&iv=1a2206e426c714091b7e48f6&tag=3a9d9f9f9a92c9a58296b8df64820c15&version=1', - password: 'elephant tree paris dragon chair galaxy', -}; diff --git a/elements/lisk-chain/test/unit/data_access/cache/block.spec.ts b/elements/lisk-chain/test/unit/data_access/cache/block.spec.ts index 0a2ce5aa3a8..392e4f73e8a 100644 --- a/elements/lisk-chain/test/unit/data_access/cache/block.spec.ts +++ b/elements/lisk-chain/test/unit/data_access/cache/block.spec.ts @@ -13,7 +13,7 @@ */ import { BlockCache } from '../../../../src/data_access/cache'; -import { BlockHeader as BlockHeaderInstance } from '../../../fixtures/block'; +import { createFakeBlockHeader } from '../../../utils/block'; describe('data_access.cache.block', () => { const MIN_CACHE_SIZE = 303; @@ -34,8 +34,7 @@ describe('data_access.cache.block', () => { describe('add', () => { it('should add block header to cache', () => { - // eslint-disable-next-line new-cap - const block = BlockHeaderInstance({ height: 1 }); + const block = createFakeBlockHeader({ height: 1 }); blocksCache.add(block); expect(blocksCache.items).toStrictEqual([block]); @@ -43,8 +42,7 @@ describe('data_access.cache.block', () => { it('should only contain maximum of 500 block header at given point in time', () => { const [blocks] = Array.from({ length: 510 }, (_, i) => - // eslint-disable-next-line new-cap - blocksCache.add(BlockHeaderInstance({ height: i })), + blocksCache.add(createFakeBlockHeader({ height: i })), ); const blockIds = blocks.map(b => b.id); @@ -55,14 +53,12 @@ describe('data_access.cache.block', () => { it('should remove the least height block header and add new highest height block header', () => { const [blocks] = Array.from({ length: 510 }, (_, i) => - // eslint-disable-next-line new-cap - blocksCache.add(BlockHeaderInstance({ height: i })), + blocksCache.add(createFakeBlockHeader({ height: i })), ); const maxHeight = Math.max(...blocksCache.items.map(b => b.height)); const minHeight = Math.min(...blocksCache.items.map(b => b.height)); const [lowestHeightBlock] = blocks.filter(b => b.height === minHeight); - // eslint-disable-next-line new-cap - const newBlock = BlockHeaderInstance({ height: maxHeight + 1 }); + const newBlock = createFakeBlockHeader({ height: maxHeight + 1 }); expect(blocksCache.getByHeight(minHeight)).toEqual(lowestHeightBlock); @@ -78,13 +74,11 @@ describe('data_access.cache.block', () => { it('should only allow to insert block header with highest height', () => { const [blocks] = Array.from({ length: 510 }, (_, i) => - // eslint-disable-next-line new-cap - blocksCache.add(BlockHeaderInstance({ height: i })), + blocksCache.add(createFakeBlockHeader({ height: i })), ); const minHeight = Math.min(...blocksCache.items.map(b => b.height)); const [lowestHeightBlock] = blocks.filter(b => b.height === minHeight); - // eslint-disable-next-line new-cap - const newBlock = BlockHeaderInstance({ height: minHeight + 1 }); + const newBlock = createFakeBlockHeader({ height: minHeight + 1 }); expect(blocksCache.getByHeight(minHeight)).toEqual(lowestHeightBlock); @@ -99,8 +93,7 @@ describe('data_access.cache.block', () => { describe('remove', () => { it('if the cache is emptied below the min cache size it should set needsRefill to true', () => { const [blocks] = Array.from({ length: 303 }, (_, i) => - // eslint-disable-next-line new-cap - blocksCache.add(BlockHeaderInstance({ height: i })), + blocksCache.add(createFakeBlockHeader({ height: i })), ); blocksCache.remove(blocks[302].id); @@ -110,22 +103,20 @@ describe('data_access.cache.block', () => { describe('getByID', () => { it('should return undefined if block does not exists', () => { - // eslint-disable-next-line new-cap - const block = BlockHeaderInstance({ height: 1 }); + const block = createFakeBlockHeader({ height: 1 }); blocksCache.add(block); expect(blocksCache.items).toStrictEqual([block]); - expect(blocksCache.getByID('123')).toBeUndefined(); + expect(blocksCache.getByID(Buffer.from('123'))).toBeUndefined(); }); it('should return undefined if block does not exists when empty', () => { expect(blocksCache.items).toStrictEqual([]); - expect(blocksCache.getByID('123')).toBeUndefined(); + expect(blocksCache.getByID(Buffer.from('123'))).toBeUndefined(); }); it('should return the block for a given id', () => { - // eslint-disable-next-line new-cap - const block = BlockHeaderInstance({ height: 1 }); + const block = createFakeBlockHeader({ height: 1 }); blocksCache.add(block); expect(blocksCache.items).toStrictEqual([block]); @@ -135,24 +126,24 @@ describe('data_access.cache.block', () => { describe('getByIDs', () => { it('should return empty array if the cache is empty', () => { - expect(blocksCache.getByIDs(['123'])).toBeEmpty(); + expect(blocksCache.getByIDs([Buffer.from('123')])).toBeEmpty(); }); it('should return empty array if matching block ids does not exists', () => { const [blocks] = Array.from({ length: 10 }, (_, i) => - // eslint-disable-next-line new-cap - blocksCache.add(BlockHeaderInstance({ height: i })), + blocksCache.add(createFakeBlockHeader({ height: i })), ); const blockIds = blocks.map(b => b.id); expect(blocksCache.items).toStrictEqual(blocks); - expect(blocksCache.getByIDs([...blockIds, '111111'])).toBeEmpty(); + expect( + blocksCache.getByIDs([...blockIds, Buffer.from('111111')]), + ).toBeEmpty(); }); it('should return all the blocks for given block ids', () => { const [blocks] = Array.from({ length: 10 }, (_, i) => - // eslint-disable-next-line new-cap - blocksCache.add(BlockHeaderInstance({ height: i })), + blocksCache.add(createFakeBlockHeader({ height: i })), ); const blockIds = blocks.map(b => b.id); @@ -168,8 +159,7 @@ describe('data_access.cache.block', () => { it('should return empty array if blocks does not exists between height range', () => { const [blocks] = Array.from({ length: 10 }, (_, i) => - // eslint-disable-next-line new-cap - blocksCache.add(BlockHeaderInstance({ height: i + 1 })), + blocksCache.add(createFakeBlockHeader({ height: i + 1 })), ); expect(blocksCache.items).toStrictEqual(blocks); @@ -180,8 +170,7 @@ describe('data_access.cache.block', () => { it('should return all the blocks for given block height range', () => { const [blocks] = Array.from({ length: 10 }, (_, i) => - // eslint-disable-next-line new-cap - blocksCache.add(BlockHeaderInstance({ height: i + 1 })), + blocksCache.add(createFakeBlockHeader({ height: i + 1 })), ); const heights = blocks.map(b => b.height); const fromHeight = heights[0]; diff --git a/elements/lisk-chain/test/unit/data_access/data_access.spec.ts b/elements/lisk-chain/test/unit/data_access/data_access.spec.ts index 94ef8b9a8fc..b4dc7c98638 100644 --- a/elements/lisk-chain/test/unit/data_access/data_access.spec.ts +++ b/elements/lisk-chain/test/unit/data_access/data_access.spec.ts @@ -22,15 +22,38 @@ import { } from '@liskhq/lisk-db'; import { TransferTransaction } from '@liskhq/lisk-transactions'; import { DataAccess } from '../../../src/data_access'; -import { BlockHeader as BlockHeaderInstance } from '../../fixtures/block'; -import { BlockInstance, BlockJSON } from '../../../src/types'; +import { + createFakeBlockHeader, + defaultBlockHeaderAssetSchema, + createValidDefaultBlock, + encodeDefaultBlockHeader, + encodedDefaultBlock, +} from '../../utils/block'; +import { Block } from '../../../src/types'; +import { baseAccountSchema } from '../../../src/schema'; +import { + createFakeDefaultAccount, + encodeDefaultAccount, + defaultAccountAssetSchema, +} from '../../utils/account'; jest.mock('@liskhq/lisk-db'); describe('data_access', () => { let dataAccess: DataAccess; let db: any; - let block: BlockInstance; + let block: Block; + + const defaultAccountSchema = { + ...baseAccountSchema, + properties: { + ...baseAccountSchema.properties, + asset: { + ...baseAccountSchema.properties.asset, + properties: defaultAccountAssetSchema, + }, + }, + }; beforeEach(() => { db = new KVStore('temp'); @@ -40,19 +63,17 @@ describe('data_access', () => { (getLastPrefix as jest.Mock).mockImplementation(str => str); dataAccess = new DataAccess({ db, + accountSchema: defaultAccountSchema, + registeredBlockHeaders: { + 0: defaultBlockHeaderAssetSchema, + 2: defaultBlockHeaderAssetSchema, + }, registeredTransactions: { 8: TransferTransaction }, minBlockHeaderCache: 3, maxBlockHeaderCache: 5, }); - block = { - // eslint-disable-next-line new-cap - ...BlockHeaderInstance({ height: 1 }), - totalAmount: 1, - totalFee: 1, - reward: 1, - transactions: [], - }; - dataAccess.deserializeBlockHeader = jest.fn().mockResolvedValue(block); + block = createValidDefaultBlock({ header: { height: 1 } }); + dataAccess.decodeBlockHeader = jest.fn().mockResolvedValue(block.header); }); afterEach(() => { @@ -66,7 +87,7 @@ describe('data_access', () => { // Arrange (dataAccess as any)._blocksCache = { add: jest.fn() }; // Act - dataAccess.addBlockHeader(block); + dataAccess.addBlockHeader(block.header); // Assert expect((dataAccess as any)._blocksCache.add).toHaveBeenCalled(); @@ -76,10 +97,10 @@ describe('data_access', () => { describe('#getBlockHeadersByIDs', () => { it('should not call db if cache exists', async () => { // Arrange - dataAccess.addBlockHeader(block); + dataAccess.addBlockHeader(block.header); // Act - await dataAccess.getBlockHeadersByIDs([block.id]); + await dataAccess.getBlockHeadersByIDs([block.header.id]); // Assert expect(db.get).not.toHaveBeenCalled(); @@ -88,10 +109,10 @@ describe('data_access', () => { it('should return persisted blocks if cache does not exist', async () => { // Arrange (db.get as jest.Mock).mockResolvedValue( - Buffer.from(JSON.stringify(block)), + encodeDefaultBlockHeader(block.header), ); // Act - await dataAccess.getBlockHeadersByIDs([block.id]); + await dataAccess.getBlockHeadersByIDs([block.header.id]); // Assert expect(db.get).toHaveBeenCalled(); @@ -101,7 +122,7 @@ describe('data_access', () => { describe('#getBlockHeaderByHeight', () => { it('should not call db if cache exists', async () => { // Arrange - dataAccess.addBlockHeader(block); + dataAccess.addBlockHeader(block.header); // Act await dataAccess.getBlockHeaderByHeight(1); @@ -115,32 +136,34 @@ describe('data_access', () => { (db.createReadStream as jest.Mock).mockReturnValue( Readable.from([ { - value: Buffer.from(JSON.stringify(block.id)), + value: block.header.id, }, ]), ); when(db.get) - .calledWith(`blocks:height:${formatInt(block.height)}`) - .mockResolvedValue(Buffer.from(JSON.stringify(block.id)) as never) - .calledWith(`blocks:id:${block.id}`) - .mockResolvedValue(Buffer.from(JSON.stringify(block)) as never); + .calledWith(`blocks:height:${formatInt(block.header.height)}`) + .mockResolvedValue(block.header.id as never) + .calledWith(`blocks:id:${block.header.id.toString('binary')}`) + .mockResolvedValue(encodeDefaultBlockHeader(block.header) as never); // Act await dataAccess.getBlockHeaderByHeight(1); // Assert expect(db.get).toHaveBeenCalledTimes(2); expect(db.get).toHaveBeenCalledWith( - `blocks:height:${formatInt(block.height)}`, + `blocks:height:${formatInt(block.header.height)}`, + ); + expect(db.get).toHaveBeenCalledWith( + `blocks:id:${block.header.id.toString('binary')}`, ); - expect(db.get).toHaveBeenCalledWith(`blocks:id:${block.id}`); }); }); describe('#getBlockHeadersByHeightBetween', () => { it('should not call db if cache exists', async () => { // Arrange - dataAccess.addBlockHeader({ ...block, height: 0 }); - dataAccess.addBlockHeader(block); + dataAccess.addBlockHeader({ ...block.header, height: 0 }); + dataAccess.addBlockHeader(block.header); // Act await dataAccess.getBlockHeadersByHeightBetween(0, 1); @@ -155,12 +178,12 @@ describe('data_access', () => { (db.createReadStream as jest.Mock).mockReturnValue( Readable.from([ { - value: Buffer.from(JSON.stringify(block.id)), + value: block.header.id, }, ]), ); (db.get as jest.Mock).mockResolvedValue( - Buffer.from(JSON.stringify(block)), + encodeDefaultBlockHeader(block.header), ); // Act @@ -175,7 +198,7 @@ describe('data_access', () => { describe('#getBlockHeadersWithHeights', () => { it('should not call db if cache exists', async () => { // Arrange - dataAccess.addBlockHeader(block); + dataAccess.addBlockHeader(block.header); // Act await dataAccess.getBlockHeadersWithHeights([1]); @@ -187,26 +210,28 @@ describe('data_access', () => { it('should return persisted blocks if cache does not exist', async () => { // Arrange when(db.get) - .calledWith(`blocks:height:${formatInt(block.height)}`) - .mockResolvedValue(Buffer.from(JSON.stringify(block.id)) as never) - .calledWith(`blocks:id:${block.id}`) - .mockResolvedValue(Buffer.from(JSON.stringify(block)) as never); + .calledWith(`blocks:height:${formatInt(block.header.height)}`) + .mockResolvedValue(block.header.id as never) + .calledWith(`blocks:id:${block.header.id.toString('binary')}`) + .mockResolvedValue(encodeDefaultBlockHeader(block.header) as never); // Act await dataAccess.getBlockHeadersWithHeights([1]); // Assert expect(db.get).toHaveBeenCalledTimes(2); expect(db.get).toHaveBeenCalledWith( - `blocks:height:${formatInt(block.height)}`, + `blocks:height:${formatInt(block.header.height)}`, + ); + expect(db.get).toHaveBeenCalledWith( + `blocks:id:${block.header.id.toString('binary')}`, ); - expect(db.get).toHaveBeenCalledWith(`blocks:id:${block.id}`); }); }); describe('#getLastBlockHeader', () => { it('should not call db if cache exists', async () => { // Arrange - dataAccess.addBlockHeader(block); + dataAccess.addBlockHeader(block.header); // Act await dataAccess.getLastBlockHeader(); @@ -218,12 +243,12 @@ describe('data_access', () => { it('should return persisted blocks if cache does not exist', async () => { // Arrange (db.get as jest.Mock).mockResolvedValue( - Buffer.from(JSON.stringify(block)), + encodeDefaultBlockHeader(block.header), ); (db.createReadStream as jest.Mock).mockReturnValue( Readable.from([ { - value: Buffer.from(JSON.stringify(block.id)), + value: block.header.id, }, ]), ); @@ -233,17 +258,19 @@ describe('data_access', () => { // Assert expect(db.get).toHaveBeenCalledTimes(1); expect(db.createReadStream).toHaveBeenCalledTimes(1); - expect(db.get).toHaveBeenCalledWith(`blocks:id:${block.id}`); + expect(db.get).toHaveBeenCalledWith( + `blocks:id:${block.header.id.toString('binary')}`, + ); }); }); describe('#getLastCommonBlockHeader', () => { it('should not call db if cache exists', async () => { // Arrange - dataAccess.addBlockHeader(block); + dataAccess.addBlockHeader(block.header); // Act - await dataAccess.getLastCommonBlockHeader([block.id]); + await dataAccess.getLastCommonBlockHeader([block.header.id]); // Assert expect(db.get).not.toHaveBeenCalled(); @@ -252,10 +279,13 @@ describe('data_access', () => { it('should return persisted blocks if cache does not exist', async () => { // Arrange (db.get as jest.Mock).mockResolvedValue( - Buffer.from(JSON.stringify(block)), + encodeDefaultBlockHeader(block.header), ); // Act - await dataAccess.getLastCommonBlockHeader([block.id, 'random-id']); + await dataAccess.getLastCommonBlockHeader([ + block.header.id, + Buffer.from('random-id'), + ]); // Assert expect(db.get).toHaveBeenCalledTimes(2); @@ -268,9 +298,9 @@ describe('data_access', () => { when(db.get) .mockRejectedValue(new NotFoundError('Data not found') as never) .calledWith('blocks:id:1') - .mockResolvedValue(Buffer.from(JSON.stringify(block)) as never); + .mockResolvedValue(encodeDefaultBlockHeader(block.header) as never); // Act - await dataAccess.getBlocksByIDs(['1']); + await dataAccess.getBlocksByIDs([Buffer.from('1')]); // Assert expect(db.get).toHaveBeenCalledWith('blocks:id:1'); @@ -283,14 +313,14 @@ describe('data_access', () => { (db.createReadStream as jest.Mock).mockReturnValue( Readable.from([ { - value: Buffer.from(JSON.stringify(block.id)), + value: block.header.id, }, ]), ); when(db.get) .mockRejectedValue(new NotFoundError('Data not found') as never) - .calledWith(`blocks:id:${block.id}`) - .mockResolvedValue(Buffer.from(JSON.stringify(block)) as never); + .calledWith(`blocks:id:${block.header.id.toString('binary')}`) + .mockResolvedValue(encodeDefaultBlockHeader(block.header) as never); // Act await dataAccess.getBlocksByHeightBetween(1, 2); @@ -306,14 +336,14 @@ describe('data_access', () => { (db.createReadStream as jest.Mock).mockReturnValue( Readable.from([ { - value: Buffer.from(JSON.stringify(block.id)), + value: block.header.id, }, ]), ); when(db.get) .mockRejectedValue(new NotFoundError('Data not found') as never) - .calledWith(`blocks:id:${block.id}`) - .mockResolvedValue(Buffer.from(JSON.stringify(block)) as never); + .calledWith(`blocks:id:${block.header.id.toString('binary')}`) + .mockResolvedValue(encodeDefaultBlockHeader(block.header) as never); // Act await dataAccess.getLastBlock(); @@ -326,10 +356,12 @@ describe('data_access', () => { describe('#isBlockPersisted', () => { it('should call check if the id exists in the database', async () => { // Act - await dataAccess.isBlockPersisted(block.id); + await dataAccess.isBlockPersisted(block.header.id); // Assert - expect(db.exists).toHaveBeenCalledWith(`blocks:id:${block.id}`); + expect(db.exists).toHaveBeenCalledWith( + `blocks:id:${block.header.id.toString('binary')}`, + ); }); }); @@ -339,7 +371,7 @@ describe('data_access', () => { (db.createReadStream as jest.Mock).mockImplementation(() => Readable.from([ { - value: Buffer.from(JSON.stringify(block)), + value: encodedDefaultBlock(block), }, ]), ); @@ -357,7 +389,7 @@ describe('data_access', () => { (db.createReadStream as jest.Mock).mockImplementation(() => Readable.from([ { - value: Buffer.from(JSON.stringify(block)), + value: encodedDefaultBlock(block), }, ]), ); @@ -400,15 +432,17 @@ describe('data_access', () => { describe('#getAccountsByPublicKey', () => { it('should convert public key to address and get by address', async () => { // Arrange - const account = { - publicKey: + const account = createFakeDefaultAccount({ + publicKey: Buffer.from( '456efe283f25ea5bb21476b6dfb77cec4dbd33a4d1b5e60e4dc28e8e8b10fc4e', - address: 'cc96c0a5db38b968f563e7af6fb435585c889111', - nonce: '0', - }; + 'hex', + ), + address: Buffer.from('cc96c0a5db38b968f563e7af6fb435585c889111', 'hex'), + nonce: BigInt('0'), + }); when(db.get) - .calledWith(`accounts:address:${account.address}`) - .mockResolvedValue(Buffer.from(JSON.stringify(account)) as never); + .calledWith(`accounts:address:${account.address.toString('binary')}`) + .mockResolvedValue(encodeDefaultAccount(account) as never); // Act const [result] = await dataAccess.getAccountsByPublicKey([ account.publicKey, @@ -416,7 +450,7 @@ describe('data_access', () => { // Assert expect(db.get).toHaveBeenCalledWith( - `accounts:address:${account.address}`, + `accounts:address:${account.address.toString('binary')}`, ); expect(typeof result.nonce).toBe('bigint'); }); @@ -425,22 +459,24 @@ describe('data_access', () => { describe('#getAccountByAddress', () => { it('should get account by address and decode them', async () => { // Arrange - const account = { - publicKey: + const account = createFakeDefaultAccount({ + publicKey: Buffer.from( '456efe283f25ea5bb21476b6dfb77cec4dbd33a4d1b5e60e4dc28e8e8b10fc4e', - address: '7546125166665832140L', - nonce: '0', - balance: '100', - }; + 'hex', + ), + address: Buffer.from('cc96c0a5db38b968f563e7af6fb435585c889111', 'hex'), + nonce: BigInt('0'), + balance: BigInt('100'), + }); when(db.get) - .calledWith(`accounts:address:${account.address}`) - .mockResolvedValue(Buffer.from(JSON.stringify(account)) as never); + .calledWith(`accounts:address:${account.address.toString('binary')}`) + .mockResolvedValue(encodeDefaultAccount(account) as never); // Act const result = await dataAccess.getAccountByAddress(account.address); // Assert expect(db.get).toHaveBeenCalledWith( - `accounts:address:${account.address}`, + `accounts:address:${account.address.toString('binary')}`, ); expect(typeof result.balance).toEqual('bigint'); }); @@ -450,26 +486,40 @@ describe('data_access', () => { it('should get accounts by each address and decode them', async () => { // Arrange const accounts = [ - { - publicKey: + createFakeDefaultAccount({ + publicKey: Buffer.from( '456efe283f25ea5bb21476b6dfb77cec4dbd33a4d1b5e60e4dc28e8e8b10fc4e', - address: '7546125166665832140L', - nonce: '0', - balance: '100', - }, - { - publicKey: + 'hex', + ), + address: Buffer.from( + 'cc96c0a5db38b968f563e7af6fb435585c889111', + 'hex', + ), + nonce: BigInt('0'), + balance: BigInt('100'), + }), + createFakeDefaultAccount({ + publicKey: Buffer.from( 'd468707933e4f24888dc1f00c8f84b2642c0edf3d694e2bb5daa7a0d87d18708', - address: '10676488814586252632L', - nonce: '0', - balance: '300', - }, + 'hex', + ), + address: Buffer.from( + '584dd8a902822a9469fb2911fcc14ed5fd98220d', + 'hex', + ), + nonce: BigInt('0'), + balance: BigInt('300'), + }), ]; when(db.get) - .calledWith(`accounts:address:${accounts[0].address}`) - .mockResolvedValue(Buffer.from(JSON.stringify(accounts[0])) as never) - .calledWith(`accounts:address:${accounts[1].address}`) - .mockResolvedValue(Buffer.from(JSON.stringify(accounts[1])) as never); + .calledWith( + `accounts:address:${accounts[0].address.toString('binary')}`, + ) + .mockResolvedValue(encodeDefaultAccount(accounts[0]) as never) + .calledWith( + `accounts:address:${accounts[1].address.toString('binary')}`, + ) + .mockResolvedValue(encodeDefaultAccount(accounts[1]) as never); // Act const result = await dataAccess.getAccountsByAddress( accounts.map(acc => acc.address), @@ -483,21 +533,36 @@ describe('data_access', () => { describe('#getTransactionsByIDs', () => { it('should get transaction by id', async () => { + const tx = new TransferTransaction({ + id: Buffer.from('1065693148641117014'), + type: 8, + fee: BigInt('10000000'), + nonce: BigInt('0'), + senderPublicKey: Buffer.from( + '0fe9a3f1a21b5530f27f87a414b549e79a940bf24fdf2b2f05e7f22aeeecc86a', + 'hex', + ), + signatures: [ + Buffer.from( + 'c49a1b9e8f5da4ddd9c8ad49b6c35af84c233701d53a876ef6e385a46888800334e28430166e2de8cac207452913f0e8b439b03ef8a795748ea23e28b8b1c00c', + 'hex', + ), + ], + asset: { + amount: BigInt('1'), + recipientAddress: Buffer.from( + '0fe9a3f1a21b5530f27f87a414b549e79a940bf2', + 'hex', + ), + data: '', + }, + } as any); // Arrange when(db.get) - .calledWith('transactions:id:1') - .mockResolvedValue( - Buffer.from( - JSON.stringify({ - id: '1', - fee: '100', - nonce: '0', - type: 8, - }), - ) as never, - ); + .calledWith(`transactions:id:${tx.id.toString('hex')}`) + .mockResolvedValue(tx.getBytes() as never); // Act - const [result] = await dataAccess.getTransactionsByIDs(['1']); + const [result] = await dataAccess.getTransactionsByIDs([tx.id]); // Assert expect(db.get).toHaveBeenCalledWith('transactions:id:1'); @@ -508,7 +573,7 @@ describe('data_access', () => { describe('#isTransactionPersisted', () => { it('should call exists with the id', async () => { // Act - await dataAccess.isTransactionPersisted('1'); + await dataAccess.isTransactionPersisted(Buffer.from('1')); // Assert expect(db.exists).toHaveBeenCalledWith('transactions:id:1'); @@ -516,67 +581,80 @@ describe('data_access', () => { }); describe('serialize', () => { - it('should convert all the field to be JSON format', () => { - const blockInstance = dataAccess.serialize(block); - - expect(blockInstance.reward).toBe(block.reward.toString()); - expect(blockInstance.totalFee).toBe(block.totalFee.toString()); - expect(blockInstance.totalAmount).toBe(block.totalAmount.toString()); + it('should convert all the field to be a buffer', () => { + const buffer = dataAccess.encode(block); + expect(buffer).toBeInstanceOf(Buffer); }); }); describe('deserialize', () => { - const blockJSON = { - totalFee: '10000000', - totalAmount: '1', - transactionRoot: - '564352bc451aca0e2aeca2aebf7a3d7af18dbac73eaa31623971bfc63d20339c', - payloadLength: 117, - numberOfTransactions: 1, - version: 2, - height: 2, - transactions: [ - { - id: '1065693148641117014', - blockId: '7360015088758644957', + const originalBlock = { + header: createFakeBlockHeader({ + transactionRoot: Buffer.from( + '564352bc451aca0e2aeca2aebf7a3d7af18dbac73eaa31623971bfc63d20339c', + 'hex', + ), + version: 2, + height: 2, + reward: BigInt(0), + timestamp: 1000, + previousBlockID: Buffer.from( + 'c49a1b9e8f5da4ddd9c8ad49b6c35af84c233701d53a876ef6e385a468888003', + 'hex', + ), + generatorPublicKey: Buffer.from( + '1c51f8d57dd74b9cede1fa957f46559cd9596655c46ae9a306364dc5b39581d1', + 'hex', + ), + signature: Buffer.from( + 'acbe0321dfc4323dd0e6f41269d7dd875ae2bbc6adeb9a4b179cca00328c31e641599b5b0d16d9620886133ed977909d228ab777903f9c0d3842b9ea8630b909', + 'hex', + ), + asset: { + seedReveal: Buffer.from('00000000000000000000000000000000', 'hex'), + maxHeightPreviouslyForged: 1, + maxHeightPrevoted: 0, + }, + }), + payload: [ + new TransferTransaction({ + id: Buffer.from('1065693148641117014'), type: 8, - fee: '10000000', - nonce: '0', - senderPublicKey: + fee: BigInt('10000000'), + nonce: BigInt('0'), + senderPublicKey: Buffer.from( '0fe9a3f1a21b5530f27f87a414b549e79a940bf24fdf2b2f05e7f22aeeecc86a', + 'hex', + ), signatures: [ - 'c49a1b9e8f5da4ddd9c8ad49b6c35af84c233701d53a876ef6e385a46888800334e28430166e2de8cac207452913f0e8b439b03ef8a795748ea23e28b8b1c00c', + Buffer.from( + 'c49a1b9e8f5da4ddd9c8ad49b6c35af84c233701d53a876ef6e385a46888800334e28430166e2de8cac207452913f0e8b439b03ef8a795748ea23e28b8b1c00c', + 'hex', + ), ], asset: { - amount: '1', - recipientId: '10361596175468657749L', + amount: BigInt('1'), + recipientAddress: Buffer.from( + '0fe9a3f1a21b5530f27f87a414b549e79a940bf2', + 'hex', + ), + data: '', }, - }, + } as any), ], - reward: '0', - timestamp: 1000, - generatorPublicKey: - '1c51f8d57dd74b9cede1fa957f46559cd9596655c46ae9a306364dc5b39581d1', - blockSignature: - 'acbe0321dfc4323dd0e6f41269d7dd875ae2bbc6adeb9a4b179cca00328c31e641599b5b0d16d9620886133ed977909d228ab777903f9c0d3842b9ea8630b909', - id: '7360015088758644957', - seedReveal: '00000000000000000000000000000000', - previousBlockId: '1349213844499460766', - maxHeightPreviouslyForged: 1, - maxHeightPrevoted: 0, - } as BlockJSON; + }; it('should convert big number field to be instance', () => { - const blockInstance = dataAccess.deserialize(blockJSON); - - expect(typeof blockInstance.totalAmount).toBe('bigint'); - expect(typeof blockInstance.totalFee).toBe('bigint'); - expect(typeof blockInstance.reward).toBe('bigint'); + const encodedBlock = encodedDefaultBlock(originalBlock); + const decodedBlock = dataAccess.decode(encodedBlock); + expect(decodedBlock).toEqual(originalBlock); }); it('should convert transaction to be a class', () => { - const blockInstance = dataAccess.deserialize(blockJSON); - expect(blockInstance.transactions[0]).toBeInstanceOf(TransferTransaction); + const decodedBlock = dataAccess.decode( + encodedDefaultBlock(originalBlock), + ); + expect(decodedBlock.payload[0]).toBeInstanceOf(TransferTransaction); }); }); @@ -590,22 +668,18 @@ describe('data_access', () => { const blocks = []; for (let i = 0; i < 5; i += 1) { block = { - // eslint-disable-next-line new-cap - ...BlockHeaderInstance({ height: i + 10 }), - totalAmount: 1, - totalFee: 1, - reward: 1, - transactions: [], + header: createFakeBlockHeader({ height: i + 10 }), + payload: [], }; blocks.push(block); - dataAccess.addBlockHeader(block); + dataAccess.addBlockHeader(block.header); } // Act // Remove enough blocks for blocksCache.needsRefill to be true - await dataAccess.removeBlockHeader(blocks[4].id); - await dataAccess.removeBlockHeader(blocks[3].id); - await dataAccess.removeBlockHeader(blocks[2].id); + await dataAccess.removeBlockHeader(blocks[4].header.id); + await dataAccess.removeBlockHeader(blocks[3].header.id); + await dataAccess.removeBlockHeader(blocks[2].header.id); // Assert expect(dataAccess.getBlocksByHeightBetween).toHaveBeenCalledWith(7, 9); }); diff --git a/elements/lisk-chain/test/utils/account.ts b/elements/lisk-chain/test/utils/account.ts new file mode 100644 index 00000000000..94a3f7e36ee --- /dev/null +++ b/elements/lisk-chain/test/utils/account.ts @@ -0,0 +1,147 @@ +/* + * 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. + */ + +import { getRandomBytes } from '@liskhq/lisk-cryptography'; +import { codec } from '@liskhq/lisk-codec'; +import { Account } from '../../src'; +import { baseAccountSchema } from '../../src/schema'; + +export const genesisAccount = { + address: Buffer.from('d04699e57c4a3846c988f3c15306796f8eae5c1c', 'hex'), + publicKey: Buffer.from( + '0fe9a3f1a21b5530f27f87a414b549e79a940bf24fdf2b2f05e7f22aeeecc86a', + 'hex', + ), + passphrase: + 'peanut hundred pen hawk invite exclude brain chunk gadget wait wrong ready', + balance: '10000000000000000', + encryptedPassphrase: + 'iterations=1&salt=e8c7dae4c893e458e0ebb8bff9a36d84&cipherText=c0fab123d83c386ffacef9a171b6e0e0e9d913e58b7972df8e5ef358afbc65f99c9a2b6fe7716f708166ed72f59f007d2f96a91f48f0428dd51d7c9962e0c6a5fc27ca0722038f1f2cf16333&iv=1a2206e426c714091b7e48f6&tag=3a9d9f9f9a92c9a58296b8df64820c15&version=1', + password: 'elephant tree paris dragon chair galaxy', +}; + +export const defaultAccountAssetSchema = { + delegate: { + type: 'object', + fieldNumber: 1, + properties: { + username: { dataType: 'string', fieldNumber: 1 }, + pomHeights: { + type: 'array', + items: { dataType: 'uint32' }, + fieldNumber: 2, + }, + consecutiveMissedBlocks: { dataType: 'uint32', fieldNumber: 3 }, + lastForgedHeight: { dataType: 'uint32', fieldNumber: 4 }, + isBanned: { dataType: 'boolean', fieldNumber: 5 }, + totalVotesReceived: { dataType: 'uint64', fieldNumber: 6 }, + }, + required: [ + 'username', + 'pomHeights', + 'consecutiveMissedBlocks', + 'lastForgedHeight', + 'isBanned', + 'totalVotesReceived', + ], + }, + sentVotes: { + type: 'object', + fieldNumber: 2, + properties: { + delegateAddress: { dataType: 'bytes', fieldNumber: 1 }, + amount: { dataType: 'uint64', fieldNumber: 2 }, + }, + required: ['delegateAddress', 'amount'], + }, + unlocking: { + type: 'object', + fieldNumber: 3, + properties: { + delegateAddress: { dataType: 'bytes', fieldNumber: 1 }, + amount: { dataType: 'uint64', fieldNumber: 2 }, + unvoteHeight: { dataType: 'uint32', fieldNumber: 3 }, + }, + required: ['delegateAddress', 'amount', 'unvoteHeight'], + }, +}; + +export interface AccountAsset { + delegate: DelegateAccountAsset; + sentVotes: VoteAccountAsset[]; + unlocking: UnlockingAccountAsset[]; +} + +export interface DelegateAccountAsset { + username: string; + pomHeights: number[]; + consecutiveMissedBlocks: number; + lastForgedHeight: number; + isBanned: boolean; + totalVotesReceived: bigint; +} + +export interface VoteAccountAsset { + delegateAddress: Buffer; + amount: bigint; +} + +export interface UnlockingAccountAsset { + delegateAddress: Buffer; + amount: bigint; + unvoteHeight: number; +} + +export const createFakeDefaultAccount = ( + account?: Partial>, +): Account => + new Account({ + address: account?.address ?? getRandomBytes(20), + publicKey: account?.publicKey ?? Buffer.alloc(0), + balance: account?.balance ?? BigInt(0), + nonce: account?.nonce ?? BigInt(0), + keys: { + mandatoryKeys: account?.keys?.mandatoryKeys ?? [], + optionalKeys: account?.keys?.optionalKeys ?? [], + numberOfSignatures: account?.keys?.numberOfSignatures ?? 0, + }, + asset: { + delegate: { + username: account?.asset?.delegate?.username ?? '', + pomHeights: account?.asset?.delegate?.pomHeights ?? [], + consecutiveMissedBlocks: + account?.asset?.delegate?.consecutiveMissedBlocks ?? 0, + lastForgedHeight: account?.asset?.delegate?.lastForgedHeight ?? 0, + isBanned: account?.asset?.delegate?.isBanned ?? false, + totalVotesReceived: + account?.asset?.delegate?.totalVotesReceived ?? BigInt(0), + }, + sentVotes: account?.asset?.sentVotes ?? [], + unlocking: account?.asset?.unlocking ?? [], + }, + }); + +export const encodeDefaultAccount = (account: Account): Buffer => { + const defaultAccountSchema = { + ...baseAccountSchema, + properties: { + ...baseAccountSchema.properties, + asset: { + ...baseAccountSchema.properties.asset, + properties: defaultAccountAssetSchema, + }, + }, + }; + return codec.encode(defaultAccountSchema, { ...account }); +}; diff --git a/elements/lisk-chain/test/utils/block.ts b/elements/lisk-chain/test/utils/block.ts index eddc36acbc0..3c15c4ed442 100644 --- a/elements/lisk-chain/test/utils/block.ts +++ b/elements/lisk-chain/test/utils/block.ts @@ -13,220 +13,133 @@ */ import { + getRandomBytes, hash, signDataWithPrivateKey, - getPrivateAndPublicKeyBytesFromPassphrase, - hexToBuffer, - intToBuffer, - LITTLE_ENDIAN, + getPrivateAndPublicKeyFromPassphrase, } from '@liskhq/lisk-cryptography'; import { Mnemonic } from '@liskhq/lisk-passphrase'; +import { codec } from '@liskhq/lisk-codec'; +import { MerkleTree } from '@liskhq/lisk-tree'; import { BaseTransaction } from '@liskhq/lisk-transactions'; import * as genesisBlock from '../fixtures/genesis_block.json'; -import { BlockJSON, BlockInstance } from '../../src/types'; -import { getTransactionRoot } from '../../src/validate'; - -const SIZE_INT32 = 4; -const SIZE_INT64 = 8; - -export const defaultNetworkIdentifier = - '93d00fe5be70d90e7ae247936a2e7d83b50809c79b73fa14285f02c842348b3e'; - -export const getBytes = (block: BlockInstance): Buffer => { - const blockVersionBuffer = intToBuffer( - block.version, - SIZE_INT32, - LITTLE_ENDIAN, - ); - - const timestampBuffer = intToBuffer( - block.timestamp, - SIZE_INT32, - LITTLE_ENDIAN, - ); - - const previousBlockBuffer = block.previousBlockId - ? Buffer.from(block.previousBlockId, 'hex') - : Buffer.alloc(32); - - const heightBuffer = intToBuffer(block.height, SIZE_INT32, LITTLE_ENDIAN); - - const maxHeightPreviouslyForgedBuffer = intToBuffer( - block.maxHeightPreviouslyForged, - SIZE_INT32, - LITTLE_ENDIAN, - ); - - const maxHeightPrevotedBuffer = intToBuffer( - block.maxHeightPrevoted, - SIZE_INT32, - LITTLE_ENDIAN, - ); - - const numTransactionsBuffer = intToBuffer( - block.numberOfTransactions, - SIZE_INT32, - LITTLE_ENDIAN, - ); - - const totalAmountBuffer = intToBuffer( - block.totalAmount.toString(), - SIZE_INT64, - LITTLE_ENDIAN, - ); - - const totalFeeBuffer = intToBuffer( - block.totalFee.toString(), - SIZE_INT64, - LITTLE_ENDIAN, - ); - - const rewardBuffer = intToBuffer( - block.reward.toString(), - SIZE_INT64, - LITTLE_ENDIAN, - ); - - const payloadLengthBuffer = intToBuffer( - block.payloadLength, - SIZE_INT32, - LITTLE_ENDIAN, - ); - - const transactionRootBuffer = hexToBuffer(block.transactionRoot); - - const generatorPublicKeyBuffer = hexToBuffer(block.generatorPublicKey); - - const blockSignatureBuffer = block.blockSignature - ? hexToBuffer(block.blockSignature) - : Buffer.alloc(0); - - return Buffer.concat([ - blockVersionBuffer, - timestampBuffer, - previousBlockBuffer, - heightBuffer, - maxHeightPreviouslyForgedBuffer, - maxHeightPrevotedBuffer, - numTransactionsBuffer, - totalAmountBuffer, - totalFeeBuffer, - rewardBuffer, - payloadLengthBuffer, - transactionRootBuffer, - generatorPublicKeyBuffer, - blockSignatureBuffer, - ]); -}; +import { Block, BlockHeader } from '../../src/types'; +import { + signingBlockHeaderSchema, + blockHeaderSchema, + blockSchema, +} from '../../src/schema'; -const sortTransactions = (transactions: BaseTransaction[]): void => { - transactions.sort((a, b) => (a.type > b.type || a.id > b.id) as any); -}; +export const defaultNetworkIdentifier = Buffer.from( + '93d00fe5be70d90e7ae247936a2e7d83b50809c79b73fa14285f02c842348b3e', +); const getKeyPair = (): { publicKey: Buffer; privateKey: Buffer } => { const passphrase = Mnemonic.generateMnemonic(); - const { - publicKeyBytes: publicKey, - privateKeyBytes: privateKey, - } = getPrivateAndPublicKeyBytesFromPassphrase(passphrase); - return { - publicKey, - privateKey, - }; + return getPrivateAndPublicKeyFromPassphrase(passphrase); }; -// eslint-disable-next-line @typescript-eslint/explicit-function-return-type -const calculateTransactionsInfo = (block: BlockInstance) => { - sortTransactions(block.transactions); - const transactionIds = []; - let totalFee = BigInt(0); - let totalAmount = BigInt(0); - let payloadLength = 0; - - // eslint-disable-next-line @typescript-eslint/prefer-for-of - for (let i = 0; i < block.transactions.length; i += 1) { - const transaction = block.transactions[i]; - const transactionBytes = transaction.getBytes(); - - totalFee += BigInt(transaction.fee); - // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition - totalAmount += BigInt((transaction as any).asset.amount || '0'); +export const defaultBlockHeaderAssetSchema = { + $id: 'test/defaultBlockHeaderAssetSchema', + type: 'object', + properties: { + maxHeightPreviouslyForged: { + dataType: 'uint32', + fieldNumber: 1, + }, + maxHeightPrevoted: { + dataType: 'uint32', + fieldNumber: 2, + }, + seedReveal: { + dataType: 'bytes', + fieldNumber: 3, + }, + }, + required: ['maxHeightPreviouslyForged', 'maxHeightPrevoted', 'seedReveal'], +}; - payloadLength += transactionBytes.length; - transactionIds.push(transaction.id); - } +export const createFakeBlockHeader = ( + header?: Partial>, +): BlockHeader => ({ + id: hash(getRandomBytes(8)), + version: 2, + timestamp: header?.timestamp ?? 0, + height: header?.height ?? 0, + previousBlockID: header?.previousBlockID ?? hash(getRandomBytes(4)), + transactionRoot: header?.transactionRoot ?? hash(getRandomBytes(4)), + generatorPublicKey: header?.generatorPublicKey ?? getRandomBytes(32), + reward: header?.reward ?? BigInt(500000000), + asset: header?.asset ?? ({} as T), + signature: header?.signature ?? getRandomBytes(64), +}); + +export const encodeDefaultBlockHeader = (header: BlockHeader): Buffer => { + const asset = codec.encode(defaultBlockHeaderAssetSchema, header.asset); + return codec.encode(blockHeaderSchema, { ...header, asset }); +}; - const transactionRoot = getTransactionRoot(transactionIds); +export const encodedDefaultBlock = (block: Block): Buffer => { + const payload = block.payload.map(tx => tx.getBytes()); + const header = encodeDefaultBlockHeader(block.header); - return { - totalFee, - totalAmount, - transactionRoot, - payloadLength, - numberOfTransactions: block.transactions.length, - }; + return codec.encode(blockSchema, { header, payload }); }; /** * Utility function to create a block object with valid computed properties while any property can be overridden * Calculates the signature, transactionRoot etc. internally. Facilitating the creation of block with valid signature and other properties */ -export const newBlock = ( - block?: Partial, - networkIdentifier: string = defaultNetworkIdentifier, -): BlockInstance => { - const defaultBlockValues = { - version: 2, - height: 2, +export const createValidDefaultBlock = ( + block?: { header: Partial; payload?: BaseTransaction[] }, + networkIdentifier: Buffer = defaultNetworkIdentifier, +): Block => { + const keypair = getKeyPair(); + const payload = block?.payload ?? []; + const txTree = new MerkleTree(payload.map(tx => tx.id)); + + const asset = { maxHeightPreviouslyForged: 0, maxHeightPrevoted: 0, - seedReveal: '00000000000000000000000000000000', - previousBlockId: genesisBlock.id, - keypair: getKeyPair(), - transactions: [], - reward: BigInt(0), - timestamp: 1000, - }; - const blockWithDefaultValues = { - ...defaultBlockValues, - ...block, + seedReveal: getRandomBytes(16), + ...block?.header?.asset, }; - const transactionsInfo = calculateTransactionsInfo( - blockWithDefaultValues as BlockInstance, + const blockHeader = createFakeBlockHeader({ + version: 2, + height: 2, + previousBlockID: Buffer.from(genesisBlock.id, 'hex'), + reward: BigInt(0), + timestamp: 1000, + transactionRoot: txTree.root, + generatorPublicKey: keypair.publicKey, + ...block?.header, + asset, + }); + + const encodedAsset = codec.encode( + defaultBlockHeaderAssetSchema, + blockHeader.asset, ); - const blockWithCalculatedProperties = { - ...transactionsInfo, - ...blockWithDefaultValues, - generatorPublicKey: blockWithDefaultValues.keypair.publicKey.toString( - 'hex', - ), - }; - - const { keypair } = blockWithCalculatedProperties; - delete blockWithCalculatedProperties.keypair; - - // eslint-disable-next-line new-cap - const blockWithSignature = { - ...blockWithCalculatedProperties, - blockSignature: signDataWithPrivateKey( - Buffer.concat([ - Buffer.from(networkIdentifier, 'hex'), - getBytes(blockWithCalculatedProperties as BlockInstance), - ]), - keypair.privateKey, - ), - }; - const hashedBlockBytes = hash(getBytes(blockWithSignature as BlockInstance)); - - const temp = Buffer.alloc(8); - // eslint-disable-next-line no-plusplus - for (let i = 0; i < 8; i++) { - temp[i] = hashedBlockBytes[7 - i]; - } + const encodedHeaderWithoutSignature = codec.encode(signingBlockHeaderSchema, { + ...blockHeader, + asset: encodedAsset, + }); + + const signature = signDataWithPrivateKey( + Buffer.concat([networkIdentifier, encodedHeaderWithoutSignature]), + keypair.privateKey, + ); + const header = { ...blockHeader, asset: encodedAsset, signature }; + const encodedHeader = codec.encode(blockHeaderSchema, header); + const id = hash(encodedHeader); return { - ...blockWithSignature, - id: temp.readBigUInt64BE().toString(), - } as BlockInstance; + header: { + ...header, + asset, + id, + }, + payload, + }; };