From 48f3f01ab2e410a539153b3849a2ef396d094c7d Mon Sep 17 00:00:00 2001 From: sebastijankuzner Date: Mon, 25 May 2026 11:45:30 +0000 Subject: [PATCH 01/17] Fix processor tests --- .../source/processor.test.ts | 14 -------------- 1 file changed, 14 deletions(-) diff --git a/packages/transaction-pool-service/source/processor.test.ts b/packages/transaction-pool-service/source/processor.test.ts index 8c88c3041..3400b1b2a 100644 --- a/packages/transaction-pool-service/source/processor.test.ts +++ b/packages/transaction-pool-service/source/processor.test.ts @@ -1,9 +1,7 @@ import { Container } from "@mainsail/container"; import { Identifiers } from "@mainsail/constants"; import * as Exceptions from "@mainsail/exceptions"; -import { Configuration } from "@mainsail/crypto-config"; -import crypto from "../../core/bin/config/devnet/core/crypto.json"; import { describe } from "@mainsail/test-runner"; import { Processor } from "./processor"; @@ -15,7 +13,6 @@ describe<{ transaction1: any; transaction2: any; factory: any; - blockSerializer: any; }>("Processor", ({ it, assert, beforeAll, stub, spy }) => { beforeAll((context) => { context.pool = { @@ -24,32 +21,21 @@ describe<{ context.extensions = [{ throwIfCannotBroadcast: () => {} }, { throwIfCannotBroadcast: () => {} }]; - context.broadcaster = { - broadcastTransactions: () => Promise.resolve(), - }; - context.factory = { fromBytes: (bytes) => ({}), fromJson: (tx) => tx, }; - context.blockSerializer = { - headerSize: () => 152, - }; context.broadcaster = { broadcastTransactions: async () => {}, }; context.container = new Container(); - context.container.bind(Identifiers.Cryptography.Block.Serializer).toConstantValue(context.blockSerializer); - context.container.bind(Identifiers.Cryptography.Configuration).to(Configuration).inSingletonScope(); - context.container.get(Identifiers.Cryptography.Configuration).setConfig(crypto); context.container.bind(Identifiers.TransactionPool.ProcessorExtension).toConstantValue(context.extensions[0]); context.container.bind(Identifiers.TransactionPool.ProcessorExtension).toConstantValue(context.extensions[1]); context.container.bind(Identifiers.TransactionPool.Service).toConstantValue(context.pool); context.container.bind(Identifiers.Cryptography.Transaction.Factory).toConstantValue(context.factory); - context.container.bind(Identifiers.Cryptography.Transaction.Deserializer).toConstantValue({}); context.container.bind(Identifiers.TransactionPool.Broadcaster).toConstantValue(context.broadcaster); context.container.bind(Identifiers.Services.Log.Service).toConstantValue({ error: () => {}, From 35681a118f77bef29be250768a143eb3917ed417 Mon Sep 17 00:00:00 2001 From: sebastijankuzner Date: Mon, 25 May 2026 11:51:53 +0000 Subject: [PATCH 02/17] Remove actions --- .../source/actions/index.ts | 2 -- .../actions/throw-if-cannot-be-applied.ts | 23 ------------------- .../source/actions/verify-transaction.ts | 19 --------------- .../transaction-pool-service/source/index.ts | 1 - .../source/sender-state.ts | 18 ++------------- .../source/service-provider.ts | 13 +---------- 6 files changed, 3 insertions(+), 73 deletions(-) delete mode 100644 packages/transaction-pool-service/source/actions/index.ts delete mode 100644 packages/transaction-pool-service/source/actions/throw-if-cannot-be-applied.ts delete mode 100644 packages/transaction-pool-service/source/actions/verify-transaction.ts diff --git a/packages/transaction-pool-service/source/actions/index.ts b/packages/transaction-pool-service/source/actions/index.ts deleted file mode 100644 index 438af59fb..000000000 --- a/packages/transaction-pool-service/source/actions/index.ts +++ /dev/null @@ -1,2 +0,0 @@ -export * from "./throw-if-cannot-be-applied.js"; -export * from "./verify-transaction.js"; diff --git a/packages/transaction-pool-service/source/actions/throw-if-cannot-be-applied.ts b/packages/transaction-pool-service/source/actions/throw-if-cannot-be-applied.ts deleted file mode 100644 index 8945cffbb..000000000 --- a/packages/transaction-pool-service/source/actions/throw-if-cannot-be-applied.ts +++ /dev/null @@ -1,23 +0,0 @@ -import type { Contracts } from "@mainsail/contracts"; - -import { injectable } from "@mainsail/container"; -import { Services } from "@mainsail/kernel"; - -@injectable() -export class ThrowIfCannotBeAppliedAction extends Services.Triggers.Action { - public async execute( - arguments_: Contracts.Kernel.ActionArguments<{ - handler: Contracts.Transactions.TransactionHandler; - transaction: Contracts.Crypto.Transaction; - sender: Contracts.State.Wallet; - evm: Contracts.Evm.Instance; - }>, - ): Promise { - const handler = arguments_.handler; - const transaction = arguments_.transaction; - const sender = arguments_.sender; - const evm = arguments_.evm; - - await handler.throwIfCannotBeApplied(transaction, sender, evm); - } -} diff --git a/packages/transaction-pool-service/source/actions/verify-transaction.ts b/packages/transaction-pool-service/source/actions/verify-transaction.ts deleted file mode 100644 index f01731bc7..000000000 --- a/packages/transaction-pool-service/source/actions/verify-transaction.ts +++ /dev/null @@ -1,19 +0,0 @@ -import type { Contracts } from "@mainsail/contracts"; - -import { injectable } from "@mainsail/container"; -import { Services } from "@mainsail/kernel"; - -@injectable() -export class VerifyTransactionAction extends Services.Triggers.Action { - public async execute( - arguments_: Contracts.Kernel.ActionArguments<{ - handler: Contracts.Transactions.TransactionHandler; - transaction: Contracts.Crypto.Transaction; - }>, - ): Promise { - const handler = arguments_.handler; - const transaction = arguments_.transaction; - - return handler.verify(transaction); - } -} diff --git a/packages/transaction-pool-service/source/index.ts b/packages/transaction-pool-service/source/index.ts index 693659e4f..32838a909 100644 --- a/packages/transaction-pool-service/source/index.ts +++ b/packages/transaction-pool-service/source/index.ts @@ -1,4 +1,3 @@ -export * from "./actions/index.js"; export * from "./mempool.js"; export * from "./processor.js"; export * from "./query.js"; diff --git a/packages/transaction-pool-service/source/sender-state.ts b/packages/transaction-pool-service/source/sender-state.ts index 0cc18aa87..645508093 100644 --- a/packages/transaction-pool-service/source/sender-state.ts +++ b/packages/transaction-pool-service/source/sender-state.ts @@ -10,7 +10,6 @@ import { TransactionFromWrongNetworkError, UnexpectedNonceError, } from "@mainsail/exceptions"; -import { Services } from "@mainsail/kernel"; import { Wallets } from "@mainsail/state"; @injectable() @@ -32,9 +31,6 @@ export class SenderState implements Contracts.TransactionPool.SenderState { @inject(Identifiers.Transaction.Handler) private readonly transactionHandler!: Contracts.Transactions.TransactionHandler; - @inject(Identifiers.Services.Trigger.Service) - private readonly triggers!: Services.Triggers.Triggers; - @inject(Identifiers.BlockchainUtils.FeeCalculator) private readonly feeCalculator!: Contracts.BlockchainUtils.FeeCalculator; @@ -118,19 +114,9 @@ export class SenderState implements Contracts.TransactionPool.SenderState { throw new InsufficientBalanceError(); } - if ( - await this.triggers.call("verifyTransaction", { - handler: this.transactionHandler, - transaction, - }) - ) { + if (await this.transactionHandler.verify(transaction)) { try { - await this.triggers.call("throwIfCannotBeApplied", { - evm: this.evm, - handler: this.transactionHandler, - sender: this.#wallet, - transaction, - }); + await this.transactionHandler.throwIfCannotBeApplied(transaction, this.#wallet, this.evm); } catch (error) { throw new TransactionFailedToApplyError(transaction, error); } diff --git a/packages/transaction-pool-service/source/service-provider.ts b/packages/transaction-pool-service/source/service-provider.ts index d96420704..f3431bcbb 100644 --- a/packages/transaction-pool-service/source/service-provider.ts +++ b/packages/transaction-pool-service/source/service-provider.ts @@ -2,10 +2,9 @@ import type { Contracts } from "@mainsail/contracts"; import { Identifiers } from "@mainsail/constants"; import { injectable } from "@mainsail/container"; -import { Providers, Services } from "@mainsail/kernel"; +import { Providers } from "@mainsail/kernel"; import Joi from "joi"; -import { ThrowIfCannotBeAppliedAction, VerifyTransactionAction } from "./actions/index.js"; import { Mempool } from "./mempool.js"; import { Processor } from "./processor.js"; import { Query } from "./query.js"; @@ -19,7 +18,6 @@ import { Storage } from "./storage.js"; export class ServiceProvider extends Providers.ServiceProvider { public async register(): Promise { this.#registerServices(); - this.#registerActions(); } public async boot(): Promise { @@ -68,13 +66,4 @@ export class ServiceProvider extends Providers.ServiceProvider { this.app.bind(Identifiers.TransactionPool.Selector).to(Selector).inSingletonScope(); } - #registerActions(): void { - this.app - .get(Identifiers.Services.Trigger.Service) - .bind("verifyTransaction", this.app.resolve(VerifyTransactionAction)); - - this.app - .get(Identifiers.Services.Trigger.Service) - .bind("throwIfCannotBeApplied", this.app.resolve(ThrowIfCannotBeAppliedAction)); - } } From 065e984766e63dd189e9c0f662f5f2d52f96ba29 Mon Sep 17 00:00:00 2001 From: sebastijankuzner Date: Mon, 25 May 2026 11:54:19 +0000 Subject: [PATCH 03/17] Move handler to package root --- packages/transactions/source/handlers/index.ts | 2 -- packages/transactions/source/index.ts | 1 - packages/transactions/source/service-provider.ts | 2 +- packages/transactions/source/{handlers => }/transaction.ts | 0 4 files changed, 1 insertion(+), 4 deletions(-) delete mode 100644 packages/transactions/source/handlers/index.ts rename packages/transactions/source/{handlers => }/transaction.ts (100%) diff --git a/packages/transactions/source/handlers/index.ts b/packages/transactions/source/handlers/index.ts deleted file mode 100644 index 53523708c..000000000 --- a/packages/transactions/source/handlers/index.ts +++ /dev/null @@ -1,2 +0,0 @@ -export type { TransactionHandlerConstructor } from "./transaction.js"; -export { TransactionHandler } from "./transaction.js"; diff --git a/packages/transactions/source/index.ts b/packages/transactions/source/index.ts index 3e28948d6..793704e5d 100644 --- a/packages/transactions/source/index.ts +++ b/packages/transactions/source/index.ts @@ -1,2 +1 @@ -export * as Handlers from "./handlers/index.js"; export * from "./service-provider.js"; diff --git a/packages/transactions/source/service-provider.ts b/packages/transactions/source/service-provider.ts index 4d540e331..ba7ca5cce 100644 --- a/packages/transactions/source/service-provider.ts +++ b/packages/transactions/source/service-provider.ts @@ -4,8 +4,8 @@ import { Identifiers } from "@mainsail/constants"; import { injectable } from "@mainsail/container"; import { Providers } from "@mainsail/kernel"; -import { TransactionHandler } from "./handlers/index.js"; import { TransactionValidator } from "./transaction-validator.js"; +import { TransactionHandler } from "./transaction.js"; @injectable() export class ServiceProvider extends Providers.ServiceProvider { diff --git a/packages/transactions/source/handlers/transaction.ts b/packages/transactions/source/transaction.ts similarity index 100% rename from packages/transactions/source/handlers/transaction.ts rename to packages/transactions/source/transaction.ts From d4c7af62a6c85a4f8f81c29ccd0108f2320e2ba7 Mon Sep 17 00:00:00 2001 From: sebastijankuzner Date: Mon, 25 May 2026 12:29:49 +0000 Subject: [PATCH 04/17] Remove emit --- packages/transactions/source/transaction.ts | 22 +-------------------- 1 file changed, 1 insertion(+), 21 deletions(-) diff --git a/packages/transactions/source/transaction.ts b/packages/transactions/source/transaction.ts index 57e880e27..0321ab0b8 100644 --- a/packages/transactions/source/transaction.ts +++ b/packages/transactions/source/transaction.ts @@ -1,6 +1,6 @@ import type { Contracts } from "@mainsail/contracts"; -import { Events, Identifiers } from "@mainsail/constants"; +import { Identifiers } from "@mainsail/constants"; import { inject, injectable } from "@mainsail/container"; import { TransactionFailedToPreverifyError, UnexpectedLegacySecondSignatureError } from "@mainsail/exceptions"; import { assert } from "@mainsail/utils"; @@ -22,12 +22,6 @@ export class TransactionHandler implements Contracts.Transactions.TransactionHan @inject(Identifiers.BlockchainUtils.FeeCalculator) protected readonly feeCalculator!: Contracts.BlockchainUtils.FeeCalculator; - @inject(Identifiers.Services.EventDispatcher.Service) - private readonly events!: Contracts.Kernel.EventDispatcher; - - @inject(Identifiers.State.State) - private readonly state!: Contracts.State.State; - public async verify(transaction: Contracts.Crypto.Transaction): Promise { assert.string(transaction.from); return this.verifier.verifyHash(transaction); @@ -94,25 +88,11 @@ export class TransactionHandler implements Contracts.Transactions.TransactionHan const { receipt } = await instance.process(data); - void this.#emit(Events.EvmEvent.TransactionReceipt, { - receipt, - sender: transaction.from, - transactionId: transaction.hash, - }); - return receipt; } catch (error) { throw new Error(`invalid EVM call: ${error.message}`); } } - - async #emit(event: string, data?: T): Promise { - if (this.state.isBootstrap()) { - return; - } - - return this.events.dispatch(event, data); - } } export type TransactionHandlerConstructor = new () => TransactionHandler; From 37f886c9fe98d7c0a96e7a3e87b0887956b8293a Mon Sep 17 00:00:00 2001 From: sebastijankuzner Date: Mon, 25 May 2026 13:04:43 +0000 Subject: [PATCH 05/17] Remove verify --- packages/contracts/source/contracts/transactions.ts | 2 -- packages/exceptions/source/crypto.ts | 6 ------ packages/exceptions/source/pool.ts | 6 ------ packages/processor/source/transaction-processor.ts | 5 ----- .../transaction-pool-service/source/errors.test.ts | 8 -------- .../transaction-pool-service/source/sender-state.ts | 13 ++++--------- packages/transactions/source/transaction.ts | 5 ----- 7 files changed, 4 insertions(+), 41 deletions(-) diff --git a/packages/contracts/source/contracts/transactions.ts b/packages/contracts/source/contracts/transactions.ts index b4c2dd3d4..f09342f61 100644 --- a/packages/contracts/source/contracts/transactions.ts +++ b/packages/contracts/source/contracts/transactions.ts @@ -12,8 +12,6 @@ export type TransactionHandlerContext = { }; export interface TransactionHandler { - verify(transaction: Transaction): Promise; - throwIfCannotBeApplied(transaction: Transaction, sender: Wallet, evm: Instance): Promise; apply(context: TransactionHandlerContext, transaction: Transaction): Promise; diff --git a/packages/exceptions/source/crypto.ts b/packages/exceptions/source/crypto.ts index ec4c99517..40d63ada6 100644 --- a/packages/exceptions/source/crypto.ts +++ b/packages/exceptions/source/crypto.ts @@ -334,12 +334,6 @@ export class NotEnoughValidatorsError extends Exception { } } -export class InvalidSignatureError extends Exception { - public constructor() { - super(`Failed to apply transaction, because signature is invalid.`); - } -} - export class MultiPaymentAmountMismatchError extends Exception { public constructor() { super(`Payment amounts mismatch.`); diff --git a/packages/exceptions/source/pool.ts b/packages/exceptions/source/pool.ts index 720e4c616..5a1e663c0 100644 --- a/packages/exceptions/source/pool.ts +++ b/packages/exceptions/source/pool.ts @@ -83,12 +83,6 @@ export class TransactionFailedToApplyError extends PoolError { } } -export class TransactionFailedToVerifyError extends PoolError { - public constructor(transaction: Contracts.Crypto.Transaction) { - super(`tx ${transaction.hash} didn't pass verification`, "ERR_BAD_DATA"); - } -} - export class TransactionFromWrongNetworkError extends PoolError { public currentNetwork: number; diff --git a/packages/processor/source/transaction-processor.ts b/packages/processor/source/transaction-processor.ts index 83d66368a..93934e8b7 100644 --- a/packages/processor/source/transaction-processor.ts +++ b/packages/processor/source/transaction-processor.ts @@ -3,7 +3,6 @@ import type { Contracts } from "@mainsail/contracts"; import { formatCurrency } from "@mainsail/blockchain-utils"; import { Identifiers } from "@mainsail/constants"; import { inject, injectable, tagged } from "@mainsail/container"; -import { InvalidSignatureError } from "@mainsail/exceptions"; @injectable() export class TransactionProcessor implements Contracts.Processor.TransactionProcessor { @@ -52,10 +51,6 @@ export class TransactionProcessor implements Contracts.Processor.TransactionProc }, }; - if (!(await this.transactionHandler.verify(transaction))) { - throw new InvalidSignatureError(); - } - const receipt = await this.transactionHandler.apply(transactionHandlerContext, transaction); const feeConsumed = this.feeCalculator.calculateConsumed(transaction.gasPrice, receipt.gasUsed); diff --git a/packages/transaction-pool-service/source/errors.test.ts b/packages/transaction-pool-service/source/errors.test.ts index 8d51df559..fb7d5cb64 100644 --- a/packages/transaction-pool-service/source/errors.test.ts +++ b/packages/transaction-pool-service/source/errors.test.ts @@ -74,14 +74,6 @@ describe<{ assert.equal(error.message, `tx ${context.transaction.hash} cannot be applied: Something went horribly wrong`); }); - it("TransactionFailedToVerifyError", (context) => { - const error = new Exceptions.TransactionFailedToVerifyError(context.transaction); - - assert.instance(error, Exceptions.PoolError); - assert.equal(error.type, "ERR_BAD_DATA"); - assert.equal(error.message, `tx ${context.transaction.hash} didn't pass verification`); - }); - it("TransactionFromWrongNetworkError", (context) => { const error = new Exceptions.TransactionFromWrongNetworkError(context.transaction, 23); diff --git a/packages/transaction-pool-service/source/sender-state.ts b/packages/transaction-pool-service/source/sender-state.ts index 645508093..60953d3e3 100644 --- a/packages/transaction-pool-service/source/sender-state.ts +++ b/packages/transaction-pool-service/source/sender-state.ts @@ -6,7 +6,6 @@ import { InsufficientBalanceError, TransactionExceedsMaximumByteSizeError, TransactionFailedToApplyError, - TransactionFailedToVerifyError, TransactionFromWrongNetworkError, UnexpectedNonceError, } from "@mainsail/exceptions"; @@ -114,14 +113,10 @@ export class SenderState implements Contracts.TransactionPool.SenderState { throw new InsufficientBalanceError(); } - if (await this.transactionHandler.verify(transaction)) { - try { - await this.transactionHandler.throwIfCannotBeApplied(transaction, this.#wallet, this.evm); - } catch (error) { - throw new TransactionFailedToApplyError(transaction, error); - } - } else { - throw new TransactionFailedToVerifyError(transaction); + try { + await this.transactionHandler.throwIfCannotBeApplied(transaction, this.#wallet, this.evm); + } catch (error) { + throw new TransactionFailedToApplyError(transaction, error); } } } diff --git a/packages/transactions/source/transaction.ts b/packages/transactions/source/transaction.ts index 0321ab0b8..71c296f1c 100644 --- a/packages/transactions/source/transaction.ts +++ b/packages/transactions/source/transaction.ts @@ -22,11 +22,6 @@ export class TransactionHandler implements Contracts.Transactions.TransactionHan @inject(Identifiers.BlockchainUtils.FeeCalculator) protected readonly feeCalculator!: Contracts.BlockchainUtils.FeeCalculator; - public async verify(transaction: Contracts.Crypto.Transaction): Promise { - assert.string(transaction.from); - return this.verifier.verifyHash(transaction); - } - public async throwIfCannotBeApplied( transaction: Contracts.Crypto.Transaction, sender: Contracts.State.Wallet, From b7230ddf66860398e6771fd205278470fe6fe07c Mon Sep 17 00:00:00 2001 From: sebastijankuzner Date: Mon, 25 May 2026 13:12:34 +0000 Subject: [PATCH 06/17] Remove unused contracts --- .../source/contracts/transactions.ts | 22 +------------------ packages/transactions/source/transaction.ts | 2 -- 2 files changed, 1 insertion(+), 23 deletions(-) diff --git a/packages/contracts/source/contracts/transactions.ts b/packages/contracts/source/contracts/transactions.ts index f09342f61..db4b2e7d0 100644 --- a/packages/contracts/source/contracts/transactions.ts +++ b/packages/contracts/source/contracts/transactions.ts @@ -1,9 +1,7 @@ -import type { Transaction, TransactionData } from "./crypto/index.js"; +import type { Transaction } from "./crypto/index.js"; import type { BlockContext, Instance, TransactionReceipt } from "./evm/index.js"; import type { Wallet } from "./state/index.js"; -export type TransactionHandlerConstructor = new () => TransactionHandler; - export type TransactionHandlerContext = { evm: { instance: Instance; @@ -16,21 +14,3 @@ export interface TransactionHandler { apply(context: TransactionHandlerContext, transaction: Transaction): Promise; } - -export interface TransactionHandlerRegistry { - getRegisteredHandlers(): TransactionHandler[]; - - getRegisteredHandlerByType(type: number, version?: number): TransactionHandler; - - getActivatedHandlers(): Promise; - - getActivatedHandlerByType(type: number, version?: number): Promise; - - getActivatedHandlerForData(transactionData: TransactionData): Promise; -} - -export interface TransactionHandlerProvider { - isRegistrationRequired(): boolean; - - registerHandlers(): void; -} diff --git a/packages/transactions/source/transaction.ts b/packages/transactions/source/transaction.ts index 71c296f1c..0a8acfea2 100644 --- a/packages/transactions/source/transaction.ts +++ b/packages/transactions/source/transaction.ts @@ -89,5 +89,3 @@ export class TransactionHandler implements Contracts.Transactions.TransactionHan } } } - -export type TransactionHandlerConstructor = new () => TransactionHandler; From 56bb8bfb8900ea8e268a061162c26e20ffb09ae1 Mon Sep 17 00:00:00 2001 From: sebastijankuzner Date: Mon, 25 May 2026 13:20:07 +0000 Subject: [PATCH 07/17] Remove unused injects --- packages/transactions/source/transaction.ts | 9 --------- 1 file changed, 9 deletions(-) diff --git a/packages/transactions/source/transaction.ts b/packages/transactions/source/transaction.ts index 0a8acfea2..38753bb6e 100644 --- a/packages/transactions/source/transaction.ts +++ b/packages/transactions/source/transaction.ts @@ -7,21 +7,12 @@ import { assert } from "@mainsail/utils"; @injectable() export class TransactionHandler implements Contracts.Transactions.TransactionHandler { - @inject(Identifiers.Application.Instance) - protected readonly app!: Contracts.Kernel.Application; - - @inject(Identifiers.Services.Log.Service) - protected readonly logger!: Contracts.Kernel.Logger; - @inject(Identifiers.Cryptography.Configuration) protected readonly configuration!: Contracts.Crypto.Configuration; @inject(Identifiers.Cryptography.Transaction.Verifier) protected readonly verifier!: Contracts.Crypto.TransactionVerifier; - @inject(Identifiers.BlockchainUtils.FeeCalculator) - protected readonly feeCalculator!: Contracts.BlockchainUtils.FeeCalculator; - public async throwIfCannotBeApplied( transaction: Contracts.Crypto.Transaction, sender: Contracts.State.Wallet, From d9c296f9142c293a9c4a3b21ed38f4816a4d6375 Mon Sep 17 00:00:00 2001 From: sebastijankuzner Date: Mon, 25 May 2026 13:20:40 +0000 Subject: [PATCH 08/17] Make private injects --- packages/transactions/source/transaction.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/transactions/source/transaction.ts b/packages/transactions/source/transaction.ts index 38753bb6e..94cac8539 100644 --- a/packages/transactions/source/transaction.ts +++ b/packages/transactions/source/transaction.ts @@ -8,10 +8,10 @@ import { assert } from "@mainsail/utils"; @injectable() export class TransactionHandler implements Contracts.Transactions.TransactionHandler { @inject(Identifiers.Cryptography.Configuration) - protected readonly configuration!: Contracts.Crypto.Configuration; + private readonly configuration!: Contracts.Crypto.Configuration; @inject(Identifiers.Cryptography.Transaction.Verifier) - protected readonly verifier!: Contracts.Crypto.TransactionVerifier; + private readonly verifier!: Contracts.Crypto.TransactionVerifier; public async throwIfCannotBeApplied( transaction: Contracts.Crypto.Transaction, From 7779b4c371ad87529d1f763bdea047a8c9cfdb0d Mon Sep 17 00:00:00 2001 From: sebastijankuzner Date: Mon, 25 May 2026 13:23:54 +0000 Subject: [PATCH 09/17] Add EvmCallFailedError --- packages/exceptions/source/crypto.ts | 6 ++++++ packages/transactions/source/transaction.ts | 8 ++++++-- 2 files changed, 12 insertions(+), 2 deletions(-) diff --git a/packages/exceptions/source/crypto.ts b/packages/exceptions/source/crypto.ts index 40d63ada6..7cd04fc6c 100644 --- a/packages/exceptions/source/crypto.ts +++ b/packages/exceptions/source/crypto.ts @@ -168,6 +168,12 @@ export class EvmCallMissingRecipientError extends Exception { } } +export class EvmCallFailedError extends Exception { + public constructor(transaction: Contracts.Crypto.Transaction, error: Error ) { + super(`invalid EVM call for tx ${transaction.hash}: ${error.message}`); + } +} + export class InvalidMilestoneConfigurationError extends Exception { public constructor(message: string) { super(message); diff --git a/packages/transactions/source/transaction.ts b/packages/transactions/source/transaction.ts index 94cac8539..3ffb209ca 100644 --- a/packages/transactions/source/transaction.ts +++ b/packages/transactions/source/transaction.ts @@ -2,7 +2,11 @@ import type { Contracts } from "@mainsail/contracts"; import { Identifiers } from "@mainsail/constants"; import { inject, injectable } from "@mainsail/container"; -import { TransactionFailedToPreverifyError, UnexpectedLegacySecondSignatureError } from "@mainsail/exceptions"; +import { + EvmCallFailedError, + TransactionFailedToPreverifyError, + UnexpectedLegacySecondSignatureError, +} from "@mainsail/exceptions"; import { assert } from "@mainsail/utils"; @injectable() @@ -76,7 +80,7 @@ export class TransactionHandler implements Contracts.Transactions.TransactionHan return receipt; } catch (error) { - throw new Error(`invalid EVM call: ${error.message}`); + throw new EvmCallFailedError(transaction, error); } } } From 6ac469a7314714b546b1e30831844929ec132b4f Mon Sep 17 00:00:00 2001 From: sebastijankuzner Date: Mon, 25 May 2026 13:48:12 +0000 Subject: [PATCH 10/17] Split throwIfCannotBeApplied --- packages/transactions/source/transaction.ts | 17 ++++++++++++----- 1 file changed, 12 insertions(+), 5 deletions(-) diff --git a/packages/transactions/source/transaction.ts b/packages/transactions/source/transaction.ts index 3ffb209ca..3c6e70df3 100644 --- a/packages/transactions/source/transaction.ts +++ b/packages/transactions/source/transaction.ts @@ -22,15 +22,22 @@ export class TransactionHandler implements Contracts.Transactions.TransactionHan sender: Contracts.State.Wallet, evm: Contracts.Evm.Instance, ): Promise { - // Legacy + await this.#verifyLegacySecondSignature(transaction, sender); + await this.#preverifyEvm(transaction, evm); + } + + async #verifyLegacySecondSignature( + transaction: Contracts.Crypto.Transaction, + sender: Contracts.State.Wallet, + ): Promise { if (sender.hasLegacySecondPublicKey()) { await this.verifier.verifyLegacySecondSignature(transaction, sender.legacySecondPublicKey()); - } else { - if (transaction.legacySecondSignature) { - throw new UnexpectedLegacySecondSignatureError(); - } + } else if (transaction.legacySecondSignature) { + throw new UnexpectedLegacySecondSignatureError(); } + } + async #preverifyEvm(transaction: Contracts.Crypto.Transaction, evm: Contracts.Evm.Instance): Promise { const milestone = this.configuration.getMilestone(); const preverified = await evm.preverifyTransaction({ From a0f06ae8a145358cfd1487c0547cdbfa3027baf6 Mon Sep 17 00:00:00 2001 From: sebastijankuzner Date: Mon, 25 May 2026 13:58:01 +0000 Subject: [PATCH 11/17] Improve TransactionFailedToPreverifyError --- packages/exceptions/source/pool.ts | 7 ++----- packages/transactions/source/transaction.ts | 6 +----- 2 files changed, 3 insertions(+), 10 deletions(-) diff --git a/packages/exceptions/source/pool.ts b/packages/exceptions/source/pool.ts index 5a1e663c0..c110acfb4 100644 --- a/packages/exceptions/source/pool.ts +++ b/packages/exceptions/source/pool.ts @@ -66,11 +66,8 @@ export class TransactionPoolFullError extends PoolError { } export class TransactionFailedToPreverifyError extends PoolError { - public readonly error: Error; - - public constructor(transaction: Contracts.Crypto.Transaction, error: Error) { - super(`tx ${transaction.hash} cannot be preverified: ${error.message}`, "ERR_PREVERIFY"); - this.error = error; + public constructor(transaction: Contracts.Crypto.Transaction, reason: string) { + super(`tx ${transaction.hash} cannot be preverified: ${reason}`, "ERR_PREVERIFY"); } } diff --git a/packages/transactions/source/transaction.ts b/packages/transactions/source/transaction.ts index 3c6e70df3..13154a31a 100644 --- a/packages/transactions/source/transaction.ts +++ b/packages/transactions/source/transaction.ts @@ -7,7 +7,6 @@ import { TransactionFailedToPreverifyError, UnexpectedLegacySecondSignatureError, } from "@mainsail/exceptions"; -import { assert } from "@mainsail/utils"; @injectable() export class TransactionHandler implements Contracts.Transactions.TransactionHandler { @@ -55,7 +54,7 @@ export class TransactionHandler implements Contracts.Transactions.TransactionHan }); if (!preverified.success) { - throw new TransactionFailedToPreverifyError(transaction, new Error(preverified.error)); + throw new TransactionFailedToPreverifyError(transaction, preverified.error ?? "unknown"); } } @@ -63,8 +62,6 @@ export class TransactionHandler implements Contracts.Transactions.TransactionHan context: Contracts.Transactions.TransactionHandlerContext, transaction: Contracts.Crypto.Transaction, ): Promise { - assert.string(transaction.hash); - const { evmSpec } = this.configuration.getMilestone(); try { @@ -84,7 +81,6 @@ export class TransactionHandler implements Contracts.Transactions.TransactionHan }; const { receipt } = await instance.process(data); - return receipt; } catch (error) { throw new EvmCallFailedError(transaction, error); From 9fd5bd1a4a4496ba16fe90d5b2ed6ad87831b452 Mon Sep 17 00:00:00 2001 From: sebastijankuzner Date: Mon, 25 May 2026 14:01:35 +0000 Subject: [PATCH 12/17] Remove index --- packages/contracts/source/contracts/evm/evm.ts | 1 - packages/evm/bindings/src/ctx.rs | 1 - 2 files changed, 2 deletions(-) diff --git a/packages/contracts/source/contracts/evm/evm.ts b/packages/contracts/source/contracts/evm/evm.ts index 4604d6786..5979b1845 100644 --- a/packages/contracts/source/contracts/evm/evm.ts +++ b/packages/contracts/source/contracts/evm/evm.ts @@ -101,7 +101,6 @@ export interface PreverifyTransactionContext { readonly nonce: bigint; readonly data: Buffer; readonly txHash: string; - readonly index?: number; readonly specId: SpecId; readonly blockGasLimit: bigint; } diff --git a/packages/evm/bindings/src/ctx.rs b/packages/evm/bindings/src/ctx.rs index d14224ef6..4a0895d06 100644 --- a/packages/evm/bindings/src/ctx.rs +++ b/packages/evm/bindings/src/ctx.rs @@ -29,7 +29,6 @@ pub struct JsTransactionContext { pub nonce: BigInt, pub data: Buffer, pub tx_hash: String, - pub index: Option, pub block_context: JsBlockContext, pub spec_id: String, } From ece834d3e4651438ce07d8af4323538bb6036555 Mon Sep 17 00:00:00 2001 From: sebastijankuzner Date: Mon, 25 May 2026 14:05:39 +0000 Subject: [PATCH 13/17] Extract common fields --- packages/transactions/source/transaction.ts | 45 +++++++++------------ 1 file changed, 20 insertions(+), 25 deletions(-) diff --git a/packages/transactions/source/transaction.ts b/packages/transactions/source/transaction.ts index 13154a31a..45ccbb805 100644 --- a/packages/transactions/source/transaction.ts +++ b/packages/transactions/source/transaction.ts @@ -40,17 +40,8 @@ export class TransactionHandler implements Contracts.Transactions.TransactionHan const milestone = this.configuration.getMilestone(); const preverified = await evm.preverifyTransaction({ + ...this.#toEvmTransactionFields(transaction, milestone.evmSpec), blockGasLimit: BigInt(milestone.block.maxGasLimit), - data: Buffer.from(transaction.data.slice(2), "hex"), - from: transaction.from, - gasLimit: BigInt(transaction.gasLimit), - gasPrice: BigInt(transaction.gasPrice), - legacyAddress: transaction.senderLegacyAddress, - nonce: transaction.nonce, - specId: milestone.evmSpec, - to: transaction.to, - txHash: transaction.hash, - value: transaction.value, }); if (!preverified.success) { @@ -63,27 +54,31 @@ export class TransactionHandler implements Contracts.Transactions.TransactionHan transaction: Contracts.Crypto.Transaction, ): Promise { const { evmSpec } = this.configuration.getMilestone(); + const { blockContext, instance } = context.evm; try { - const { blockContext, instance } = context.evm; - const data = { + const { receipt } = await instance.process({ + ...this.#toEvmTransactionFields(transaction, evmSpec), blockContext, - data: Buffer.from(transaction.data.slice(2), "hex"), - from: transaction.from, - gasLimit: BigInt(transaction.gasLimit), - gasPrice: BigInt(transaction.gasPrice), - legacyAddress: transaction.senderLegacyAddress, - nonce: transaction.nonce, - specId: evmSpec, - to: transaction.to, - txHash: transaction.hash, - value: transaction.value, - }; - - const { receipt } = await instance.process(data); + }); return receipt; } catch (error) { throw new EvmCallFailedError(transaction, error); } } + + #toEvmTransactionFields(transaction: Contracts.Crypto.Transaction, specId: Contracts.Evm.SpecId) { + return { + data: Buffer.from(transaction.data.slice(2), "hex"), + from: transaction.from, + gasLimit: BigInt(transaction.gasLimit), + gasPrice: BigInt(transaction.gasPrice), + legacyAddress: transaction.senderLegacyAddress, + nonce: transaction.nonce, + specId, + to: transaction.to, + txHash: transaction.hash, + value: transaction.value, + }; + } } From cc517733065bbd3d079b1942d6684cf04efd7720 Mon Sep 17 00:00:00 2001 From: sebastijankuzner Date: Mon, 25 May 2026 14:21:55 +0000 Subject: [PATCH 14/17] Deps --- packages/transactions/package.json | 3 +-- pnpm-lock.yaml | 5 ++--- 2 files changed, 3 insertions(+), 5 deletions(-) diff --git a/packages/transactions/package.json b/packages/transactions/package.json index 26b23df76..b70a25732 100644 --- a/packages/transactions/package.json +++ b/packages/transactions/package.json @@ -24,8 +24,7 @@ "@mainsail/constants": "workspace:*", "@mainsail/container": "workspace:*", "@mainsail/exceptions": "workspace:*", - "@mainsail/kernel": "workspace:*", - "@mainsail/utils": "workspace:*" + "@mainsail/kernel": "workspace:*" }, "devDependencies": { "@mainsail/contracts": "workspace:*", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 8f9ba0be9..d5a7485d4 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -3195,9 +3195,6 @@ importers: '@mainsail/kernel': specifier: workspace:* version: link:../kernel - '@mainsail/utils': - specifier: workspace:* - version: link:../utils devDependencies: '@mainsail/contracts': specifier: workspace:* @@ -6296,6 +6293,7 @@ packages: basic-ftp@5.0.5: resolution: {integrity: sha512-4Bcg1P8xhUuqcii/S0Z9wiHIrQVPMermM1any+MX5GeGD7faD3/msQUDGLol9wOcz4/jbg/WJnGqoJF6LiBdtg==} engines: {node: '>=10.0.0'} + deprecated: Security vulnerability fixed in 5.2.1, please upgrade bcrypto@5.5.2: resolution: {integrity: sha512-k3PF755oJM0+25iOVuraNedF5XneykxRwl+oBoMeQPfYee4qX8hHQhKCsNZWLthNYgi41GH2ysopd/8sDQDhEw==} @@ -10831,6 +10829,7 @@ packages: uuid@9.0.1: resolution: {integrity: sha512-b+1eJOlsR9K8HJpow9Ok3fiWOWSIcIzXodvv0rQjVoOVNpWMpxf1wZNpt4y9h10odCNrqnYp1OBzRktckBe3sA==} + deprecated: uuid@10 and below is no longer supported. For ESM codebases, update to uuid@latest. For CommonJS codebases, use uuid@11 (but be aware this version will likely be deprecated in 2028). hasBin: true uvu@0.5.6: From bc6a144a2d029d0290e531bde39a91a102dc21ed Mon Sep 17 00:00:00 2001 From: sebastijankuzner <58827427+sebastijankuzner@users.noreply.github.com> Date: Mon, 25 May 2026 14:24:05 +0000 Subject: [PATCH 15/17] style: resolve style guide violations [ci-lint-fix] --- packages/exceptions/source/crypto.ts | 2 +- packages/transaction-pool-service/source/service-provider.ts | 1 - 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/packages/exceptions/source/crypto.ts b/packages/exceptions/source/crypto.ts index 7cd04fc6c..6e1a7e661 100644 --- a/packages/exceptions/source/crypto.ts +++ b/packages/exceptions/source/crypto.ts @@ -169,7 +169,7 @@ export class EvmCallMissingRecipientError extends Exception { } export class EvmCallFailedError extends Exception { - public constructor(transaction: Contracts.Crypto.Transaction, error: Error ) { + public constructor(transaction: Contracts.Crypto.Transaction, error: Error) { super(`invalid EVM call for tx ${transaction.hash}: ${error.message}`); } } diff --git a/packages/transaction-pool-service/source/service-provider.ts b/packages/transaction-pool-service/source/service-provider.ts index f3431bcbb..919bb2e89 100644 --- a/packages/transaction-pool-service/source/service-provider.ts +++ b/packages/transaction-pool-service/source/service-provider.ts @@ -65,5 +65,4 @@ export class ServiceProvider extends Providers.ServiceProvider { this.app.bind(Identifiers.TransactionPool.Storage).to(Storage).inSingletonScope(); this.app.bind(Identifiers.TransactionPool.Selector).to(Selector).inSingletonScope(); } - } From a3c4b2a948a6c672b4eedb5da0fe1ec408ec18ea Mon Sep 17 00:00:00 2001 From: sebastijankuzner Date: Tue, 26 May 2026 23:14:40 +0000 Subject: [PATCH 16/17] Add high S check --- packages/contracts/source/contracts/crypto/identities.ts | 1 + packages/crypto-signature-ecdsa/source/signature.ts | 9 ++++++--- packages/crypto-transaction/source/factory.ts | 4 ++++ 3 files changed, 11 insertions(+), 3 deletions(-) diff --git a/packages/contracts/source/contracts/crypto/identities.ts b/packages/contracts/source/contracts/crypto/identities.ts index e624b49be..9a0da4897 100644 --- a/packages/contracts/source/contracts/crypto/identities.ts +++ b/packages/contracts/source/contracts/crypto/identities.ts @@ -69,4 +69,5 @@ export interface SignatureEcdsa { signRecoverable(message: Buffer, privateKey: Buffer): Promise; verifyRecoverable(signature: EcdsaSignature, message: Buffer, publicKey: Buffer): Promise; recoverPublicKey(message: Buffer, signature: EcdsaSignature): string; + isLowS(signature: EcdsaSignature): boolean; } diff --git a/packages/crypto-signature-ecdsa/source/signature.ts b/packages/crypto-signature-ecdsa/source/signature.ts index 55531db0e..1db086cae 100644 --- a/packages/crypto-signature-ecdsa/source/signature.ts +++ b/packages/crypto-signature-ecdsa/source/signature.ts @@ -20,12 +20,15 @@ export class Signature implements Contracts.Crypto.SignatureEcdsa { message: Buffer, publicKey: Buffer, ): Promise { - const signatureRS = Buffer.from(signature.r + signature.s, "hex"); - if (!secp256k1.isLowS(signatureRS)) { + if (!this.isLowS(signature)) { return false; } - return secp256k1.verify(message, signatureRS, publicKey); + return secp256k1.verify(message, Buffer.from(signature.r + signature.s, "hex"), publicKey); + } + + public isLowS(signature: Contracts.Crypto.EcdsaSignature): boolean { + return secp256k1.isLowS(Buffer.from(signature.r + signature.s, "hex")); } public recoverPublicKey(message: Buffer, signature: Contracts.Crypto.EcdsaSignature): string { diff --git a/packages/crypto-transaction/source/factory.ts b/packages/crypto-transaction/source/factory.ts index 43b805313..66879d7d6 100644 --- a/packages/crypto-transaction/source/factory.ts +++ b/packages/crypto-transaction/source/factory.ts @@ -101,6 +101,10 @@ export class TransactionFactory implements Contracts.Crypto.TransactionFactory { assert.string(data.r); assert.string(data.s); + if (!this.signatureSerializer.isLowS({ r: data.r, s: data.s, v: data.v })) { + throw new InvalidTransactionBytesError("non-canonical signature (high S value)"); + } + const unsignedHash = await this.hashFactory.toHashUnsigned(data); const hash = await this.hashFactory.toHash(data); From 64bba31c401722de8c709d81bd0f4ce939b67b71 Mon Sep 17 00:00:00 2001 From: sebastijankuzner Date: Tue, 26 May 2026 23:16:56 +0000 Subject: [PATCH 17/17] Rename variable --- packages/crypto-transaction/source/factory.ts | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/packages/crypto-transaction/source/factory.ts b/packages/crypto-transaction/source/factory.ts index 66879d7d6..7bac5f8e5 100644 --- a/packages/crypto-transaction/source/factory.ts +++ b/packages/crypto-transaction/source/factory.ts @@ -21,7 +21,7 @@ export class TransactionFactory implements Contracts.Crypto.TransactionFactory { @inject(Identifiers.Cryptography.Signature.Instance) @tagged("type", "wallet") - private readonly signatureSerializer!: Contracts.Crypto.SignatureEcdsa; + private readonly signatureEcdsa!: Contracts.Crypto.SignatureEcdsa; @inject(Identifiers.Cryptography.Transaction.Deserializer) private readonly deserializer!: Contracts.Crypto.TransactionDeserializer; @@ -101,14 +101,14 @@ export class TransactionFactory implements Contracts.Crypto.TransactionFactory { assert.string(data.r); assert.string(data.s); - if (!this.signatureSerializer.isLowS({ r: data.r, s: data.s, v: data.v })) { + if (!this.signatureEcdsa.isLowS({ r: data.r, s: data.s, v: data.v })) { throw new InvalidTransactionBytesError("non-canonical signature (high S value)"); } const unsignedHash = await this.hashFactory.toHashUnsigned(data); const hash = await this.hashFactory.toHash(data); - const senderPublicKey = this.signatureSerializer.recoverPublicKey(unsignedHash, { + const senderPublicKey = this.signatureEcdsa.recoverPublicKey(unsignedHash, { r: data.r, s: data.s, v: data.v,