diff --git a/__tests__/unit/core-blockchain/processor/block-processor.test.ts b/__tests__/unit/core-blockchain/processor/block-processor.test.ts index c01e127914..af32ec0a51 100644 --- a/__tests__/unit/core-blockchain/processor/block-processor.test.ts +++ b/__tests__/unit/core-blockchain/processor/block-processor.test.ts @@ -52,6 +52,11 @@ describe("BlockProcessor", () => { const roundState = { getActiveDelegates: jest.fn().mockReturnValue([]), }; + const stateStore = { + getLastBlock: jest.fn(), + getLastBlocks: jest.fn(), + getLastStoredBlockHeight: jest.fn(), + }; const databaseInterceptor = {}; @@ -65,7 +70,7 @@ describe("BlockProcessor", () => { sandbox.app.bind(Container.Identifiers.DatabaseInterceptor).toConstantValue(databaseInterceptor); sandbox.app.bind(Container.Identifiers.RoundState).toConstantValue(roundState); sandbox.app.bind(Container.Identifiers.TransactionHandlerRegistry).toConstantValue(transactionHandlerRegistry); - sandbox.app.bind(Container.Identifiers.StateStore).toConstantValue({}); + sandbox.app.bind(Container.Identifiers.StateStore).toConstantValue(stateStore); sandbox.app.bind(Container.Identifiers.TransactionPoolService).toConstantValue({}); sandbox.app.bind(Container.Identifiers.TriggerService).to(Services.Triggers.Triggers).inSingletonScope(); @@ -356,7 +361,7 @@ describe("BlockProcessor", () => { }); }); - it("should execute AlreadyForgedHandler when block has already forged transactions", async () => { + it("should execute AlreadyForgedHandler when block has already forged transactions in database", async () => { const transactionData = { id: "34821dfa9cbe59aad663b972326ff19265d788c4d4142747606aa29b19d6b1dab", version: 2, @@ -375,6 +380,47 @@ describe("BlockProcessor", () => { getAttribute: jest.fn().mockReturnValue("generatorusername"), }; walletRepository.findByPublicKey = jest.fn().mockReturnValueOnce(generatorWallet); + stateStore.getLastBlock.mockReturnValueOnce(baseBlock); + stateStore.getLastStoredBlockHeight.mockReturnValueOnce(baseBlock.data.height); + stateStore.getLastBlocks.mockReturnValueOnce([]); + + const blockProcessor = sandbox.app.resolve(BlockProcessor); + + await blockProcessor.process(block); + + expect(AlreadyForgedHandler.prototype.execute).toBeCalledTimes(1); + }); + + it("should execute AlreadyForgedHandler when block has already forged transactions in stateStore", async () => { + const transactionData = { + id: "34821dfa9cbe59aad663b972326ff19265d788c4d4142747606aa29b19d6b1dab", + version: 2, + senderPublicKey: "038082dad560a22ea003022015e3136b21ef1ffd9f2fd50049026cbe8e2258ca17", + nonce: Utils.BigNumber.make(2), + } as Interfaces.ITransactionData; + const transactionData2 = { + id: "34821dfa9cbe59aad663b972326ff19265d788c4d4142747606aa29b19d6b1dac", + version: 2, + senderPublicKey: "038082dad560a22ea003022015e3136b21ef1ffd9f2fd50049026cbe8e2258ca17", + nonce: Utils.BigNumber.make(3), + } as Interfaces.ITransactionData; + const block = { + ...chainedBlock, + transactions: [{ data: transactionData, id: transactionData.id } as Interfaces.ITransaction], + }; + roundState.getActiveDelegates = jest.fn().mockReturnValueOnce([]); + blockchain.getLastBlock = jest.fn().mockReturnValueOnce(baseBlock); + transactionRepository.getForgedTransactionsIds = jest.fn().mockReturnValueOnce([]); + walletRepository.getNonce = jest.fn().mockReturnValueOnce(Utils.BigNumber.ONE); + const generatorWallet = { + getAttribute: jest.fn().mockReturnValue("generatorusername"), + }; + walletRepository.findByPublicKey = jest.fn().mockReturnValueOnce(generatorWallet); + stateStore.getLastBlock.mockReturnValueOnce({ data: { height: 2 } }); + stateStore.getLastBlocks.mockReturnValueOnce([ + { data: { height: 2 }, transactions: [transactionData, transactionData2] }, + ]); + stateStore.getLastStoredBlockHeight.mockReturnValue(1); const blockProcessor = sandbox.app.resolve(BlockProcessor); diff --git a/packages/core-blockchain/src/processor/block-processor.ts b/packages/core-blockchain/src/processor/block-processor.ts index 7a8a7780a7..5fdde32da9 100644 --- a/packages/core-blockchain/src/processor/block-processor.ts +++ b/packages/core-blockchain/src/processor/block-processor.ts @@ -41,6 +41,9 @@ export class BlockProcessor { @Container.tagged("state", "blockchain") private readonly walletRepository!: Contracts.State.WalletRepository; + @Container.inject(Container.Identifiers.StateStore) + private readonly stateStore!: Contracts.State.StateStore; + @Container.inject(Container.Identifiers.TriggerService) private readonly triggers!: Services.Triggers.Triggers; @@ -123,13 +126,29 @@ export class BlockProcessor { private async checkBlockContainsForgedTransactions(block: Interfaces.IBlock): Promise { if (block.transactions.length > 0) { - const forgedIds: string[] = await this.transactionRepository.getForgedTransactionsIds( - block.transactions.map((tx) => { - AppUtils.assert.defined(tx.id); + const transactionIds = block.transactions.map((tx) => { + AppUtils.assert.defined(tx.id); - return tx.id; - }), - ); + return tx.id; + }); + + const forgedIds: string[] = await this.transactionRepository.getForgedTransactionsIds(transactionIds); + + if (this.stateStore.getLastBlock().data.height !== this.stateStore.getLastStoredBlockHeight()) { + const transactionIdsSet = new Set(transactionIds); + + for (const stateBlock of this.stateStore + .getLastBlocks() + .filter((block) => block.data.height > this.stateStore.getLastStoredBlockHeight())) { + stateBlock.transactions.forEach((tx) => { + AppUtils.assert.defined(tx.id); + + if (transactionIdsSet.has(tx.id)) { + forgedIds.push(tx.id); + } + }); + } + } /* istanbul ignore else */ if (forgedIds.length > 0) {