From 9115130fd65f0971697459b9e5d914686d3fe027 Mon Sep 17 00:00:00 2001 From: sebastijankuzner Date: Mon, 11 May 2026 11:21:39 +0000 Subject: [PATCH 01/26] Implement transaction selector --- .../source/selector.ts | 71 +++++++++++++++++++ 1 file changed, 71 insertions(+) create mode 100644 packages/transaction-pool-service/source/selector.ts diff --git a/packages/transaction-pool-service/source/selector.ts b/packages/transaction-pool-service/source/selector.ts new file mode 100644 index 000000000..6930d0835 --- /dev/null +++ b/packages/transaction-pool-service/source/selector.ts @@ -0,0 +1,71 @@ +import type { Contracts } from "@mainsail/contracts"; + +import { Identifiers } from "@mainsail/constants"; +import { injectable, inject } from "@mainsail/container"; + +type GetBatchOptions = { + blockRound: string; + maxSize: number; + maxBytes: number; +}; + +type GetBatchResult = { + transactions: Contracts.Crypto.Transaction[]; + remaining: number; +}; + +@injectable() +export class TransactionSelector { + @inject(Identifiers.TransactionPool.Query) + private readonly poolQuery!: Contracts.TransactionPool.Query; + + #transactions: Contracts.Crypto.Transaction[] = []; + #currentBlockRound = ""; + #index = 0; + + public async getBatch(options: GetBatchOptions): Promise { + await this.#prepare(options.blockRound); + + const transactions: Contracts.Crypto.Transaction[] = []; + let bytesLeft = options.maxBytes; + + while (this.#index < this.#transactions.length) { + const transaction = this.#transactions[this.#index]; + + if (bytesLeft - 4 - transaction.serialized.length < 0) { + break; + } + + transactions.push(transaction); + bytesLeft -= 4; + bytesLeft -= transaction.serialized.length; + + if (transactions.length >= options.maxSize) { + break; + } + + this.#index++; + } + + return { + remaining: this.#transactions.length - this.#index, + transactions, + }; + } + + public clear(): void { + this.#transactions = []; + this.#currentBlockRound = ""; + this.#index = 0; + } + + async #prepare(blockRound: string): Promise { + if (this.#currentBlockRound === blockRound) { + return; + } + + this.#currentBlockRound = blockRound; + this.#index = 0; + this.#transactions = await this.poolQuery.getFromHighestPriority().all() + } +} From a17245fb37e0b8b9751bd522ab3f445a8a4fe69f Mon Sep 17 00:00:00 2001 From: sebastijankuzner Date: Mon, 11 May 2026 11:26:56 +0000 Subject: [PATCH 02/26] Add selector --- .../source/contracts/transaction-pool/index.ts | 1 + .../contracts/transaction-pool/selector.ts | 17 +++++++++++++++++ .../transaction-pool-service/source/selector.ts | 11 +---------- 3 files changed, 19 insertions(+), 10 deletions(-) create mode 100644 packages/contracts/source/contracts/transaction-pool/selector.ts diff --git a/packages/contracts/source/contracts/transaction-pool/index.ts b/packages/contracts/source/contracts/transaction-pool/index.ts index 721b88d7d..bece70e08 100644 --- a/packages/contracts/source/contracts/transaction-pool/index.ts +++ b/packages/contracts/source/contracts/transaction-pool/index.ts @@ -3,6 +3,7 @@ export * from "./client.js"; export * from "./mempool.js"; export * from "./processor.js"; export * from "./query.js"; +export * from "./selector.js"; export * from "./sender-mempool.js"; export * from "./sender-state.js"; export * from "./service.js"; diff --git a/packages/contracts/source/contracts/transaction-pool/selector.ts b/packages/contracts/source/contracts/transaction-pool/selector.ts new file mode 100644 index 000000000..36ea4b912 --- /dev/null +++ b/packages/contracts/source/contracts/transaction-pool/selector.ts @@ -0,0 +1,17 @@ +import type { Transaction } from "../crypto/index.js"; + +export type GetBatchOptions = { + blockRound: string; + maxSize: number; + maxBytes: number; +}; + +export type GetBatchResult = { + transactions: Transaction[]; + remaining: number; +}; + +export interface TransactionSelector { + getBatch(options: GetBatchOptions): Promise; + clear(): void; +} diff --git a/packages/transaction-pool-service/source/selector.ts b/packages/transaction-pool-service/source/selector.ts index 6930d0835..4db3a449c 100644 --- a/packages/transaction-pool-service/source/selector.ts +++ b/packages/transaction-pool-service/source/selector.ts @@ -3,16 +3,7 @@ import type { Contracts } from "@mainsail/contracts"; import { Identifiers } from "@mainsail/constants"; import { injectable, inject } from "@mainsail/container"; -type GetBatchOptions = { - blockRound: string; - maxSize: number; - maxBytes: number; -}; -type GetBatchResult = { - transactions: Contracts.Crypto.Transaction[]; - remaining: number; -}; @injectable() export class TransactionSelector { @@ -23,7 +14,7 @@ export class TransactionSelector { #currentBlockRound = ""; #index = 0; - public async getBatch(options: GetBatchOptions): Promise { + public async getBatch(options: Contracts.TransactionPool.GetBatchOptions): Promise { await this.#prepare(options.blockRound); const transactions: Contracts.Crypto.Transaction[] = []; From 37f2674111c189946c1a712a112302d4d9bc6440 Mon Sep 17 00:00:00 2001 From: sebastijankuzner Date: Mon, 11 May 2026 11:29:40 +0000 Subject: [PATCH 03/26] Register selector --- packages/constants/source/identifiers.ts | 1 + .../contracts/source/contracts/transaction-pool/selector.ts | 2 +- packages/transaction-pool-service/source/selector.ts | 2 +- packages/transaction-pool-service/source/service-provider.ts | 2 ++ 4 files changed, 5 insertions(+), 2 deletions(-) diff --git a/packages/constants/source/identifiers.ts b/packages/constants/source/identifiers.ts index 19eee1c92..a718add07 100644 --- a/packages/constants/source/identifiers.ts +++ b/packages/constants/source/identifiers.ts @@ -301,6 +301,7 @@ export const Identifiers = { Processor: Symbol("TransactionPool"), ProcessorExtension: Symbol("TransactionPool"), Query: Symbol("TransactionPool"), + Selector: Symbol("TransactionPool"), SenderMempool: { Factory: Symbol("TransactionPool"), }, diff --git a/packages/contracts/source/contracts/transaction-pool/selector.ts b/packages/contracts/source/contracts/transaction-pool/selector.ts index 36ea4b912..cc95fac9f 100644 --- a/packages/contracts/source/contracts/transaction-pool/selector.ts +++ b/packages/contracts/source/contracts/transaction-pool/selector.ts @@ -11,7 +11,7 @@ export type GetBatchResult = { remaining: number; }; -export interface TransactionSelector { +export interface Selector { getBatch(options: GetBatchOptions): Promise; clear(): void; } diff --git a/packages/transaction-pool-service/source/selector.ts b/packages/transaction-pool-service/source/selector.ts index 4db3a449c..89ceebb5c 100644 --- a/packages/transaction-pool-service/source/selector.ts +++ b/packages/transaction-pool-service/source/selector.ts @@ -6,7 +6,7 @@ import { injectable, inject } from "@mainsail/container"; @injectable() -export class TransactionSelector { +export class Selector implements Contracts.TransactionPool.Selector { @inject(Identifiers.TransactionPool.Query) private readonly poolQuery!: Contracts.TransactionPool.Query; diff --git a/packages/transaction-pool-service/source/service-provider.ts b/packages/transaction-pool-service/source/service-provider.ts index 395f44251..d96420704 100644 --- a/packages/transaction-pool-service/source/service-provider.ts +++ b/packages/transaction-pool-service/source/service-provider.ts @@ -9,6 +9,7 @@ import { ThrowIfCannotBeAppliedAction, VerifyTransactionAction } from "./actions import { Mempool } from "./mempool.js"; import { Processor } from "./processor.js"; import { Query } from "./query.js"; +import { Selector } from "./selector.js"; import { SenderMempool } from "./sender-mempool.js"; import { SenderState } from "./sender-state.js"; import { Service } from "./service.js"; @@ -64,6 +65,7 @@ export class ServiceProvider extends Providers.ServiceProvider { this.app.bind(Identifiers.TransactionPool.SenderState).to(SenderState); this.app.bind(Identifiers.TransactionPool.Service).to(Service).inSingletonScope(); this.app.bind(Identifiers.TransactionPool.Storage).to(Storage).inSingletonScope(); + this.app.bind(Identifiers.TransactionPool.Selector).to(Selector).inSingletonScope(); } #registerActions(): void { From 9f081f5fb67e534c68cd78f606ba6d983b34090b Mon Sep 17 00:00:00 2001 From: sebastijankuzner Date: Mon, 11 May 2026 11:49:50 +0000 Subject: [PATCH 04/26] Update worker --- .../contracts/transaction-pool/worker.ts | 3 +- .../source/handlers/get-transactions.ts | 29 +++---------------- .../source/worker-handler.ts | 4 +-- 3 files changed, 8 insertions(+), 28 deletions(-) diff --git a/packages/contracts/source/contracts/transaction-pool/worker.ts b/packages/contracts/source/contracts/transaction-pool/worker.ts index 5fbdaba44..3de4709ad 100644 --- a/packages/contracts/source/contracts/transaction-pool/worker.ts +++ b/packages/contracts/source/contracts/transaction-pool/worker.ts @@ -2,12 +2,13 @@ import type { CommitHandler } from "../crypto/index.js"; import type { EventListener } from "../kernel/index.js"; import type { EventCallback, Subprocess } from "../kernel/ipc.js"; import type { KeyValuePair } from "../types/index.js"; +import type { GetBatchResult, GetBatchOptions } from "./selector.js"; export type WorkerFlags = KeyValuePair; export interface WorkerScriptHandler { boot(flags: WorkerFlags): Promise; - getTransactions(): Promise; + getTransactions(options: GetBatchOptions): Promise; removeTransaction(address: string, id: string): Promise; commit(height: number, sendersAddresses: string[], consumedGas: number, isSyncing: boolean): Promise; setPeer(ip: string): Promise; diff --git a/packages/transaction-pool-worker/source/handlers/get-transactions.ts b/packages/transaction-pool-worker/source/handlers/get-transactions.ts index 76a134a7d..dc7b8c009 100644 --- a/packages/transaction-pool-worker/source/handlers/get-transactions.ts +++ b/packages/transaction-pool-worker/source/handlers/get-transactions.ts @@ -5,31 +5,10 @@ import { inject, injectable } from "@mainsail/container"; @injectable() export class GetTransactionsHandler { - @inject(Identifiers.TransactionPool.Query) - private readonly poolQuery!: Contracts.TransactionPool.Query; + @inject(Identifiers.TransactionPool.Selector) + private readonly selector!: Contracts.TransactionPool.Selector; - @inject(Identifiers.Cryptography.Configuration) - private readonly configuration!: Contracts.Crypto.Configuration; - - @inject(Identifiers.Cryptography.Block.HeaderSize) - private readonly headerSize!: () => number; - - public async handle(): Promise { - const milestone = this.configuration.getMilestone(); - let bytesLeft: number = milestone.block.maxPayload - this.headerSize(); - - const candidateTransactions: Contracts.Crypto.Transaction[] = []; - for (const transaction of await this.poolQuery.getFromHighestPriority().all()) { - if (bytesLeft - 4 - transaction.serialized.length < 0) { - break; - } - - candidateTransactions.push(transaction); - - bytesLeft -= 4; - bytesLeft -= transaction.serialized.length; - } - - return candidateTransactions.map((transaction) => transaction.serialized.toString("hex")); + public async handle(options: Contracts.TransactionPool.GetBatchOptions): Promise { + return this.selector.getBatch(options); } } diff --git a/packages/transaction-pool-worker/source/worker-handler.ts b/packages/transaction-pool-worker/source/worker-handler.ts index 31aca6f85..89667dc32 100644 --- a/packages/transaction-pool-worker/source/worker-handler.ts +++ b/packages/transaction-pool-worker/source/worker-handler.ts @@ -39,8 +39,8 @@ export class WorkerScriptHandler implements Contracts.TransactionPool.WorkerScri await this.#app.resolve(CommitHandler).handle(height, sendersAddresses, consumedGas, isSyncing); } - public async getTransactions(): Promise { - return await this.#app.resolve(GetTransactionsHandler).handle(); + public async getTransactions(options: Contracts.TransactionPool.GetBatchOptions): Promise { + return await this.#app.resolve(GetTransactionsHandler).handle(options); } public async removeTransaction(address: string, id: string): Promise { From f25c2067186197b57638fd33df6ad01920c320f4 Mon Sep 17 00:00:00 2001 From: sebastijankuzner Date: Mon, 11 May 2026 11:53:48 +0000 Subject: [PATCH 05/26] Clean packages on ts:token --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 939bd1a59..90e287337 100644 --- a/package.json +++ b/package.json @@ -12,7 +12,7 @@ "build:rs": "lerna run build-rs --scope=@mainsail/evm", "build:rs:token": "echo \"force-rs $(date +%s)\" > .build-rs", "build:ts": "lerna run build", - "build:ts:token": "echo \"force-ts $(date +%s)\" > .build-ts", + "build:ts:token": "echo \"force-ts $(date +%s)\" > .build-ts && pnpm run clean:packages", "clean": "pnpm run clean:packages && rm -r .nx", "clean:all": "pnpm run clean && pnpm run clean:node_modules", "clean:node_modules": "rm -rf packages/*/node_modules && rm -rf node_modules", From 707ee3b95ded6e8a8ea1e1d02cc47c826ab3aa70 Mon Sep 17 00:00:00 2001 From: sebastijankuzner Date: Mon, 11 May 2026 12:04:08 +0000 Subject: [PATCH 06/26] Update forger --- .../contracts/transaction-pool/worker.ts | 3 +- packages/forger/source/transaction-forger.ts | 108 +++++++++--------- .../transaction-pool-worker/source/worker.ts | 5 +- 3 files changed, 60 insertions(+), 56 deletions(-) diff --git a/packages/contracts/source/contracts/transaction-pool/worker.ts b/packages/contracts/source/contracts/transaction-pool/worker.ts index 3de4709ad..197bf4b48 100644 --- a/packages/contracts/source/contracts/transaction-pool/worker.ts +++ b/packages/contracts/source/contracts/transaction-pool/worker.ts @@ -23,9 +23,8 @@ export type WorkerSubprocess = Subprocess; export type WorkerSubprocessFactory = () => WorkerSubprocess; -export interface Worker extends Omit, CommitHandler, EventListener { +export interface Worker extends Omit, CommitHandler, EventListener { getQueueSize(): number; kill(): Promise; - getTransactionBytes(): Promise; registerEventHandler(event: string, callback: EventCallback): void; } diff --git a/packages/forger/source/transaction-forger.ts b/packages/forger/source/transaction-forger.ts index 92103fc06..451c66125 100644 --- a/packages/forger/source/transaction-forger.ts +++ b/packages/forger/source/transaction-forger.ts @@ -26,9 +26,6 @@ export class TransactionForger implements Contracts.Forger.TransactionForger { @inject(Identifiers.Transaction.Validator.Factory) private readonly createTransactionValidator!: Contracts.Transactions.TransactionValidatorFactory; - @inject(Identifiers.Cryptography.Transaction.Factory) - private readonly transactionFactory!: Contracts.Crypto.TransactionFactory; - @inject(Identifiers.Services.Log.Service) private readonly logger!: Contracts.Kernel.Logger; @@ -49,8 +46,6 @@ export class TransactionForger implements Contracts.Forger.TransactionForger { gasUsed: number; fee: bigint; }> { - const transactionBytes = await this.txPoolWorker.getTransactionBytes(); - const validator = this.createTransactionValidator(); const evm = validator.getEvm(); @@ -72,70 +67,81 @@ export class TransactionForger implements Contracts.Forger.TransactionForger { performance.now() + milestone.timeouts.blockPrepareTime * this.pluginConfiguration.getRequired("txCollatorFactor"); - for (const bytes of transactionBytes.values()) { - if (performance.now() > timeLimit) { - break; - } - - const transaction = await this.transactionFactory.fromBytes(bytes); + while (true) { + const batch = await this.txPoolWorker.getTransactions({ + blockRound: "0-0", + maxBytes: 10_000_000, + maxSize: 100, - if (failedSenders.has(transaction.senderPublicKey)) { - continue; - } + }); - try { - if (gasLeft < 21000) { + for (const transaction of batch.transactions) { + if (performance.now() > timeLimit) { break; } - let optimisticExecution = false; + if (failedSenders.has(transaction.senderPublicKey)) { + continue; + } - const gasLimit = transaction.gasLimit; - if (gasLeft - gasLimit < 0) { - // Optimistically execute transaction even if the gas limit exceeds the remaining - // block space since there's possibly still space to fit the actual gas consumed. + try { + if (gasLeft < 21000) { + break; + } - // If the consumed gas exceeds the remaining block space, we ignore the transaction and - // calculate the root from the previous state (rollback). - optimisticExecution = true; - this.logger.info( - `attempting optimistic execution of tx ${transaction.hash} (tx.gas=${gasLimit} gasLeft=${gasLeft})`, - ); + let optimisticExecution = false; - await evm.snapshot(commitKey); - } + const gasLimit = transaction.gasLimit; + if (gasLeft - gasLimit < 0) { + // Optimistically execute transaction even if the gas limit exceeds the remaining + // block space since there's possibly still space to fit the actual gas consumed. - const result = await validator.validate( - { commitKey, gasLimit: milestone.block.maxGasLimit, generatorAddress, timestamp }, - transaction, - ); + // If the consumed gas exceeds the remaining block space, we ignore the transaction and + // calculate the root from the previous state (rollback). + optimisticExecution = true; + this.logger.info( + `attempting optimistic execution of tx ${transaction.hash} (tx.gas=${gasLimit} gasLeft=${gasLeft})`, + ); - gasLeft -= Number(result.gasUsed); + await evm.snapshot(commitKey); + } - // Ignore transaction if it uses more than what's left. - if (gasLeft < 0) { - this.logger.warn( - `skipping tx ${transaction.hash} due to insufficient block space (tx.gasUsed=${Number(result.gasUsed)} gasLeft=${gasLeft} optimistic=${optimisticExecution})`, + const result = await validator.validate( + { commitKey, gasLimit: milestone.block.maxGasLimit, generatorAddress, timestamp }, + transaction, ); - if (optimisticExecution) { - await evm.rollback(commitKey); + gasLeft -= Number(result.gasUsed); + + // Ignore transaction if it uses more than what's left. + if (gasLeft < 0) { + this.logger.warn( + `skipping tx ${transaction.hash} due to insufficient block space (tx.gasUsed=${Number(result.gasUsed)} gasLeft=${gasLeft} optimistic=${optimisticExecution})`, + ); + + if (optimisticExecution) { + await evm.rollback(commitKey); + } + + break; } - break; - } + gasUsed += Number(result.gasUsed); + fee += this.gasFeeCalculator.calculateConsumed(transaction.gasPrice, result.gasUsed); + candidateTransactions.push(transaction); + } catch (error) { + this.logger.warn( + `tx ${transaction.hash} from ${transaction.from} failed to collate: ${error.message}`, + ); - gasUsed += Number(result.gasUsed); - fee += this.gasFeeCalculator.calculateConsumed(transaction.gasPrice, result.gasUsed); - candidateTransactions.push(transaction); - } catch (error) { - this.logger.warn( - `tx ${transaction.hash} from ${transaction.from} failed to collate: ${error.message}`, - ); + await this.txPoolWorker.removeTransaction(transaction.from, transaction.hash); - await this.txPoolWorker.removeTransaction(transaction.from, transaction.hash); + failedSenders.add(transaction.senderPublicKey); + } + } - failedSenders.add(transaction.senderPublicKey); + if(batch.remaining === 0) { + break; } } diff --git a/packages/transaction-pool-worker/source/worker.ts b/packages/transaction-pool-worker/source/worker.ts index d8707dc18..0211fc1ee 100644 --- a/packages/transaction-pool-worker/source/worker.ts +++ b/packages/transaction-pool-worker/source/worker.ts @@ -79,9 +79,8 @@ export class Worker implements Contracts.TransactionPool.Worker { await this.ipcSubprocess.sendRequest("start", blockNumber); } - public async getTransactionBytes(): Promise { - const response: string[] = await this.ipcSubprocess.sendRequest("getTransactions"); - return response.map((transaction: string) => Buffer.from(transaction, "hex")); + public async getTransactions(options: Contracts.TransactionPool.GetBatchOptions): Promise { + return this.ipcSubprocess.sendRequest("getTransactions", options); } public async removeTransaction(address: string, id: string): Promise { From c3f94fa9a51c3ac77c68f196f7e824a53f47a64a Mon Sep 17 00:00:00 2001 From: sebastijankuzner Date: Mon, 11 May 2026 12:40:24 +0000 Subject: [PATCH 07/26] Pass transaction data --- .../source/contracts/transaction-pool/selector.ts | 4 ++-- packages/forger/source/transaction-forger.ts | 9 ++++++++- packages/transaction-pool-service/source/selector.ts | 6 ++---- 3 files changed, 12 insertions(+), 7 deletions(-) diff --git a/packages/contracts/source/contracts/transaction-pool/selector.ts b/packages/contracts/source/contracts/transaction-pool/selector.ts index cc95fac9f..379596a85 100644 --- a/packages/contracts/source/contracts/transaction-pool/selector.ts +++ b/packages/contracts/source/contracts/transaction-pool/selector.ts @@ -1,4 +1,4 @@ -import type { Transaction } from "../crypto/index.js"; +import type { TransactionData } from "../crypto/index.js"; export type GetBatchOptions = { blockRound: string; @@ -7,7 +7,7 @@ export type GetBatchOptions = { }; export type GetBatchResult = { - transactions: Transaction[]; + transactions: TransactionData[]; remaining: number; }; diff --git a/packages/forger/source/transaction-forger.ts b/packages/forger/source/transaction-forger.ts index 451c66125..d4f792321 100644 --- a/packages/forger/source/transaction-forger.ts +++ b/packages/forger/source/transaction-forger.ts @@ -14,6 +14,9 @@ export class TransactionForger implements Contracts.Forger.TransactionForger { @inject(Identifiers.Cryptography.Configuration) private readonly cryptoConfiguration!: Contracts.Crypto.Configuration; + @inject(Identifiers.Cryptography.Transaction.Factory) + private readonly transactionFactory!: Contracts.Crypto.TransactionFactory; + @inject(Identifiers.State.Store) protected readonly stateStore!: Contracts.State.Store; @@ -75,11 +78,15 @@ export class TransactionForger implements Contracts.Forger.TransactionForger { }); - for (const transaction of batch.transactions) { + console.log(`forging batch with ${batch.transactions.length} transactions, remaining in pool: ${batch.remaining}`); + + for (const tx of batch.transactions) { if (performance.now() > timeLimit) { break; } + const transaction = await this.transactionFactory.fromData(tx); + if (failedSenders.has(transaction.senderPublicKey)) { continue; } diff --git a/packages/transaction-pool-service/source/selector.ts b/packages/transaction-pool-service/source/selector.ts index 89ceebb5c..c029d2d62 100644 --- a/packages/transaction-pool-service/source/selector.ts +++ b/packages/transaction-pool-service/source/selector.ts @@ -3,8 +3,6 @@ import type { Contracts } from "@mainsail/contracts"; import { Identifiers } from "@mainsail/constants"; import { injectable, inject } from "@mainsail/container"; - - @injectable() export class Selector implements Contracts.TransactionPool.Selector { @inject(Identifiers.TransactionPool.Query) @@ -17,7 +15,7 @@ export class Selector implements Contracts.TransactionPool.Selector { public async getBatch(options: Contracts.TransactionPool.GetBatchOptions): Promise { await this.#prepare(options.blockRound); - const transactions: Contracts.Crypto.Transaction[] = []; + const transactions: Contracts.Crypto.TransactionData[] = []; let bytesLeft = options.maxBytes; while (this.#index < this.#transactions.length) { @@ -27,7 +25,7 @@ export class Selector implements Contracts.TransactionPool.Selector { break; } - transactions.push(transaction); + transactions.push(transaction.toData()); bytesLeft -= 4; bytesLeft -= transaction.serialized.length; From 30037480417338c1fdc5c5d2cf42b19b74ad2d98 Mon Sep 17 00:00:00 2001 From: sebastijankuzner Date: Mon, 11 May 2026 12:43:23 +0000 Subject: [PATCH 08/26] Use block-round identifier --- packages/forger/source/transaction-forger.ts | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/packages/forger/source/transaction-forger.ts b/packages/forger/source/transaction-forger.ts index d4f792321..26abc828e 100644 --- a/packages/forger/source/transaction-forger.ts +++ b/packages/forger/source/transaction-forger.ts @@ -72,10 +72,9 @@ export class TransactionForger implements Contracts.Forger.TransactionForger { while (true) { const batch = await this.txPoolWorker.getTransactions({ - blockRound: "0-0", + blockRound: `${commitKey.blockNumber}-${commitKey.round}`, maxBytes: 10_000_000, maxSize: 100, - }); console.log(`forging batch with ${batch.transactions.length} transactions, remaining in pool: ${batch.remaining}`); From bcdc9ee9cc438742e7c97798b7af3e333059352b Mon Sep 17 00:00:00 2001 From: sebastijankuzner Date: Mon, 11 May 2026 16:14:43 +0000 Subject: [PATCH 09/26] Use iterator --- packages/forger/source/transaction-forger.ts | 116 ++++++++---------- .../forger/source/transaction-iterable.ts | 40 ++++++ 2 files changed, 93 insertions(+), 63 deletions(-) create mode 100644 packages/forger/source/transaction-iterable.ts diff --git a/packages/forger/source/transaction-forger.ts b/packages/forger/source/transaction-forger.ts index 26abc828e..11bb1cbc5 100644 --- a/packages/forger/source/transaction-forger.ts +++ b/packages/forger/source/transaction-forger.ts @@ -5,8 +5,13 @@ import { inject, injectable, tagged } from "@mainsail/container"; import { Identifiers as EvmConsensusIdentifiers } from "@mainsail/evm-consensus"; import { performance } from "perf_hooks"; +import { TransactionIterable } from "./transaction-iterable.js"; + @injectable() export class TransactionForger implements Contracts.Forger.TransactionForger { + @inject(Identifiers.Application.Instance) + private readonly app!: Contracts.Kernel.Application; + @inject(Identifiers.ServiceProvider.Configuration) @tagged("plugin", "forger") private readonly pluginConfiguration!: Contracts.Kernel.PluginConfiguration; @@ -14,9 +19,6 @@ export class TransactionForger implements Contracts.Forger.TransactionForger { @inject(Identifiers.Cryptography.Configuration) private readonly cryptoConfiguration!: Contracts.Crypto.Configuration; - @inject(Identifiers.Cryptography.Transaction.Factory) - private readonly transactionFactory!: Contracts.Crypto.TransactionFactory; - @inject(Identifiers.State.Store) protected readonly stateStore!: Contracts.State.Store; @@ -70,84 +72,72 @@ export class TransactionForger implements Contracts.Forger.TransactionForger { performance.now() + milestone.timeouts.blockPrepareTime * this.pluginConfiguration.getRequired("txCollatorFactor"); - while (true) { - const batch = await this.txPoolWorker.getTransactions({ - blockRound: `${commitKey.blockNumber}-${commitKey.round}`, - maxBytes: 10_000_000, - maxSize: 100, - }); + const transactionIterable = this.app + .resolve(TransactionIterable) + .initialize(commitKey); - console.log(`forging batch with ${batch.transactions.length} transactions, remaining in pool: ${batch.remaining}`); - - for (const tx of batch.transactions) { - if (performance.now() > timeLimit) { - break; - } + for await (const transaction of transactionIterable) { + if (performance.now() > timeLimit) { + break; + } - const transaction = await this.transactionFactory.fromData(tx); + if (failedSenders.has(transaction.senderPublicKey)) { + continue; + } - if (failedSenders.has(transaction.senderPublicKey)) { - continue; + try { + if (gasLeft < 21000) { + break; } - try { - if (gasLeft < 21000) { - break; - } - - let optimisticExecution = false; + let optimisticExecution = false; - const gasLimit = transaction.gasLimit; - if (gasLeft - gasLimit < 0) { - // Optimistically execute transaction even if the gas limit exceeds the remaining - // block space since there's possibly still space to fit the actual gas consumed. + const gasLimit = transaction.gasLimit; + if (gasLeft - gasLimit < 0) { + // Optimistically execute transaction even if the gas limit exceeds the remaining + // block space since there's possibly still space to fit the actual gas consumed. - // If the consumed gas exceeds the remaining block space, we ignore the transaction and - // calculate the root from the previous state (rollback). - optimisticExecution = true; - this.logger.info( - `attempting optimistic execution of tx ${transaction.hash} (tx.gas=${gasLimit} gasLeft=${gasLeft})`, - ); - - await evm.snapshot(commitKey); - } - - const result = await validator.validate( - { commitKey, gasLimit: milestone.block.maxGasLimit, generatorAddress, timestamp }, - transaction, + // If the consumed gas exceeds the remaining block space, we ignore the transaction and + // calculate the root from the previous state (rollback). + optimisticExecution = true; + this.logger.info( + `attempting optimistic execution of tx ${transaction.hash} (tx.gas=${gasLimit} gasLeft=${gasLeft})`, ); - gasLeft -= Number(result.gasUsed); - - // Ignore transaction if it uses more than what's left. - if (gasLeft < 0) { - this.logger.warn( - `skipping tx ${transaction.hash} due to insufficient block space (tx.gasUsed=${Number(result.gasUsed)} gasLeft=${gasLeft} optimistic=${optimisticExecution})`, - ); + await evm.snapshot(commitKey); + } - if (optimisticExecution) { - await evm.rollback(commitKey); - } + const result = await validator.validate( + { commitKey, gasLimit: milestone.block.maxGasLimit, generatorAddress, timestamp }, + transaction, + ); - break; - } + gasLeft -= Number(result.gasUsed); - gasUsed += Number(result.gasUsed); - fee += this.gasFeeCalculator.calculateConsumed(transaction.gasPrice, result.gasUsed); - candidateTransactions.push(transaction); - } catch (error) { + // Ignore transaction if it uses more than what's left. + if (gasLeft < 0) { this.logger.warn( - `tx ${transaction.hash} from ${transaction.from} failed to collate: ${error.message}`, + `skipping tx ${transaction.hash} due to insufficient block space (tx.gasUsed=${Number(result.gasUsed)} gasLeft=${gasLeft} optimistic=${optimisticExecution})`, ); - await this.txPoolWorker.removeTransaction(transaction.from, transaction.hash); + if (optimisticExecution) { + await evm.rollback(commitKey); + } - failedSenders.add(transaction.senderPublicKey); + break; } - } - if(batch.remaining === 0) { - break; + gasUsed += Number(result.gasUsed); + fee += this.gasFeeCalculator.calculateConsumed(transaction.gasPrice, result.gasUsed); + candidateTransactions.push(transaction); + } catch (error) { + this.logger.warn( + `tx ${transaction.hash} from ${transaction.from} failed to collate: ${error.message}`, + ); + + await this.txPoolWorker.removeTransaction(transaction.from, transaction.hash); + + failedSenders.add(transaction.senderPublicKey); } } diff --git a/packages/forger/source/transaction-iterable.ts b/packages/forger/source/transaction-iterable.ts new file mode 100644 index 000000000..ecac94e26 --- /dev/null +++ b/packages/forger/source/transaction-iterable.ts @@ -0,0 +1,40 @@ +import type { Contracts } from "@mainsail/contracts"; + +import { Identifiers } from "@mainsail/constants"; +import { inject, injectable } from "@mainsail/container"; + + +@injectable() +export class TransactionIterable implements AsyncIterable { + @inject(Identifiers.TransactionPool.Worker) + private readonly txPoolWorker!: Contracts.TransactionPool.Worker; + + @inject(Identifiers.Cryptography.Transaction.Factory) + private readonly transactionFactory!: Contracts.Crypto.TransactionFactory; + + #commitKey!: Contracts.Evm.CommitKey; + + public initialize(commitKey: Contracts.Evm.CommitKey): TransactionIterable { + this.#commitKey = commitKey; + return this + } + + public async *[Symbol.asyncIterator](): AsyncIterator { + while (true) { + const batch = await this.txPoolWorker.getTransactions({ + blockRound: `${this.#commitKey.blockNumber}-${this.#commitKey.round}`, + maxBytes: 10_000_000, + maxSize: 100, + }); + + for (const tx of batch.transactions) { + const transaction = await this.transactionFactory.fromData(tx); + yield transaction; + } + + if(batch.transactions.length === 0) { + return; + } + } + } +} From f4849ca66fec0854bcf219740baca0d8aa9ed878 Mon Sep 17 00:00:00 2001 From: sebastijankuzner Date: Mon, 11 May 2026 16:16:14 +0000 Subject: [PATCH 10/26] Inline --- packages/forger/source/transaction-forger.ts | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/packages/forger/source/transaction-forger.ts b/packages/forger/source/transaction-forger.ts index 11bb1cbc5..8b163d092 100644 --- a/packages/forger/source/transaction-forger.ts +++ b/packages/forger/source/transaction-forger.ts @@ -161,14 +161,11 @@ export class TransactionForger implements Contracts.Forger.TransactionForger { }); } - const logsBloom = await evm.logsBloom(commitKey); - const stateRoot = await evm.stateRoot(commitKey, previousBlock.stateRoot); - return { fee, gasUsed, - logsBloom, - stateRoot, + logsBloom: await evm.logsBloom(commitKey), + stateRoot: await evm.stateRoot(commitKey, previousBlock.stateRoot), transactions: candidateTransactions, }; } finally { From e40f3a2052723a730d8fc54480bb854d8ffa2a81 Mon Sep 17 00:00:00 2001 From: sebastijankuzner Date: Mon, 11 May 2026 16:23:55 +0000 Subject: [PATCH 11/26] Use initializer --- packages/forger/source/block-forger.ts | 22 +++++----- packages/forger/source/service-provider.ts | 2 - packages/forger/source/transaction-forger.ts | 44 +++++++++++++------- 3 files changed, 39 insertions(+), 29 deletions(-) diff --git a/packages/forger/source/block-forger.ts b/packages/forger/source/block-forger.ts index 1a6889a08..b88d99b86 100644 --- a/packages/forger/source/block-forger.ts +++ b/packages/forger/source/block-forger.ts @@ -4,8 +4,13 @@ import { Identifiers } from "@mainsail/constants"; import { inject, injectable } from "@mainsail/container"; import { assert } from "@mainsail/utils"; +import { TransactionForger } from "./transaction-forger.js"; + @injectable() export class BlockForger implements Contracts.Forger.BlockForger { + @inject(Identifiers.Application.Instance) + private readonly app!: Contracts.Kernel.Application; + @inject(Identifiers.Cryptography.Configuration) private readonly cryptoConfiguration!: Contracts.Crypto.Configuration; @@ -18,9 +23,6 @@ export class BlockForger implements Contracts.Forger.BlockForger { @inject(Identifiers.Cryptography.Hash.Factory) private readonly hashFactory!: Contracts.Crypto.HashFactory; - @inject(Identifiers.Forger.Transaction) - protected readonly transactionForger!: Contracts.Forger.TransactionForger; - @inject(Identifiers.BlockchainUtils.FeeCalculator) protected readonly gasFeeCalculator!: Contracts.BlockchainUtils.FeeCalculator; @@ -32,14 +34,12 @@ export class BlockForger implements Contracts.Forger.BlockForger { const previousBlock = this.stateStore.getLastBlock(); const blockNumber = previousBlock.number + 1; - const { fee, gasUsed, logsBloom, stateRoot, transactions } = await this.transactionForger.getTransactions( - generatorAddress, - timestamp, - { - blockNumber: BigInt(blockNumber), - round: BigInt(round), - }, - ); + const transactionForger = this.app.resolve(TransactionForger).initialize(generatorAddress, timestamp, { + blockNumber: BigInt(blockNumber), + round: BigInt(round), + }); + + const { fee, gasUsed, logsBloom, stateRoot, transactions } = await transactionForger.getTransactions(); return this.#makeBlock(round, generatorAddress, logsBloom, stateRoot, transactions, timestamp, gasUsed, fee); } diff --git a/packages/forger/source/service-provider.ts b/packages/forger/source/service-provider.ts index 6cd2f511e..63d82ab52 100644 --- a/packages/forger/source/service-provider.ts +++ b/packages/forger/source/service-provider.ts @@ -4,12 +4,10 @@ import { Providers } from "@mainsail/kernel"; import Joi from "joi"; import { BlockForger } from "./block-forger.js"; -import { TransactionForger } from "./transaction-forger.js"; @injectable() export class ServiceProvider extends Providers.ServiceProvider { public async register(): Promise { - this.app.bind(Identifiers.Forger.Transaction).to(TransactionForger).inSingletonScope(); this.app.bind(Identifiers.Forger.Block).to(BlockForger).inSingletonScope(); } diff --git a/packages/forger/source/transaction-forger.ts b/packages/forger/source/transaction-forger.ts index 8b163d092..bf9310fd3 100644 --- a/packages/forger/source/transaction-forger.ts +++ b/packages/forger/source/transaction-forger.ts @@ -40,11 +40,23 @@ export class TransactionForger implements Contracts.Forger.TransactionForger { @inject(Identifiers.BlockchainUtils.FeeCalculator) protected readonly gasFeeCalculator!: Contracts.BlockchainUtils.FeeCalculator; - async getTransactions( + #generatorAddress!: string; + #timestamp!: number + #commitKey!: Contracts.Evm.CommitKey; + + public initialize( generatorAddress: string, timestamp: number, - commitKey: Contracts.Evm.CommitKey, - ): Promise<{ + commitKey: Contracts.Evm.CommitKey + ): TransactionForger { + this.#generatorAddress = generatorAddress; + this.#timestamp = timestamp; + this.#commitKey = commitKey; + + return this; + } + + async getTransactions(): Promise<{ logsBloom: string; stateRoot: string; transactions: Contracts.Crypto.Transaction[]; @@ -56,7 +68,7 @@ export class TransactionForger implements Contracts.Forger.TransactionForger { try { await evm.initializeGenesis(this.genesisInfo); - await evm.prepareNextCommit({ commitKey }); + await evm.prepareNextCommit({ commitKey: this.#commitKey }); const candidateTransactions: Contracts.Crypto.Transaction[] = []; const failedSenders: Set = new Set(); @@ -74,7 +86,7 @@ export class TransactionForger implements Contracts.Forger.TransactionForger { const transactionIterable = this.app .resolve(TransactionIterable) - .initialize(commitKey); + .initialize(this.#commitKey); for await (const transaction of transactionIterable) { if (performance.now() > timeLimit) { @@ -104,11 +116,11 @@ export class TransactionForger implements Contracts.Forger.TransactionForger { `attempting optimistic execution of tx ${transaction.hash} (tx.gas=${gasLimit} gasLeft=${gasLeft})`, ); - await evm.snapshot(commitKey); + await evm.snapshot(this.#commitKey); } const result = await validator.validate( - { commitKey, gasLimit: milestone.block.maxGasLimit, generatorAddress, timestamp }, + { commitKey: this.#commitKey, gasLimit: milestone.block.maxGasLimit, generatorAddress: this.#generatorAddress, timestamp: this.#timestamp }, transaction, ); @@ -121,7 +133,7 @@ export class TransactionForger implements Contracts.Forger.TransactionForger { ); if (optimisticExecution) { - await evm.rollback(commitKey); + await evm.rollback(this.#commitKey); } break; @@ -143,29 +155,29 @@ export class TransactionForger implements Contracts.Forger.TransactionForger { await evm.updateRewardsAndVotes({ blockReward: BigInt(milestone.reward), - commitKey, + commitKey: this.#commitKey, specId: milestone.evmSpec, - timestamp: BigInt(timestamp), - validatorAddress: generatorAddress, + timestamp: BigInt(this.#timestamp), + validatorAddress: this.#generatorAddress, }); if (this.roundCalculator.isNewRound(previousBlock.number + 2)) { const { roundValidators } = this.cryptoConfiguration.getMilestone(previousBlock.number + 2); await evm.calculateRoundValidators({ - commitKey, + commitKey: this.#commitKey, roundValidators: BigInt(roundValidators), specId: milestone.evmSpec, - timestamp: BigInt(timestamp), - validatorAddress: generatorAddress, + timestamp: BigInt(this.#timestamp), + validatorAddress: this.#generatorAddress, }); } return { fee, gasUsed, - logsBloom: await evm.logsBloom(commitKey), - stateRoot: await evm.stateRoot(commitKey, previousBlock.stateRoot), + logsBloom: await evm.logsBloom(this.#commitKey), + stateRoot: await evm.stateRoot(this.#commitKey, previousBlock.stateRoot), transactions: candidateTransactions, }; } finally { From 55dc5d5b793e4f925ebe64c4c8474b639205dc82 Mon Sep 17 00:00:00 2001 From: sebastijankuzner Date: Mon, 11 May 2026 16:50:55 +0000 Subject: [PATCH 12/26] Reduce complexity --- packages/forger/source/transaction-forger.ts | 222 +++++++++++-------- 1 file changed, 129 insertions(+), 93 deletions(-) diff --git a/packages/forger/source/transaction-forger.ts b/packages/forger/source/transaction-forger.ts index bf9310fd3..532eee279 100644 --- a/packages/forger/source/transaction-forger.ts +++ b/packages/forger/source/transaction-forger.ts @@ -41,147 +41,183 @@ export class TransactionForger implements Contracts.Forger.TransactionForger { protected readonly gasFeeCalculator!: Contracts.BlockchainUtils.FeeCalculator; #generatorAddress!: string; - #timestamp!: number + #timestamp!: number; #commitKey!: Contracts.Evm.CommitKey; + #validator!: Contracts.Transactions.TransactionValidator; + #evm!: Contracts.Evm.Instance; + #milestone!: Contracts.Crypto.Milestone; + #previousBlock!: Contracts.Crypto.Block; + #timeLimit!: number; + + #failedSenders: Set = new Set(); + public initialize( generatorAddress: string, timestamp: number, - commitKey: Contracts.Evm.CommitKey + commitKey: Contracts.Evm.CommitKey, ): TransactionForger { this.#generatorAddress = generatorAddress; this.#timestamp = timestamp; this.#commitKey = commitKey; + this.#validator = this.createTransactionValidator(); + this.#evm = this.#validator.getEvm(); + + this.#milestone = this.cryptoConfiguration.getMilestone(); + this.#previousBlock = this.stateStore.getLastBlock(); + + // txCollatorFactor% of the time for block preparation, the rest is for block and proposal serialization and signing + this.#timeLimit = + performance.now() + + this.#milestone.timeouts.blockPrepareTime * + this.pluginConfiguration.getRequired("txCollatorFactor"); + return this; } - async getTransactions(): Promise<{ + public async getTransactions(): Promise<{ logsBloom: string; stateRoot: string; transactions: Contracts.Crypto.Transaction[]; gasUsed: number; fee: bigint; }> { - const validator = this.createTransactionValidator(); - const evm = validator.getEvm(); - try { - await evm.initializeGenesis(this.genesisInfo); - await evm.prepareNextCommit({ commitKey: this.#commitKey }); + await this.#evm.initializeGenesis(this.genesisInfo); + await this.#evm.prepareNextCommit({ commitKey: this.#commitKey }); - const candidateTransactions: Contracts.Crypto.Transaction[] = []; - const failedSenders: Set = new Set(); + const { fee, gasUsed, transactions } = await this.#processTransactions(); - const previousBlock = this.stateStore.getLastBlock(); - const milestone = this.cryptoConfiguration.getMilestone(); - let gasLeft = milestone.block.maxGasLimit; - let gasUsed = 0; - let fee = 0n; + await this.#updateRewardsAndVotes(); + await this.#calculateRoundValidators(); - // txCollatorFactor% of the time for block preparation, the rest is for block and proposal serialization and signing - const timeLimit = - performance.now() + - milestone.timeouts.blockPrepareTime * this.pluginConfiguration.getRequired("txCollatorFactor"); + return { + fee, + gasUsed, + logsBloom: await this.#evm.logsBloom(this.#commitKey), + stateRoot: await this.#evm.stateRoot(this.#commitKey, this.#previousBlock.stateRoot), + transactions, + }; + } finally { + await this.#evm.dispose(); + } + } - const transactionIterable = this.app - .resolve(TransactionIterable) - .initialize(this.#commitKey); + async #processTransactions(): Promise<{ + fee: bigint; + gasUsed: number; + transactions: Contracts.Crypto.Transaction[]; + }> { + const transactions: Contracts.Crypto.Transaction[] = []; - for await (const transaction of transactionIterable) { - if (performance.now() > timeLimit) { - break; - } + let gasLeft = this.#milestone.block.maxGasLimit; + let gasUsed = 0; + let fee = 0n; - if (failedSenders.has(transaction.senderPublicKey)) { - continue; - } + const transactionIterable = this.app + .resolve(TransactionIterable) + .initialize(this.#commitKey); - try { - if (gasLeft < 21000) { - break; - } + for await (const transaction of transactionIterable) { + if (performance.now() > this.#timeLimit) { + break; + } - let optimisticExecution = false; + if (this.#failedSenders.has(transaction.senderPublicKey)) { + continue; + } - const gasLimit = transaction.gasLimit; - if (gasLeft - gasLimit < 0) { - // Optimistically execute transaction even if the gas limit exceeds the remaining - // block space since there's possibly still space to fit the actual gas consumed. + try { + if (gasLeft < 21000) { + break; + } - // If the consumed gas exceeds the remaining block space, we ignore the transaction and - // calculate the root from the previous state (rollback). - optimisticExecution = true; - this.logger.info( - `attempting optimistic execution of tx ${transaction.hash} (tx.gas=${gasLimit} gasLeft=${gasLeft})`, - ); + let optimisticExecution = false; - await evm.snapshot(this.#commitKey); - } + const gasLimit = transaction.gasLimit; + if (gasLeft - gasLimit < 0) { + // Optimistically execute transaction even if the gas limit exceeds the remaining + // block space since there's possibly still space to fit the actual gas consumed. - const result = await validator.validate( - { commitKey: this.#commitKey, gasLimit: milestone.block.maxGasLimit, generatorAddress: this.#generatorAddress, timestamp: this.#timestamp }, - transaction, + // If the consumed gas exceeds the remaining block space, we ignore the transaction and + // calculate the root from the previous state (rollback). + optimisticExecution = true; + this.logger.info( + `attempting optimistic execution of tx ${transaction.hash} (tx.gas=${gasLimit} gasLeft=${gasLeft})`, ); - gasLeft -= Number(result.gasUsed); - - // Ignore transaction if it uses more than what's left. - if (gasLeft < 0) { - this.logger.warn( - `skipping tx ${transaction.hash} due to insufficient block space (tx.gasUsed=${Number(result.gasUsed)} gasLeft=${gasLeft} optimistic=${optimisticExecution})`, - ); + await this.#evm.snapshot(this.#commitKey); + } - if (optimisticExecution) { - await evm.rollback(this.#commitKey); - } + const result = await this.#validateTransaction(transaction); - break; - } + gasLeft -= Number(result.gasUsed); - gasUsed += Number(result.gasUsed); - fee += this.gasFeeCalculator.calculateConsumed(transaction.gasPrice, result.gasUsed); - candidateTransactions.push(transaction); - } catch (error) { + // Ignore transaction if it uses more than what's left. + if (gasLeft < 0) { this.logger.warn( - `tx ${transaction.hash} from ${transaction.from} failed to collate: ${error.message}`, + `skipping tx ${transaction.hash} due to insufficient block space (tx.gasUsed=${Number(result.gasUsed)} gasLeft=${gasLeft} optimistic=${optimisticExecution})`, ); - await this.txPoolWorker.removeTransaction(transaction.from, transaction.hash); + if (optimisticExecution) { + await this.#evm.rollback(this.#commitKey); + } - failedSenders.add(transaction.senderPublicKey); + break; } + + gasUsed += Number(result.gasUsed); + fee += this.gasFeeCalculator.calculateConsumed(transaction.gasPrice, result.gasUsed); + transactions.push(transaction); + } catch (error) { + await this.#handleFailedTransaction(transaction, error as Error); } + } - await evm.updateRewardsAndVotes({ - blockReward: BigInt(milestone.reward), + return { fee, gasUsed, transactions }; + } + + async #validateTransaction(transaction: Contracts.Crypto.Transaction): Promise { + return this.#validator.validate( + { commitKey: this.#commitKey, - specId: milestone.evmSpec, - timestamp: BigInt(this.#timestamp), - validatorAddress: this.#generatorAddress, - }); + gasLimit: this.#milestone.block.maxGasLimit, + generatorAddress: this.#generatorAddress, + timestamp: this.#timestamp, + }, + transaction, + ); + } - if (this.roundCalculator.isNewRound(previousBlock.number + 2)) { - const { roundValidators } = this.cryptoConfiguration.getMilestone(previousBlock.number + 2); + async #handleFailedTransaction(transaction: Contracts.Crypto.Transaction, error: Error): Promise { + this.logger.warn(`tx ${transaction.hash} from ${transaction.from} failed to collate: ${error.message}`); - await evm.calculateRoundValidators({ - commitKey: this.#commitKey, - roundValidators: BigInt(roundValidators), - specId: milestone.evmSpec, - timestamp: BigInt(this.#timestamp), - validatorAddress: this.#generatorAddress, - }); - } + await this.txPoolWorker.removeTransaction(transaction.from, transaction.hash); + this.#failedSenders.add(transaction.senderPublicKey); + } - return { - fee, - gasUsed, - logsBloom: await evm.logsBloom(this.#commitKey), - stateRoot: await evm.stateRoot(this.#commitKey, previousBlock.stateRoot), - transactions: candidateTransactions, - }; - } finally { - await evm.dispose(); + async #updateRewardsAndVotes(): Promise { + await this.#evm.updateRewardsAndVotes({ + blockReward: BigInt(this.#milestone.reward), + commitKey: this.#commitKey, + specId: this.#milestone.evmSpec, + timestamp: BigInt(this.#timestamp), + validatorAddress: this.#generatorAddress, + }); + } + + async #calculateRoundValidators(): Promise { + if (this.roundCalculator.isNewRound(this.#previousBlock.number + 2)) { + const { roundValidators } = this.cryptoConfiguration.getMilestone(this.#previousBlock.number + 2); + + await this.#evm.calculateRoundValidators({ + commitKey: this.#commitKey, + roundValidators: BigInt(roundValidators), + specId: this.#milestone.evmSpec, + timestamp: BigInt(this.#timestamp), + validatorAddress: this.#generatorAddress, + }); } } } From f13623789d667e83d0e3567c431e41d0763cec89 Mon Sep 17 00:00:00 2001 From: sebastijankuzner Date: Mon, 11 May 2026 17:08:40 +0000 Subject: [PATCH 13/26] Improve code --- packages/forger/source/transaction-forger.ts | 104 +++++++++++-------- 1 file changed, 59 insertions(+), 45 deletions(-) diff --git a/packages/forger/source/transaction-forger.ts b/packages/forger/source/transaction-forger.ts index 532eee279..5c12cbdfd 100644 --- a/packages/forger/source/transaction-forger.ts +++ b/packages/forger/source/transaction-forger.ts @@ -7,6 +7,13 @@ import { performance } from "perf_hooks"; import { TransactionIterable } from "./transaction-iterable.js"; +type ProcessTransactionResult = { + fee: bigint; + gasUsed: number; + transactions: Contracts.Crypto.Transaction[]; + gasLeft: number; +}; + @injectable() export class TransactionForger implements Contracts.Forger.TransactionForger { @inject(Identifiers.Application.Instance) @@ -51,7 +58,6 @@ export class TransactionForger implements Contracts.Forger.TransactionForger { #failedSenders: Set = new Set(); - public initialize( generatorAddress: string, timestamp: number, @@ -104,16 +110,13 @@ export class TransactionForger implements Contracts.Forger.TransactionForger { } } - async #processTransactions(): Promise<{ - fee: bigint; - gasUsed: number; - transactions: Contracts.Crypto.Transaction[]; - }> { - const transactions: Contracts.Crypto.Transaction[] = []; - - let gasLeft = this.#milestone.block.maxGasLimit; - let gasUsed = 0; - let fee = 0n; + async #processTransactions(): Promise { + const result: ProcessTransactionResult = { + fee: 0n, + gasLeft: this.#milestone.block.maxGasLimit, + gasUsed: 0, + transactions: [] as Contracts.Crypto.Transaction[], + }; const transactionIterable = this.app .resolve(TransactionIterable) @@ -128,54 +131,65 @@ export class TransactionForger implements Contracts.Forger.TransactionForger { continue; } - try { - if (gasLeft < 21000) { - break; - } + if (result.gasLeft < 21000) { + break; + } - let optimisticExecution = false; + const processNext = await this.#processTransaction(transaction, result); + if (!processNext) { + break; + } + } - const gasLimit = transaction.gasLimit; - if (gasLeft - gasLimit < 0) { - // Optimistically execute transaction even if the gas limit exceeds the remaining - // block space since there's possibly still space to fit the actual gas consumed. + return result; + } - // If the consumed gas exceeds the remaining block space, we ignore the transaction and - // calculate the root from the previous state (rollback). - optimisticExecution = true; - this.logger.info( - `attempting optimistic execution of tx ${transaction.hash} (tx.gas=${gasLimit} gasLeft=${gasLeft})`, - ); + async #processTransaction( + transaction: Contracts.Crypto.Transaction, + result: ProcessTransactionResult, + ): Promise { + try { + let optimisticExecution = false; - await this.#evm.snapshot(this.#commitKey); - } + if (result.gasLeft - transaction.gasLimit < 0) { + // Optimistically execute transaction even if the gas limit exceeds the remaining + // block space since there's possibly still space to fit the actual gas consumed. + + // If the consumed gas exceeds the remaining block space, we ignore the transaction and + // calculate the root from the previous state (rollback). + optimisticExecution = true; + this.logger.info( + `attempting optimistic execution of tx ${transaction.hash} (tx.gas=${transaction.gasLimit} gasLeft=${result.gasLeft})`, + ); - const result = await this.#validateTransaction(transaction); + await this.#evm.snapshot(this.#commitKey); + } - gasLeft -= Number(result.gasUsed); + const validation = await this.#validateTransaction(transaction); - // Ignore transaction if it uses more than what's left. - if (gasLeft < 0) { - this.logger.warn( - `skipping tx ${transaction.hash} due to insufficient block space (tx.gasUsed=${Number(result.gasUsed)} gasLeft=${gasLeft} optimistic=${optimisticExecution})`, - ); + result.gasLeft -= Number(validation.gasUsed); - if (optimisticExecution) { - await this.#evm.rollback(this.#commitKey); - } + // Ignore transaction if it uses more than what's left. + if (result.gasLeft < 0) { + this.logger.warn( + `skipping tx ${transaction.hash} due to insufficient block space (tx.gasUsed=${Number(validation.gasUsed)} gasLeft=${transaction.gasLimit} optimistic=${optimisticExecution})`, + ); - break; + if (optimisticExecution) { + await this.#evm.rollback(this.#commitKey); } - gasUsed += Number(result.gasUsed); - fee += this.gasFeeCalculator.calculateConsumed(transaction.gasPrice, result.gasUsed); - transactions.push(transaction); - } catch (error) { - await this.#handleFailedTransaction(transaction, error as Error); + return false; } + + result.gasUsed += Number(validation.gasUsed); + result.fee += this.gasFeeCalculator.calculateConsumed(transaction.gasPrice, validation.gasUsed); + result.transactions.push(transaction); + } catch (error) { + await this.#handleFailedTransaction(transaction, error as Error); } - return { fee, gasUsed, transactions }; + return true; } async #validateTransaction(transaction: Contracts.Crypto.Transaction): Promise { From 577b7617ecbf6d13574e790ecd5eeb41eefb609c Mon Sep 17 00:00:00 2001 From: sebastijankuzner Date: Mon, 11 May 2026 17:24:59 +0000 Subject: [PATCH 14/26] Improve code --- packages/forger/source/transaction-forger.ts | 22 +++++++------------- 1 file changed, 8 insertions(+), 14 deletions(-) diff --git a/packages/forger/source/transaction-forger.ts b/packages/forger/source/transaction-forger.ts index 5c12cbdfd..eeca27eef 100644 --- a/packages/forger/source/transaction-forger.ts +++ b/packages/forger/source/transaction-forger.ts @@ -135,10 +135,7 @@ export class TransactionForger implements Contracts.Forger.TransactionForger { break; } - const processNext = await this.#processTransaction(transaction, result); - if (!processNext) { - break; - } + await this.#processTransaction(transaction, result); } return result; @@ -147,17 +144,15 @@ export class TransactionForger implements Contracts.Forger.TransactionForger { async #processTransaction( transaction: Contracts.Crypto.Transaction, result: ProcessTransactionResult, - ): Promise { + ): Promise { try { - let optimisticExecution = false; - - if (result.gasLeft - transaction.gasLimit < 0) { + const optimisticExecution = result.gasLeft - transaction.gasLimit < 0; + if (optimisticExecution) { // Optimistically execute transaction even if the gas limit exceeds the remaining // block space since there's possibly still space to fit the actual gas consumed. // If the consumed gas exceeds the remaining block space, we ignore the transaction and // calculate the root from the previous state (rollback). - optimisticExecution = true; this.logger.info( `attempting optimistic execution of tx ${transaction.hash} (tx.gas=${transaction.gasLimit} gasLeft=${result.gasLeft})`, ); @@ -166,20 +161,21 @@ export class TransactionForger implements Contracts.Forger.TransactionForger { } const validation = await this.#validateTransaction(transaction); - + // Reduce gas left even for optimistic executions, to prevent further processing. result.gasLeft -= Number(validation.gasUsed); // Ignore transaction if it uses more than what's left. if (result.gasLeft < 0) { this.logger.warn( - `skipping tx ${transaction.hash} due to insufficient block space (tx.gasUsed=${Number(validation.gasUsed)} gasLeft=${transaction.gasLimit} optimistic=${optimisticExecution})`, + `Skipping tx ${transaction.hash} due to insufficient block space (tx.gasUsed=${Number(validation.gasUsed)} gasLeft=${transaction.gasLimit} optimistic=${optimisticExecution})`, ); if (optimisticExecution) { await this.#evm.rollback(this.#commitKey); } - return false; + // TODO: This looks wrong. Is transaction removed even if added optimistically. + return; } result.gasUsed += Number(validation.gasUsed); @@ -188,8 +184,6 @@ export class TransactionForger implements Contracts.Forger.TransactionForger { } catch (error) { await this.#handleFailedTransaction(transaction, error as Error); } - - return true; } async #validateTransaction(transaction: Contracts.Crypto.Transaction): Promise { From dddf459f074679eaaac1888c3ac18b32a719ffe1 Mon Sep 17 00:00:00 2001 From: sebastijankuzner Date: Mon, 11 May 2026 17:37:53 +0000 Subject: [PATCH 15/26] Handle gasLeft < 0 --- packages/forger/source/transaction-forger.ts | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/packages/forger/source/transaction-forger.ts b/packages/forger/source/transaction-forger.ts index eeca27eef..2a2a34766 100644 --- a/packages/forger/source/transaction-forger.ts +++ b/packages/forger/source/transaction-forger.ts @@ -164,7 +164,6 @@ export class TransactionForger implements Contracts.Forger.TransactionForger { // Reduce gas left even for optimistic executions, to prevent further processing. result.gasLeft -= Number(validation.gasUsed); - // Ignore transaction if it uses more than what's left. if (result.gasLeft < 0) { this.logger.warn( `Skipping tx ${transaction.hash} due to insufficient block space (tx.gasUsed=${Number(validation.gasUsed)} gasLeft=${transaction.gasLimit} optimistic=${optimisticExecution})`, @@ -172,10 +171,11 @@ export class TransactionForger implements Contracts.Forger.TransactionForger { if (optimisticExecution) { await this.#evm.rollback(this.#commitKey); + return; + } else { + // In practice, this should never happen since the validator should reject transactions that exceed the block gas limit, but we check just in case. + throw new Error(`Non-optimistic transaction processing requires more gas than remaining block space (tx.gasUsed=${Number(validation.gasUsed)} gasLeft=${transaction.gasLimit})`); } - - // TODO: This looks wrong. Is transaction removed even if added optimistically. - return; } result.gasUsed += Number(validation.gasUsed); From 29cb226bd6bd0da5d9e7972cba028f1888727c13 Mon Sep 17 00:00:00 2001 From: sebastijankuzner Date: Mon, 11 May 2026 17:40:01 +0000 Subject: [PATCH 16/26] Gas used as number --- packages/forger/source/transaction-forger.ts | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/packages/forger/source/transaction-forger.ts b/packages/forger/source/transaction-forger.ts index 2a2a34766..f0eddfec9 100644 --- a/packages/forger/source/transaction-forger.ts +++ b/packages/forger/source/transaction-forger.ts @@ -162,11 +162,12 @@ export class TransactionForger implements Contracts.Forger.TransactionForger { const validation = await this.#validateTransaction(transaction); // Reduce gas left even for optimistic executions, to prevent further processing. - result.gasLeft -= Number(validation.gasUsed); + const gasUsed = Number(validation.gasUsed); + result.gasLeft -= gasUsed; if (result.gasLeft < 0) { this.logger.warn( - `Skipping tx ${transaction.hash} due to insufficient block space (tx.gasUsed=${Number(validation.gasUsed)} gasLeft=${transaction.gasLimit} optimistic=${optimisticExecution})`, + `Skipping tx ${transaction.hash} due to insufficient block space (tx.gasUsed=${gasUsed} gasLeft=${transaction.gasLimit} optimistic=${optimisticExecution})`, ); if (optimisticExecution) { @@ -174,11 +175,11 @@ export class TransactionForger implements Contracts.Forger.TransactionForger { return; } else { // In practice, this should never happen since the validator should reject transactions that exceed the block gas limit, but we check just in case. - throw new Error(`Non-optimistic transaction processing requires more gas than remaining block space (tx.gasUsed=${Number(validation.gasUsed)} gasLeft=${transaction.gasLimit})`); + throw new Error(`Non-optimistic transaction processing requires more gas than remaining block space (tx.gasUsed=${gasUsed} gasLeft=${transaction.gasLimit})`); } } - result.gasUsed += Number(validation.gasUsed); + result.gasUsed += gasUsed; result.fee += this.gasFeeCalculator.calculateConsumed(transaction.gasPrice, validation.gasUsed); result.transactions.push(transaction); } catch (error) { From ce58319576eaa6fb2ca1aab617ec9433c3984838 Mon Sep 17 00:00:00 2001 From: sebastijankuzner Date: Mon, 11 May 2026 17:43:35 +0000 Subject: [PATCH 17/26] Remove identifier --- packages/constants/source/identifiers.ts | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/packages/constants/source/identifiers.ts b/packages/constants/source/identifiers.ts index a718add07..f275fcbe8 100644 --- a/packages/constants/source/identifiers.ts +++ b/packages/constants/source/identifiers.ts @@ -174,8 +174,7 @@ export const Identifiers = { }, }, Forger: { - Block: Symbol("Forger"), - Transaction: Symbol("Forger"), + Block: Symbol("Forger") }, P2P: { ApiNode: { From 8ae5da0069d9d1a584d0e0f99b6ec3a2f6af1b6f Mon Sep 17 00:00:00 2001 From: sebastijankuzner Date: Mon, 11 May 2026 17:47:06 +0000 Subject: [PATCH 18/26] Clear selector on commit --- packages/transaction-pool-worker/source/handlers/commit.ts | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/packages/transaction-pool-worker/source/handlers/commit.ts b/packages/transaction-pool-worker/source/handlers/commit.ts index bec4ad9cd..1985db569 100644 --- a/packages/transaction-pool-worker/source/handlers/commit.ts +++ b/packages/transaction-pool-worker/source/handlers/commit.ts @@ -14,6 +14,9 @@ export class CommitHandler { @inject(Identifiers.TransactionPool.Service) private readonly transactionPoolService!: Contracts.TransactionPool.Service; + @inject(Identifiers.TransactionPool.Selector) + private readonly selector!: Contracts.TransactionPool.Selector; + @inject(Identifiers.Services.Log.Service) protected readonly logger!: Contracts.Kernel.Logger; @@ -25,6 +28,7 @@ export class CommitHandler { ): Promise { try { this.stateStore.setBlockNumber(blockNumber); + this.selector.clear(); if (this.configuration.isNewMilestone()) { void this.transactionPoolService.reAddTransactions(); From 0dccc003b1f14fcc22cfc4d09e8e25b26e1450bd Mon Sep 17 00:00:00 2001 From: sebastijankuzner Date: Fri, 15 May 2026 11:51:45 +0000 Subject: [PATCH 19/26] Cleanup worker --- tests/functional/consensus/source/worker.ts | 31 ++------------------- 1 file changed, 2 insertions(+), 29 deletions(-) diff --git a/tests/functional/consensus/source/worker.ts b/tests/functional/consensus/source/worker.ts index d34815b2c..e28153470 100644 --- a/tests/functional/consensus/source/worker.ts +++ b/tests/functional/consensus/source/worker.ts @@ -1,12 +1,10 @@ +import type { Contracts } from "@mainsail/contracts"; + import { Identifiers } from "@mainsail/constants"; import { inject, injectable, tagged } from "@mainsail/container"; -import type { Contracts } from "@mainsail/contracts"; @injectable() export class Worker implements Contracts.Crypto.WorkerScriptHandler { - // @inject(Identifiers.Cryptography.Block.Factory) - // private readonly blockFactoryImp!: Contracts.Crypto.BlockFactory; - @inject(Identifiers.Cryptography.Transaction.Factory) private readonly transactionFactoryImp!: Contracts.Crypto.TransactionFactory; @@ -18,10 +16,6 @@ export class Worker implements Contracts.Crypto.WorkerScriptHandler { @tagged("type", "consensus") private readonly publicKeyFactoryImp!: Contracts.Crypto.PublicKeyFactory; - // @inject(Identifiers.Cryptography.Signature.Instance) - // @tagged("type", "wallet") - // private readonly walletSignatureImp!: Contracts.Crypto.Signature; - public async boot(flags: Contracts.Crypto.WorkerFlags): Promise { // } @@ -76,27 +70,6 @@ export class Worker implements Contracts.Crypto.WorkerScriptHandler { return this.#call(this.consensusSignatureImp, method, arguments_); } - // async #callWalletSignawture>( - // method: K, - // arguments_: Parameters, - // ): Promise> { - // return this.#call(this.walletSignatureImp, method, arguments_); - // } - - // async #callTransactionFactory>( - // method: K, - // arguments_: Parameters, - // ): Promise> { - // return this.#call(this.transactionFactoryImp, method, arguments_); - // } - - // async #callBlockFactory>( - // method: K, - // arguments_: Parameters, - // ): Promise> { - // return this.#call(this.blockFactoryImp, method, arguments_); - // } - async #callPublicKeyFactory>( method: K, arguments_: Parameters, From 02eb2d84bf15190f759f56c8ded32cf52510db3d Mon Sep 17 00:00:00 2001 From: sebastijankuzner Date: Fri, 15 May 2026 11:53:34 +0000 Subject: [PATCH 20/26] Fix consensus tests --- tests/functional/consensus/source/setup.ts | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/tests/functional/consensus/source/setup.ts b/tests/functional/consensus/source/setup.ts index a445668c6..924714648 100644 --- a/tests/functional/consensus/source/setup.ts +++ b/tests/functional/consensus/source/setup.ts @@ -1,12 +1,14 @@ -import { Identifiers } from "@mainsail/constants"; import type { Contracts } from "@mainsail/contracts"; + +import { Identifiers } from "@mainsail/constants"; import { Application, Bootstrap, Providers, Services } from "@mainsail/kernel"; import { join } from "path"; import { dirSync } from "tmp"; import type { ValidatorsJson } from "./contracts.js"; -import { TestLogger } from "./logger.js"; import type { P2PRegistry } from "./p2p.js"; + +import { TestLogger } from "./logger.js"; import { ProposerCalculator } from "./proposer-calculator.js"; import { Worker } from "./worker.js"; @@ -36,7 +38,7 @@ const setup = async (id: number, p2pRegistry: P2PRegistry, crypto: any, validato }); app.bind(Identifiers.TransactionPool.Worker).toConstantValue({ - getTransactionBytes: async () => [], + getTransactions: async () => ({ remaining: 0, transactions: [] }), onCommit: async () => { }, }); app.bind(Identifiers.Evm.Worker).toConstantValue({ From 20aa1b0990e92c3c73f4c8aa5e10eb56649a70f8 Mon Sep 17 00:00:00 2001 From: sebastijankuzner Date: Fri, 15 May 2026 11:59:06 +0000 Subject: [PATCH 21/26] Fix workers --- tests/functional/resync/source/pool-worker.ts | 16 +++++++++------- .../transaction-pool-api/source/pool-worker.ts | 16 +++++++++------- 2 files changed, 18 insertions(+), 14 deletions(-) diff --git a/tests/functional/resync/source/pool-worker.ts b/tests/functional/resync/source/pool-worker.ts index 2a084e46a..f4fd1c539 100644 --- a/tests/functional/resync/source/pool-worker.ts +++ b/tests/functional/resync/source/pool-worker.ts @@ -1,12 +1,12 @@ +import type { Contracts } from "@mainsail/contracts"; + import { Identifiers } from "@mainsail/constants"; import { inject, injectable } from "@mainsail/container"; -import type { Contracts } from "@mainsail/contracts"; -import { GetTransactionsHandler } from "@mainsail/transaction-pool-worker/distribution/handlers/index.js"; @injectable() export class PoolWorker implements Contracts.TransactionPool.Worker { - @inject(Identifiers.Application.Instance) - private readonly app!: Contracts.Kernel.Application; + @inject(Identifiers.TransactionPool.Query) + private readonly poolQuery!: Contracts.TransactionPool.Query; @inject(Identifiers.TransactionPool.Mempool) private readonly transactionPoolMempool!: Contracts.TransactionPool.Mempool; @@ -33,9 +33,11 @@ export class PoolWorker implements Contracts.TransactionPool.Worker { await this.transactionPoolMempool.reAddTransactions([...sendersAddresses.keys()]); } - public async getTransactionBytes(): Promise { - const response: string[] = await this.app.resolve(GetTransactionsHandler).handle(); - return response.map((transaction: string) => Buffer.from(transaction, "hex")); + public async getTransactions(options: Contracts.TransactionPool.GetBatchOptions): Promise { + return { + remaining: 0, + transactions: (await this.poolQuery.getFromHighestPriority().all()).map((transaction) => transaction.toData()), + } } public async removeTransaction(address: string, hash: string): Promise { diff --git a/tests/functional/transaction-pool-api/source/pool-worker.ts b/tests/functional/transaction-pool-api/source/pool-worker.ts index 143910eba..16f015417 100644 --- a/tests/functional/transaction-pool-api/source/pool-worker.ts +++ b/tests/functional/transaction-pool-api/source/pool-worker.ts @@ -1,12 +1,12 @@ +import type { Contracts } from "@mainsail/contracts"; + import { Identifiers } from "@mainsail/constants"; import { inject, injectable } from "@mainsail/container"; -import type { Contracts } from "@mainsail/contracts"; -import { GetTransactionsHandler } from "@mainsail/transaction-pool-worker/distribution/handlers/index.js"; @injectable() export class PoolWorker implements Contracts.TransactionPool.Worker { - @inject(Identifiers.Application.Instance) - private readonly app!: Contracts.Kernel.Application; + @inject(Identifiers.TransactionPool.Query) + private readonly poolQuery!: Contracts.TransactionPool.Query; @inject(Identifiers.TransactionPool.Mempool) private readonly transactionPoolMempool!: Contracts.TransactionPool.Mempool; @@ -33,9 +33,11 @@ export class PoolWorker implements Contracts.TransactionPool.Worker { await this.transactionPoolMempool.reAddTransactions([...sendersAddresses.keys()]); } - public async getTransactionBytes(): Promise { - const response: string[] = await this.app.resolve(GetTransactionsHandler).handle(); - return response.map((transaction: string) => Buffer.from(transaction, "hex")); + public async getTransactions(options: Contracts.TransactionPool.GetBatchOptions): Promise { + return { + remaining: 0, + transactions: (await this.poolQuery.getFromHighestPriority().all()).map((transaction) => transaction.toData()), + } } public async removeTransaction(address: string, hash: string): Promise { From 1b007708791c710e6af2381913bda48b666dd075 Mon Sep 17 00:00:00 2001 From: sebastijankuzner Date: Fri, 15 May 2026 13:43:03 +0000 Subject: [PATCH 22/26] Cleanup worker --- .../transaction-pool-api/source/worker.ts | 46 ++----------------- 1 file changed, 3 insertions(+), 43 deletions(-) diff --git a/tests/functional/transaction-pool-api/source/worker.ts b/tests/functional/transaction-pool-api/source/worker.ts index 174447d4f..7914451cf 100644 --- a/tests/functional/transaction-pool-api/source/worker.ts +++ b/tests/functional/transaction-pool-api/source/worker.ts @@ -1,12 +1,10 @@ -import { inject, injectable, tagged } from "@mainsail/container"; import type { Contracts } from "@mainsail/contracts"; + import { Identifiers } from "@mainsail/constants"; +import { inject, injectable, tagged } from "@mainsail/container"; @injectable() export class Worker implements Contracts.Crypto.WorkerScriptHandler { - // @inject(Identifiers.Cryptography.Block.Factory) - // private readonly blockFactoryImp!: Contracts.Crypto.BlockFactory; - @inject(Identifiers.Cryptography.Transaction.Factory) private readonly transactionFactoryImp!: Contracts.Crypto.TransactionFactory; @@ -14,17 +12,7 @@ export class Worker implements Contracts.Crypto.WorkerScriptHandler { @tagged("type", "consensus") private readonly consensusSignatureImp!: Contracts.Crypto.SignatureBls; - // @inject(Identifiers.Cryptography.Identity.PublicKey.Factory) - // @tagged("type", "consensus") - // private readonly publicKeyFactoryImp!: Contracts.Crypto.PublicKeyFactory; - - // @inject(Identifiers.Cryptography.Signature.Instance) - // @tagged("type", "wallet") - // private readonly walletSignatureImp!: Contracts.Crypto.Signature; - - public async boot(flags: Contracts.Crypto.WorkerFlags): Promise { - // - } + public async boot(flags: Contracts.Crypto.WorkerFlags): Promise {} public async consensusSignature>( method: K, @@ -76,34 +64,6 @@ export class Worker implements Contracts.Crypto.WorkerScriptHandler { return this.#call(this.consensusSignatureImp, method, arguments_); } - // async #callWalletSignawture>( - // method: K, - // arguments_: Parameters, - // ): Promise> { - // return this.#call(this.walletSignatureImp, method, arguments_); - // } - - // async #callTransactionFactory>( - // method: K, - // arguments_: Parameters, - // ): Promise> { - // return this.#call(this.transactionFactoryImp, method, arguments_); - // } - - // async #callBlockFactory>( - // method: K, - // arguments_: Parameters, - // ): Promise> { - // return this.#call(this.blockFactoryImp, method, arguments_); - // } - - // async #callPublicKeyFactory>( - // method: K, - // arguments_: Parameters, - // ): Promise> { - // return this.#call(this.publicKeyFactoryImp, method, arguments_); - // } - async #call any }, K extends Contracts.Kernel.IPC.Requests>( object: T, method: K, From 4e2c13b27c247d70360f17ff8fdd4ed25fd4e5f5 Mon Sep 17 00:00:00 2001 From: sebastijankuzner Date: Fri, 15 May 2026 13:48:09 +0000 Subject: [PATCH 23/26] Fix pool workers --- tests/functional/resync/source/pool-worker.ts | 5 ++++- tests/functional/resync/source/worker.ts | 3 ++- tests/functional/transaction-pool-api/source/pool-worker.ts | 5 ++++- 3 files changed, 10 insertions(+), 3 deletions(-) diff --git a/tests/functional/resync/source/pool-worker.ts b/tests/functional/resync/source/pool-worker.ts index f4fd1c539..5301982ab 100644 --- a/tests/functional/resync/source/pool-worker.ts +++ b/tests/functional/resync/source/pool-worker.ts @@ -34,10 +34,13 @@ export class PoolWorker implements Contracts.TransactionPool.Worker { } public async getTransactions(options: Contracts.TransactionPool.GetBatchOptions): Promise { - return { + const result = { remaining: 0, transactions: (await this.poolQuery.getFromHighestPriority().all()).map((transaction) => transaction.toData()), } + + this.transactionPoolMempool.flush(); + return result; } public async removeTransaction(address: string, hash: string): Promise { diff --git a/tests/functional/resync/source/worker.ts b/tests/functional/resync/source/worker.ts index d35d67a9e..06713b2ac 100644 --- a/tests/functional/resync/source/worker.ts +++ b/tests/functional/resync/source/worker.ts @@ -1,6 +1,7 @@ +import type { Contracts } from "@mainsail/contracts"; + import { Identifiers } from "@mainsail/constants"; import { inject, injectable, tagged } from "@mainsail/container"; -import type { Contracts } from "@mainsail/contracts"; @injectable() export class Worker implements Contracts.Crypto.WorkerScriptHandler { diff --git a/tests/functional/transaction-pool-api/source/pool-worker.ts b/tests/functional/transaction-pool-api/source/pool-worker.ts index 16f015417..9e03368f4 100644 --- a/tests/functional/transaction-pool-api/source/pool-worker.ts +++ b/tests/functional/transaction-pool-api/source/pool-worker.ts @@ -34,10 +34,13 @@ export class PoolWorker implements Contracts.TransactionPool.Worker { } public async getTransactions(options: Contracts.TransactionPool.GetBatchOptions): Promise { - return { + const result = { remaining: 0, transactions: (await this.poolQuery.getFromHighestPriority().all()).map((transaction) => transaction.toData()), } + + this.transactionPoolMempool.flush(); + return result; } public async removeTransaction(address: string, hash: string): Promise { From 298b715e749a176f6b55f1dddd2a149b3d0cb075 Mon Sep 17 00:00:00 2001 From: sebastijankuzner <58827427+sebastijankuzner@users.noreply.github.com> Date: Fri, 15 May 2026 13:50:45 +0000 Subject: [PATCH 24/26] style: resolve style guide violations [ci-lint-fix] --- packages/constants/source/identifiers.ts | 2 +- packages/forger/source/transaction-forger.ts | 4 +++- packages/forger/source/transaction-iterable.ts | 5 ++--- packages/transaction-pool-service/source/selector.ts | 6 ++++-- .../source/handlers/get-transactions.ts | 4 +++- packages/transaction-pool-worker/source/worker-handler.ts | 4 +++- packages/transaction-pool-worker/source/worker.ts | 4 +++- 7 files changed, 19 insertions(+), 10 deletions(-) diff --git a/packages/constants/source/identifiers.ts b/packages/constants/source/identifiers.ts index f275fcbe8..be87f2ae7 100644 --- a/packages/constants/source/identifiers.ts +++ b/packages/constants/source/identifiers.ts @@ -174,7 +174,7 @@ export const Identifiers = { }, }, Forger: { - Block: Symbol("Forger") + Block: Symbol("Forger"), }, P2P: { ApiNode: { diff --git a/packages/forger/source/transaction-forger.ts b/packages/forger/source/transaction-forger.ts index f0eddfec9..f138c3931 100644 --- a/packages/forger/source/transaction-forger.ts +++ b/packages/forger/source/transaction-forger.ts @@ -175,7 +175,9 @@ export class TransactionForger implements Contracts.Forger.TransactionForger { return; } else { // In practice, this should never happen since the validator should reject transactions that exceed the block gas limit, but we check just in case. - throw new Error(`Non-optimistic transaction processing requires more gas than remaining block space (tx.gasUsed=${gasUsed} gasLeft=${transaction.gasLimit})`); + throw new Error( + `Non-optimistic transaction processing requires more gas than remaining block space (tx.gasUsed=${gasUsed} gasLeft=${transaction.gasLimit})`, + ); } } diff --git a/packages/forger/source/transaction-iterable.ts b/packages/forger/source/transaction-iterable.ts index ecac94e26..248b33564 100644 --- a/packages/forger/source/transaction-iterable.ts +++ b/packages/forger/source/transaction-iterable.ts @@ -3,7 +3,6 @@ import type { Contracts } from "@mainsail/contracts"; import { Identifiers } from "@mainsail/constants"; import { inject, injectable } from "@mainsail/container"; - @injectable() export class TransactionIterable implements AsyncIterable { @inject(Identifiers.TransactionPool.Worker) @@ -16,7 +15,7 @@ export class TransactionIterable implements AsyncIterable { @@ -32,7 +31,7 @@ export class TransactionIterable implements AsyncIterable { + public async getBatch( + options: Contracts.TransactionPool.GetBatchOptions, + ): Promise { await this.#prepare(options.blockRound); const transactions: Contracts.Crypto.TransactionData[] = []; @@ -55,6 +57,6 @@ export class Selector implements Contracts.TransactionPool.Selector { this.#currentBlockRound = blockRound; this.#index = 0; - this.#transactions = await this.poolQuery.getFromHighestPriority().all() + this.#transactions = await this.poolQuery.getFromHighestPriority().all(); } } diff --git a/packages/transaction-pool-worker/source/handlers/get-transactions.ts b/packages/transaction-pool-worker/source/handlers/get-transactions.ts index dc7b8c009..9fded8fae 100644 --- a/packages/transaction-pool-worker/source/handlers/get-transactions.ts +++ b/packages/transaction-pool-worker/source/handlers/get-transactions.ts @@ -8,7 +8,9 @@ export class GetTransactionsHandler { @inject(Identifiers.TransactionPool.Selector) private readonly selector!: Contracts.TransactionPool.Selector; - public async handle(options: Contracts.TransactionPool.GetBatchOptions): Promise { + public async handle( + options: Contracts.TransactionPool.GetBatchOptions, + ): Promise { return this.selector.getBatch(options); } } diff --git a/packages/transaction-pool-worker/source/worker-handler.ts b/packages/transaction-pool-worker/source/worker-handler.ts index 89667dc32..8ddaeb987 100644 --- a/packages/transaction-pool-worker/source/worker-handler.ts +++ b/packages/transaction-pool-worker/source/worker-handler.ts @@ -39,7 +39,9 @@ export class WorkerScriptHandler implements Contracts.TransactionPool.WorkerScri await this.#app.resolve(CommitHandler).handle(height, sendersAddresses, consumedGas, isSyncing); } - public async getTransactions(options: Contracts.TransactionPool.GetBatchOptions): Promise { + public async getTransactions( + options: Contracts.TransactionPool.GetBatchOptions, + ): Promise { return await this.#app.resolve(GetTransactionsHandler).handle(options); } diff --git a/packages/transaction-pool-worker/source/worker.ts b/packages/transaction-pool-worker/source/worker.ts index 0211fc1ee..a7da9e1a7 100644 --- a/packages/transaction-pool-worker/source/worker.ts +++ b/packages/transaction-pool-worker/source/worker.ts @@ -79,7 +79,9 @@ export class Worker implements Contracts.TransactionPool.Worker { await this.ipcSubprocess.sendRequest("start", blockNumber); } - public async getTransactions(options: Contracts.TransactionPool.GetBatchOptions): Promise { + public async getTransactions( + options: Contracts.TransactionPool.GetBatchOptions, + ): Promise { return this.ipcSubprocess.sendRequest("getTransactions", options); } From 7ad3dbbce4464ed4322a2d4da0f767bd183f03c9 Mon Sep 17 00:00:00 2001 From: sebastijankuzner Date: Fri, 15 May 2026 17:01:15 +0000 Subject: [PATCH 25/26] Fix validator unit tests --- packages/validator/test/helpers/prepare-sandbox.ts | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/packages/validator/test/helpers/prepare-sandbox.ts b/packages/validator/test/helpers/prepare-sandbox.ts index cd79620d6..031df7f8e 100644 --- a/packages/validator/test/helpers/prepare-sandbox.ts +++ b/packages/validator/test/helpers/prepare-sandbox.ts @@ -1,6 +1,7 @@ +import type { Contracts } from "@mainsail/contracts"; + import { ServiceProvider as BlockchainUtilities } from "@mainsail/blockchain-utils"; import { Identifiers } from "@mainsail/constants"; -import type { Contracts } from "@mainsail/contracts"; import { ServiceProvider as CoreCryptoAddressBase58 } from "@mainsail/crypto-address-base58"; import { ServiceProvider as CoreCryptoAddressKeccak256 } from "@mainsail/crypto-address-keccak256"; import { ServiceProvider as CoreCryptoBlock } from "@mainsail/crypto-block"; @@ -16,10 +17,10 @@ import { ServiceProvider as CoreCryptoTransaction } from "@mainsail/crypto-trans import { ServiceProvider as CoreCryptoValidation } from "@mainsail/crypto-validation"; import { ServiceProvider as CoreCryptoWif } from "@mainsail/crypto-wif"; import { Identifiers as EvmConsensusIdentifiers } from "@mainsail/evm-consensus"; +import { ServiceProvider as Forger } from "@mainsail/forger"; import { Application } from "@mainsail/kernel"; import { ServiceProvider as CoreSerializer } from "@mainsail/serializer"; import { ServiceProvider as CoreTransactions } from "@mainsail/transactions"; -import { ServiceProvider as Forger } from "@mainsail/forger"; import { ServiceProvider as CoreValidation } from "@mainsail/validation"; import crypto from "../../../core/bin/config/devnet/core/crypto.json" with { type: "json" }; @@ -62,7 +63,6 @@ export const prepareSandbox = async (context: { app?: Application }): Promise ({ - // @ts-ignore consensusSignature: (method, message, privateKey) => context .app!.getTagged(Identifiers.Cryptography.Signature.Instance, "type", "consensus")! @@ -72,7 +72,10 @@ export const prepareSandbox = async (context: { app?: Application }): Promise [], + getTransactions: async () => ({ + remaining: 0, + transactions: [], + }), }); const validator = { From 477f6d4164653339930fc86504ca362b9e816bde Mon Sep 17 00:00:00 2001 From: sebastijankuzner Date: Fri, 15 May 2026 17:13:21 +0000 Subject: [PATCH 26/26] Fix functional tests --- tests/functional/resync/source/pool-worker.ts | 10 +--------- .../transaction-pool-api/source/evm-call.test.ts | 1 + .../transaction-pool-api/source/pool-worker.ts | 9 +-------- 3 files changed, 3 insertions(+), 17 deletions(-) diff --git a/tests/functional/resync/source/pool-worker.ts b/tests/functional/resync/source/pool-worker.ts index 5301982ab..0ae491532 100644 --- a/tests/functional/resync/source/pool-worker.ts +++ b/tests/functional/resync/source/pool-worker.ts @@ -23,15 +23,7 @@ export class PoolWorker implements Contracts.TransactionPool.Worker { public getQueueSize(): number { return 0; } - async onCommit(unit: Contracts.Processor.ProcessableUnit): Promise { - const sendersAddresses: Set = new Set(); - - for (const transaction of unit.getBlock().transactions) { - sendersAddresses.add(transaction.from); - } - - await this.transactionPoolMempool.reAddTransactions([...sendersAddresses.keys()]); - } + async onCommit(unit: Contracts.Processor.ProcessableUnit): Promise {} public async getTransactions(options: Contracts.TransactionPool.GetBatchOptions): Promise { const result = { diff --git a/tests/functional/transaction-pool-api/source/evm-call.test.ts b/tests/functional/transaction-pool-api/source/evm-call.test.ts index 88fc26c29..cc001623b 100644 --- a/tests/functional/transaction-pool-api/source/evm-call.test.ts +++ b/tests/functional/transaction-pool-api/source/evm-call.test.ts @@ -422,6 +422,7 @@ describe<{ assert.equal(accept, [0]); assert.undefined(errors); + await waitBlock(context); await waitBlock(context); for (let i = 0; i < 10; i++) { diff --git a/tests/functional/transaction-pool-api/source/pool-worker.ts b/tests/functional/transaction-pool-api/source/pool-worker.ts index 9e03368f4..4f7c2aa5d 100644 --- a/tests/functional/transaction-pool-api/source/pool-worker.ts +++ b/tests/functional/transaction-pool-api/source/pool-worker.ts @@ -23,15 +23,8 @@ export class PoolWorker implements Contracts.TransactionPool.Worker { public getQueueSize(): number { return 0; } - async onCommit(unit: Contracts.Processor.ProcessableUnit): Promise { - const sendersAddresses: Set = new Set(); - for (const transaction of unit.getBlock().transactions) { - sendersAddresses.add(transaction.from); - } - - await this.transactionPoolMempool.reAddTransactions([...sendersAddresses.keys()]); - } + async onCommit(unit: Contracts.Processor.ProcessableUnit): Promise {} public async getTransactions(options: Contracts.TransactionPool.GetBatchOptions): Promise { const result = {