diff --git a/__tests__/unit/core-transaction-pool/collator.test.ts b/__tests__/unit/core-transaction-pool/collator.test.ts new file mode 100644 index 0000000000..f32ca03c49 --- /dev/null +++ b/__tests__/unit/core-transaction-pool/collator.test.ts @@ -0,0 +1,80 @@ +import { Container } from "@arkecosystem/core-kernel"; +import { Managers } from "@arkecosystem/crypto"; + +import { Collator } from "../../../packages/core-transaction-pool/src/collator"; + +jest.mock("@arkecosystem/crypto"); + +describe("Collator", () => { + const container = new Container.Container(); + + describe("getBlockCandidateTransactions", () => { + const validator = { validate: jest.fn() }; + const configuration = { get: jest.fn() }; + const createTransactionValidator = jest.fn(() => validator); + const blockchain = { getLastBlock: jest.fn() }; + const pool = { removeTransactionById: jest.fn() }; + const memory = { allSortedByFee: jest.fn() }; + const logger = { error: jest.fn() }; + + beforeAll(() => { + container.unbindAll(); + container.bind(Container.Identifiers.PluginConfiguration).toConstantValue(configuration); + container + .bind(Container.Identifiers.TransactionValidatorFactory) + .toConstantValue(createTransactionValidator); + container.bind(Container.Identifiers.BlockchainService).toConstantValue(blockchain); + container.bind(Container.Identifiers.TransactionPoolService).toConstantValue(pool); + container.bind(Container.Identifiers.TransactionPoolMemory).toConstantValue(memory); + container.bind(Container.Identifiers.LogService).toConstantValue(logger); + }); + + beforeEach(() => { + validator.validate.mockClear(); + configuration.get.mockClear(); + createTransactionValidator.mockClear(); + blockchain.getLastBlock.mockClear(); + memory.allSortedByFee.mockClear(); + logger.error.mockClear(); + }); + + it("should respect milestone transaction count limit", async () => { + const poolTransactions = new Array(10).fill({ data: "12345678" }); + const milestone = { block: { maxTransactions: 5 } }; + const lastBlock = { data: { height: 10 } }; + + (Managers.configManager.getMilestone as jest.Mock).mockReturnValueOnce(milestone); + blockchain.getLastBlock.mockReturnValueOnce(lastBlock); + memory.allSortedByFee.mockReturnValueOnce(poolTransactions); + + const collator = container.resolve(Collator); + const candidateTransaction = await collator.getBlockCandidateTransactions(); + + expect(candidateTransaction.length).toBe(5); + expect(configuration.get).toBeCalled(); + expect(Managers.configManager.getMilestone).toBeCalled(); + expect(createTransactionValidator).toBeCalled(); + expect(validator.validate).toBeCalledTimes(5); + }); + + it("should respect maxTransactionBytes configuration limit", async () => { + const poolTransactions = new Array(10).fill({ data: "12345678" }); + const milestone = { block: { maxTransactions: 100 } }; + const lastBlock = { data: { height: 10 } }; + + (Managers.configManager.getMilestone as jest.Mock).mockReturnValueOnce(milestone); + configuration.get.mockReturnValueOnce(25); + blockchain.getLastBlock.mockReturnValueOnce(lastBlock); + memory.allSortedByFee.mockReturnValueOnce(poolTransactions); + + const collator = container.resolve(Collator); + const candidateTransaction = await collator.getBlockCandidateTransactions(); + + expect(candidateTransaction.length).toBe(2); + expect(configuration.get).toBeCalled(); + expect(Managers.configManager.getMilestone).toBeCalled(); + expect(createTransactionValidator).toBeCalled(); + expect(validator.validate).toBeCalledTimes(2); + }); + }); +}); diff --git a/packages/core-transaction-pool/src/collator.ts b/packages/core-transaction-pool/src/collator.ts index a1d51c5f32..2d27a90598 100644 --- a/packages/core-transaction-pool/src/collator.ts +++ b/packages/core-transaction-pool/src/collator.ts @@ -26,26 +26,27 @@ export class Collator implements Contracts.TransactionPool.Collator { private readonly logger!: Contracts.Kernel.Logger; public async getBlockCandidateTransactions(): Promise { - let bytesLeft = this.configuration.get("maxTransactionBytes") ?? null; + let bytesLeft: number | undefined = this.configuration.get("maxTransactionBytes"); - const height = this.blockchain.getLastBlock().data.height; + const height: number = this.blockchain.getLastBlock().data.height; const milestone = Managers.configManager.getMilestone(height); const transactions: Interfaces.ITransaction[] = []; - const validator = this.createTransactionValidator(); + const validator: Contracts.State.TransactionValidator = this.createTransactionValidator(); for (const transaction of this.memory.allSortedByFee().slice()) { if (transactions.length === milestone.block.maxTransactions) { break; } + if (bytesLeft !== undefined) { + bytesLeft -= JSON.stringify(transaction.data).length; + if (bytesLeft < 0) { + break; + } + } + try { await validator.validate(transaction); - if (bytesLeft !== null) { - bytesLeft -= JSON.stringify(transaction.data).length; - if (bytesLeft < 0) { - break; - } - } transactions.push(transaction); } catch (error) { this.pool.removeTransactionById(transaction.id!);