diff --git a/__tests__/integration/core-blockchain/blockchain.test.ts b/__tests__/integration/core-blockchain/blockchain.test.ts index 81fa28e746..04d960a595 100644 --- a/__tests__/integration/core-blockchain/blockchain.test.ts +++ b/__tests__/integration/core-blockchain/blockchain.test.ts @@ -56,7 +56,7 @@ const addBlocks = async untilHeight => { const lastHeight = blockchain.getLastHeight(); for (let height = lastHeight + 1; height < untilHeight && height < 155; height++) { - const blockToProcess = Blocks.BlockFactory.fromData(allBlocks[height - 2]); + const blockToProcess = allBlocks[height - 2]; await blockchain.processBlocks([blockToProcess], () => undefined); } }; @@ -218,7 +218,9 @@ describe("Blockchain", () => { transactions, }; - return Blocks.BlockFactory.make(data, Identities.Keys.fromPassphrase(generatorKeys.secret)); + const blockInstance = Blocks.BlockFactory.make(data, Identities.Keys.fromPassphrase(generatorKeys.secret)); + + return { ...blockInstance.data, transactions: blockInstance.transactions.map(tx => tx.data) }; }; it("should restore vote balances after a rollback", async () => { diff --git a/__tests__/integration/core-transaction-pool/processor.test.ts b/__tests__/integration/core-transaction-pool/processor.test.ts index 08b266685e..bfe20188d3 100644 --- a/__tests__/integration/core-transaction-pool/processor.test.ts +++ b/__tests__/integration/core-transaction-pool/processor.test.ts @@ -2,10 +2,10 @@ import "jest-extended"; import { Blockchain, Container, State, TransactionPool } from "@arkecosystem/core-interfaces"; import { Handlers } from "@arkecosystem/core-transactions"; -import { Blocks, Identities, Interfaces, Managers, Utils } from "@arkecosystem/crypto"; +import { Blocks, Crypto, Identities, Interfaces, Managers, Utils } from "@arkecosystem/crypto"; import { generateMnemonic } from "bip39"; import { TransactionFactory } from "../../helpers/transaction-factory"; -import { delegates, genesisBlock, wallets, wallets2ndSig } from "../../utils/fixtures/unitnet"; +import { delegates, wallets, wallets2ndSig } from "../../utils/fixtures/unitnet"; import { generateWallets } from "../../utils/generators/wallets"; import { setUpFull, tearDownFull } from "./__support__/setup"; // import { Crypto, Enums, Managers } from "@arkecosystem/crypto"; @@ -736,38 +736,54 @@ describe("Transaction Guard", () => { describe("Transaction replay shouldn't pass validation", () => { afterEach(async () => blockchain.removeBlocks(blockchain.getLastHeight() - 1)); // resets to height 1 - const addBlock = async transactions => { - let totalAmount = Utils.BigNumber.ZERO; - let totalFee = Utils.BigNumber.ZERO; + const addBlock = async (generatorKeys: any, transactions: Interfaces.ITransactionData[]) => { + const timestamp = () => { + const lastBlock = blockchain.state.getLastBlock(); + return Crypto.Slots.getSlotTime(Crypto.Slots.getSlotNumber(lastBlock.data.timestamp) + 1); + }; + + const transactionData = { + amount: Utils.BigNumber.ZERO, + fee: Utils.BigNumber.ZERO, + ids: [], + }; for (const transaction of transactions) { - totalAmount = totalAmount.plus(transaction.amount); - totalFee = totalFee.plus(transaction.fee); + transactionData.amount = transactionData.amount.plus(transaction.amount); + transactionData.fee = transactionData.fee.plus(transaction.fee); + transactionData.ids.push(Buffer.from(transaction.id, "hex")); } - // makes blockchain accept a new block with the transactions specified - const block = { - id: "17882607875259085966", + const lastBlock = blockchain.state.getLastBlock(); + const data = { + timestamp: timestamp(), version: 0, - timestamp: 46583330, - height: 2, - reward: Utils.BigNumber.make(0), - previousBlock: genesisBlock.id, - numberOfTransactions: 1, + previousBlock: lastBlock.data.id, + previousBlockHex: lastBlock.data.idHex, + height: lastBlock.data.height + 1, + numberOfTransactions: transactions.length, + totalAmount: transactionData.amount, + totalFee: transactionData.fee, + reward: Utils.BigNumber.ZERO, + payloadLength: 32 * transactions.length, + payloadHash: Crypto.HashAlgorithms.sha256(transactionData.ids).toString("hex"), transactions, - totalAmount, - totalFee, - payloadLength: 0, - payloadHash: genesisBlock.payloadHash, - generatorPublicKey: delegates[0].publicKey, - blockSignature: - "3045022100e7385c6ea42bd950f7f6ab8c8619cf2f66a41d8f8f185b0bc99af032cb25f30d02200b6210176a6cedfdcbe483167fd91c21d740e0e4011d24d679c601fdd46b0de9", - createdAt: "2019-07-11T16:48:50.550Z", }; - const blockVerified = Blocks.BlockFactory.fromData(block); - blockVerified.verification.verified = true; - await blockchain.processBlocks([blockVerified], () => undefined); + const blockInstance = Blocks.BlockFactory.make( + data, + Identities.Keys.fromPassphrase(generatorKeys.secret), + ); + + await blockchain.processBlocks( + [ + { + ...blockInstance.data, + transactions: blockInstance.transactions.map(tx => tx.data), + }, + ], + () => undefined, + ); }; it("should not validate an already forged transaction", async () => { @@ -775,7 +791,9 @@ describe("Transaction Guard", () => { .withNetwork("unitnet") .withPassphrase(wallets[0].passphrase) .create(); - await addBlock(transfers); + + const forgerKeys = delegates[0]; + await addBlock(forgerKeys, transfers); const result = await processor.validate(transfers); @@ -789,7 +807,9 @@ describe("Transaction Guard", () => { .withNetwork("unitnet") .withPassphrase(wallets[0].passphrase) .create(); - await addBlock(transfers); + + const forgerKeys = delegates[0]; + await addBlock(forgerKeys, transfers); const originalId: string = transfers[0].id; diff --git a/__tests__/integration/core-transaction-pool/wallet-manager.test.ts b/__tests__/integration/core-transaction-pool/wallet-manager.test.ts index 5ed45cf977..016facfa2c 100644 --- a/__tests__/integration/core-transaction-pool/wallet-manager.test.ts +++ b/__tests__/integration/core-transaction-pool/wallet-manager.test.ts @@ -3,6 +3,8 @@ import { Wallets } from "@arkecosystem/core-state"; import { Handlers } from "@arkecosystem/core-transactions"; import { Blocks, Identities, Utils } from "@arkecosystem/crypto"; import { generateMnemonic } from "bip39"; +import { Blockchain as BlockchainClass } from "../../../packages/core-blockchain/src"; +import { BlockProcessor } from "../../../packages/core-blockchain/src/processor"; import { WalletManager } from "../../../packages/core-transaction-pool/src/wallet-manager"; import { TransactionFactory } from "../../helpers/transaction-factory"; import { delegates, genesisBlock, wallets } from "../../utils/fixtures/unitnet"; @@ -209,8 +211,9 @@ describe("Apply transactions and block rewards to wallets on new block", () => { }; const blockWithRewardVerified = Blocks.BlockFactory.fromData(blockWithReward); blockWithRewardVerified.verification.verified = true; + const processor = new BlockProcessor(blockchain as BlockchainClass); - await blockchain.processBlocks([blockWithRewardVerified], () => undefined); + await processor.process(blockWithRewardVerified); const delegateWallet = poolWalletManager.findByPublicKey(generatorPublicKey); diff --git a/__tests__/unit/core-blockchain/blockchain.test.ts b/__tests__/unit/core-blockchain/blockchain.test.ts index bbb8621e3e..fab0941b3c 100644 --- a/__tests__/unit/core-blockchain/blockchain.test.ts +++ b/__tests__/unit/core-blockchain/blockchain.test.ts @@ -116,7 +116,7 @@ describe("Blockchain", () => { const mockCallback = jest.fn(() => true); blockchain.state.blockchain = {}; - await blockchain.processBlocks([BlockFactory.fromData(blocks2to100[2])], mockCallback); + await blockchain.processBlocks([blocks2to100[2]], mockCallback); await delay(200); expect(mockCallback.mock.calls.length).toBe(1); @@ -124,13 +124,13 @@ describe("Blockchain", () => { it("should process a valid block already known", async () => { const mockCallback = jest.fn(() => true); - const lastBlock = blockchain.getLastBlock(); + const lastBlock = blockchain.getLastBlock().data; await blockchain.processBlocks([lastBlock], mockCallback); await delay(200); expect(mockCallback.mock.calls.length).toBe(1); - expect(blockchain.getLastBlock()).toEqual(lastBlock); + expect(blockchain.getLastBlock().data).toEqual(lastBlock); }); it("should process a new block with database saveBlocks failing once", async () => { @@ -139,7 +139,7 @@ describe("Blockchain", () => { database.saveBlocks = jest.fn().mockRejectedValueOnce(new Error("oops")); jest.spyOn(blockchain, "removeTopBlocks").mockReturnValueOnce(undefined); - await blockchain.processBlocks([BlockFactory.fromData(blocks2to100[2])], mockCallback); + await blockchain.processBlocks([blocks2to100[2]], mockCallback); await delay(200); expect(mockCallback.mock.calls.length).toBe(1); @@ -151,7 +151,7 @@ describe("Blockchain", () => { jest.spyOn(database, "saveBlocks").mockRejectedValueOnce(new Error("oops saveBlocks")); jest.spyOn(blockchain, "removeTopBlocks").mockReturnValueOnce(undefined); - await blockchain.processBlocks([BlockFactory.fromData(blocks2to100[2])], mockCallback); + await blockchain.processBlocks([blocks2to100[2]], mockCallback); await delay(200); expect(mockCallback.mock.calls.length).toBe(1); @@ -162,8 +162,10 @@ describe("Blockchain", () => { jest.spyOn(Utils, "isBlockChained").mockReturnValueOnce(true); const mockCallback = jest.fn(() => true); - const lastBlock = blockchain.getLastBlock(); - lastBlock.data.timestamp = Crypto.Slots.getSlotNumber() * 8000; + const lastBlock = blockchain.getLastBlock().data; + const spyGetSlotNumber = jest + .spyOn(Crypto.Slots, "getSlotNumber") + .mockReturnValue(Math.floor(lastBlock.timestamp / 8000)); const broadcastBlock = jest.spyOn(getMonitor, "broadcastBlock"); @@ -172,6 +174,8 @@ describe("Blockchain", () => { expect(mockCallback.mock.calls.length).toBe(1); expect(broadcastBlock).toHaveBeenCalled(); + + spyGetSlotNumber.mockRestore(); }); }); diff --git a/packages/core-blockchain/src/blockchain.ts b/packages/core-blockchain/src/blockchain.ts index 04bb40d4d4..23838e9a55 100644 --- a/packages/core-blockchain/src/blockchain.ts +++ b/packages/core-blockchain/src/blockchain.ts @@ -85,7 +85,7 @@ export class Blockchain implements blockchain.IBlockchain { this.queue = async.queue((blockList: { blocks: Interfaces.IBlockData[] }, cb) => { try { - return this.processBlocks(blockList.blocks.map(b => Blocks.BlockFactory.fromData(b)), cb); + return this.processBlocks(blockList.blocks, cb); } catch (error) { logger.error( `Failed to process ${blockList.blocks.length} blocks from height ${blockList.blocks[0].height} in queue.`, @@ -392,14 +392,14 @@ export class Blockchain implements blockchain.IBlockchain { /** * Process the given block. */ - public async processBlocks(blocks: Interfaces.IBlock[], callback): Promise { + public async processBlocks(blocks: Interfaces.IBlockData[], callback): Promise { const acceptedBlocks: Interfaces.IBlock[] = []; let lastProcessResult: BlockProcessorResult; if ( blocks[0] && - !isBlockChained(this.getLastBlock().data, blocks[0].data, logger) && - !Utils.isException(blocks[0].data) + !isBlockChained(this.getLastBlock().data, blocks[0], logger) && + !Utils.isException(blocks[0]) ) { // Discard remaining blocks as it won't go anywhere anyway. this.clearQueue(); @@ -408,14 +408,17 @@ export class Blockchain implements blockchain.IBlockchain { } let forkBlock: Interfaces.IBlock; + let lastProcessedBlock: Interfaces.IBlock; for (const block of blocks) { - lastProcessResult = await this.blockProcessor.process(block); + const blockInstance = Blocks.BlockFactory.fromData(block); + lastProcessResult = await this.blockProcessor.process(blockInstance); + lastProcessedBlock = blockInstance; if (lastProcessResult === BlockProcessorResult.Accepted) { - acceptedBlocks.push(block); + acceptedBlocks.push(blockInstance); } else { if (lastProcessResult === BlockProcessorResult.Rollback) { - forkBlock = block; + forkBlock = blockInstance; } break; // if one block is not accepted, the other ones won't be chained anyway @@ -459,11 +462,11 @@ export class Blockchain implements blockchain.IBlockchain { lastProcessResult === BlockProcessorResult.Accepted || lastProcessResult === BlockProcessorResult.DiscardedButCanBeBroadcasted ) { - const currentBlock: Interfaces.IBlock = blocks[blocks.length - 1]; - const blocktime: number = config.getMilestone(currentBlock.data.height).blocktime; + // broadcast last processed block + const blocktime: number = config.getMilestone(lastProcessedBlock.data.height).blocktime; - if (this.state.started && Crypto.Slots.getSlotNumber() * blocktime <= currentBlock.data.timestamp) { - this.p2p.getMonitor().broadcastBlock(currentBlock); + if (this.state.started && Crypto.Slots.getSlotNumber() * blocktime <= lastProcessedBlock.data.timestamp) { + this.p2p.getMonitor().broadcastBlock(lastProcessedBlock); } } else if (forkBlock) { this.forkBlock(forkBlock); diff --git a/packages/core-blockchain/src/replay/replay-blockchain.ts b/packages/core-blockchain/src/replay/replay-blockchain.ts index 720cd61456..5eb9122b86 100644 --- a/packages/core-blockchain/src/replay/replay-blockchain.ts +++ b/packages/core-blockchain/src/replay/replay-blockchain.ts @@ -71,7 +71,7 @@ export class ReplayBlockchain extends Blockchain { return this.disconnect(); } - const blocks: Interfaces.IBlock[] = await this.fetchBatch(startHeight, batch, lastAcceptedHeight); + const blocks: Interfaces.IBlockData[] = await this.fetchBatch(startHeight, batch, lastAcceptedHeight); this.processBlocks(blocks, async (acceptedBlocks: Interfaces.IBlock[]) => { if (acceptedBlocks.length !== blocks.length) { @@ -89,14 +89,14 @@ export class ReplayBlockchain extends Blockchain { startHeight: number, batch: number, lastAcceptedHeight: number, - ): Promise { + ): Promise { this.logger.info("Fetching blocks from database..."); const offset: number = startHeight + (batch - 1) * this.chunkSize; const count: number = Math.min(this.targetHeight - lastAcceptedHeight, this.chunkSize); const blocks: Interfaces.IBlockData[] = await this.localDatabase.getBlocks(offset, count); - return blocks.map((block: Interfaces.IBlockData) => Blocks.BlockFactory.fromData(block)); + return blocks; } private async processGenesisBlock(): Promise { diff --git a/packages/core-interfaces/src/core-blockchain/blockchain.ts b/packages/core-interfaces/src/core-blockchain/blockchain.ts index d909801882..f639d5af17 100644 --- a/packages/core-interfaces/src/core-blockchain/blockchain.ts +++ b/packages/core-interfaces/src/core-blockchain/blockchain.ts @@ -77,7 +77,7 @@ export interface IBlockchain { * @param {Function} callback * @return {(Function|void)} */ - processBlocks(blocks: Interfaces.IBlock[], callback: any): Promise; + processBlocks(blocks: Interfaces.IBlockData[], callback: any): Promise; /** * Called by forger to wake up and sync with the network.