diff --git a/packages/contracts/source/contracts/transaction-pool/mempool.ts b/packages/contracts/source/contracts/transaction-pool/mempool.ts index a43747de7..07b3886d8 100644 --- a/packages/contracts/source/contracts/transaction-pool/mempool.ts +++ b/packages/contracts/source/contracts/transaction-pool/mempool.ts @@ -4,13 +4,13 @@ import { SenderMempool } from "./sender-mempool.js"; export interface Mempool { getSize(): number; - hasSenderMempool(senderPublicKey: string): boolean; - getSenderMempool(senderPublicKey: string): SenderMempool; + hasSenderMempool(address: string): boolean; + getSenderMempool(address: string): SenderMempool; getSenderMempools(): Iterable; addTransaction(transaction: Transaction): Promise; - removeTransaction(senderPublicKey: string, id: string): Promise; - removeForgedTransaction(senderPublicKey: string, id: string): Promise; + removeTransaction(address: string, id: string): Promise; + removeForgedTransaction(address: string, id: string): Promise; fixInvalidStates(): Promise; diff --git a/packages/contracts/source/contracts/transaction-pool/sender-mempool.ts b/packages/contracts/source/contracts/transaction-pool/sender-mempool.ts index 8fdebb7a9..cb5e8c5a7 100644 --- a/packages/contracts/source/contracts/transaction-pool/sender-mempool.ts +++ b/packages/contracts/source/contracts/transaction-pool/sender-mempool.ts @@ -12,4 +12,4 @@ export interface SenderMempool { removeForgedTransaction(id: string): Transaction | undefined; } -export type SenderMempoolFactory = (publicKey: string) => Promise; +export type SenderMempoolFactory = (address: string) => Promise; diff --git a/packages/contracts/source/contracts/transaction-pool/sender-state.ts b/packages/contracts/source/contracts/transaction-pool/sender-state.ts index 2993557ed..86cdbc6c0 100644 --- a/packages/contracts/source/contracts/transaction-pool/sender-state.ts +++ b/packages/contracts/source/contracts/transaction-pool/sender-state.ts @@ -1,6 +1,6 @@ import { Transaction } from "../crypto/transactions.js"; export interface SenderState { - configure(publicKey: string): Promise; + configure(address: string): Promise; apply(transaction: Transaction): Promise; } diff --git a/packages/contracts/source/contracts/transactions.ts b/packages/contracts/source/contracts/transactions.ts index a4650ad89..93b309385 100644 --- a/packages/contracts/source/contracts/transactions.ts +++ b/packages/contracts/source/contracts/transactions.ts @@ -13,7 +13,7 @@ export type TransactionHandlerContext = { export interface TransactionApplyResult { gasUsed: number; - receipt?: TransactionReceipt; + receipt: TransactionReceipt; } export interface TransactionHandler { @@ -21,14 +21,8 @@ export interface TransactionHandler { throwIfCannotBeApplied(transaction: Transaction, sender: Wallet): Promise; - throwIfCannotEnterPool(transaction: Transaction): Promise; - apply(context: TransactionHandlerContext, transaction: Transaction): Promise; - applyToSender(context: TransactionHandlerContext, transaction: Transaction): Promise; - - applyToRecipient(context: TransactionHandlerContext, transaction: Transaction): Promise; - emitEvents(transaction: Transaction): void; verifySignatures( diff --git a/packages/contracts/source/exceptions/crypto.ts b/packages/contracts/source/exceptions/crypto.ts index 54a99f17c..718647b8d 100644 --- a/packages/contracts/source/exceptions/crypto.ts +++ b/packages/contracts/source/exceptions/crypto.ts @@ -217,10 +217,9 @@ export class AlreadyRegisteredError extends Exception { } export class UnexpectedNonceError extends Exception { - public constructor(txNonce: any, sender: Wallet, reversal: boolean) { - const action: string = reversal ? "revert" : "apply"; + public constructor(txNonce: any, sender: Wallet) { super( - `Cannot ${action} a transaction with nonce ${txNonce.toFixed()}: the ` + + `Cannot apply a transaction with nonce ${txNonce.toFixed()}: the ` + `sender ${sender.getAddress()} has nonce ${sender.getNonce().toFixed()}.`, ); } diff --git a/packages/crypto-transaction-evm-call/source/handlers/evm-call.ts b/packages/crypto-transaction-evm-call/source/handlers/evm-call.ts index a3e5ccd34..c51da513e 100644 --- a/packages/crypto-transaction-evm-call/source/handlers/evm-call.ts +++ b/packages/crypto-transaction-evm-call/source/handlers/evm-call.ts @@ -1,5 +1,5 @@ import { inject, injectable } from "@mainsail/container"; -import { Contracts, Events, Exceptions, Identifiers } from "@mainsail/contracts"; +import { Contracts, Events, Identifiers } from "@mainsail/contracts"; import { TransactionConstructor } from "@mainsail/crypto-transaction"; import { Utils } from "@mainsail/kernel"; import { Handlers } from "@mainsail/transactions"; @@ -11,9 +11,6 @@ export class EvmCallTransactionHandler extends Handlers.TransactionHandler { @inject(Identifiers.Services.EventDispatcher.Service) private readonly events!: Contracts.Kernel.EventDispatcher; - @inject(Identifiers.Evm.Gas.FeeCalculator) - private readonly gasFeeCalculator!: Contracts.Evm.GasFeeCalculator; - @inject(Identifiers.State.State) private readonly state!: Contracts.State.State; @@ -32,26 +29,7 @@ export class EvmCallTransactionHandler extends Handlers.TransactionHandler { return true; } - public async throwIfCannotBeApplied( - transaction: Contracts.Crypto.Transaction, - wallet: Contracts.State.Wallet, - ): Promise { - return super.throwIfCannotBeApplied(transaction, wallet); - } - - public async throwIfCannotEnterPool(transaction: Contracts.Crypto.Transaction): Promise {} - - public async applyToSender( - context: Contracts.Transactions.TransactionHandlerContext, - transaction: Contracts.Crypto.Transaction, - ): Promise { - await super.applyToSender(context, transaction); - - // Taken from receipt in applyToRecipient - return { gasUsed: 0 }; - } - - public async applyToRecipient( + public async apply( context: Contracts.Transactions.TransactionHandlerContext, transaction: Contracts.Crypto.Transaction, ): Promise { @@ -80,21 +58,11 @@ export class EvmCallTransactionHandler extends Handlers.TransactionHandler { value: transaction.data.amount.toBigInt(), }); - if (instance.mode() === Contracts.Evm.EvmMode.Persistent && !this.state.isBootstrap()) { - const feeConsumed = this.gasFeeCalculator.calculateConsumed( - transaction.data.fee, - Number(receipt.gasUsed), - ); - this.logger.debug( - `executed EVM call (success=${receipt.success}, gasUsed=${receipt.gasUsed} paidNativeFee=${Utils.formatCurrency(this.configuration, feeConsumed)} deployed=${receipt.deployedContractAddress})`, - ); - - void this.#emit(Events.EvmEvent.TransactionReceipt, { - receipt, - sender: address, - transactionId: transaction.id, - }); - } + void this.#emit(Events.EvmEvent.TransactionReceipt, { + receipt, + sender: address, + transactionId: transaction.id, + }); return { gasUsed: Number(receipt.gasUsed), receipt }; } catch (error) { @@ -102,19 +70,6 @@ export class EvmCallTransactionHandler extends Handlers.TransactionHandler { } } - protected verifyTransactionFee(transaction: Contracts.Crypto.Transaction, sender: Contracts.State.Wallet): void { - Utils.assert.defined(transaction.data.asset?.evmCall); - - const maxFee = this.gasFeeCalculator.calculate(transaction); - if (sender.getBalance().minus(maxFee).isNegative() && this.configuration.getHeight() > 0) { - throw new Exceptions.InsufficientBalanceError(); - } - } - - protected applyFeeToSender(transaction: Contracts.Crypto.Transaction, sender: Contracts.State.Wallet): void { - // Fee is taken after EVM execution to take the actual consumed gas into account - } - async #emit(event: Contracts.Kernel.EventName, data?: T): Promise { if (this.state.isBootstrap()) { return; diff --git a/packages/processor/source/transaction-processor.ts b/packages/processor/source/transaction-processor.ts index 95c854965..50debe79e 100644 --- a/packages/processor/source/transaction-processor.ts +++ b/packages/processor/source/transaction-processor.ts @@ -1,6 +1,6 @@ import { inject, injectable, tagged } from "@mainsail/container"; import { Contracts, Exceptions, Identifiers } from "@mainsail/contracts"; -import { Utils as AppUtils } from "@mainsail/kernel"; +import { Utils } from "@mainsail/kernel"; @injectable() export class TransactionProcessor implements Contracts.Processor.TransactionProcessor { @@ -8,6 +8,12 @@ export class TransactionProcessor implements Contracts.Processor.TransactionProc @tagged("instance", "evm") private readonly evm!: Contracts.Evm.Instance; + @inject(Identifiers.Evm.Gas.FeeCalculator) + protected readonly gasFeeCalculator!: Contracts.Evm.GasFeeCalculator; + + @inject(Identifiers.Services.Log.Service) + protected readonly logger!: Contracts.Kernel.Logger; + @inject(Identifiers.Application.Instance) public readonly app!: Contracts.Kernel.Application; @@ -46,7 +52,15 @@ export class TransactionProcessor implements Contracts.Processor.TransactionProc } const result = await transactionHandler.apply(transactionHandlerContext, transaction); - AppUtils.assert.defined(transaction.data.senderPublicKey); + Utils.assert.defined(result.receipt); + + const feeConsumed = this.gasFeeCalculator.calculateConsumed( + transaction.data.fee, + Number(result.receipt.gasUsed), + ); + this.logger.debug( + `executed EVM call (success=${result.receipt.success}, gasUsed=${result.receipt.gasUsed} paidNativeFee=${Utils.formatCurrency(this.configuration, feeConsumed)} deployed=${result.receipt.deployedContractAddress})`, + ); return { gasUsed: result.gasUsed, receipt: result.receipt }; } diff --git a/packages/state/source/wallets/wallet.ts b/packages/state/source/wallets/wallet.ts index 0d7b96ab4..cbb79ca5d 100644 --- a/packages/state/source/wallets/wallet.ts +++ b/packages/state/source/wallets/wallet.ts @@ -1,15 +1,23 @@ -import { injectable } from "@mainsail/container"; -import { Contracts } from "@mainsail/contracts"; +import { inject, injectable, tagged } from "@mainsail/container"; +import { Contracts, Identifiers } from "@mainsail/contracts"; import { BigNumber } from "@mainsail/utils"; @injectable() export class Wallet implements Contracts.State.Wallet { + @inject(Identifiers.Evm.Instance) + @tagged("instance", "evm") + private readonly evm!: Contracts.Evm.Instance; + protected address!: string; protected balance = BigNumber.ZERO; protected nonce = BigNumber.ZERO; - public init(address: string): Wallet { + public async init(address: string): Promise { this.address = address; + + const accountInfo = await this.evm.getAccountInfo(address); + this.balance = BigNumber.make(accountInfo.balance); + this.nonce = BigNumber.make(accountInfo.nonce); return this; } diff --git a/packages/transaction-pool-service/package.json b/packages/transaction-pool-service/package.json index 5a24f3e29..04eb8d273 100644 --- a/packages/transaction-pool-service/package.json +++ b/packages/transaction-pool-service/package.json @@ -25,6 +25,7 @@ "@mainsail/container": "workspace:*", "@mainsail/contracts": "workspace:*", "@mainsail/kernel": "workspace:*", + "@mainsail/state": "workspace:*", "better-sqlite3": "11.2.1", "fs-extra": "11.2.0", "joi": "17.12.2" diff --git a/packages/transaction-pool-service/source/actions/apply-transaction.ts b/packages/transaction-pool-service/source/actions/apply-transaction.ts deleted file mode 100644 index 1103694c3..000000000 --- a/packages/transaction-pool-service/source/actions/apply-transaction.ts +++ /dev/null @@ -1,18 +0,0 @@ -import { Contracts } from "@mainsail/contracts"; -import { Types } from "@mainsail/kernel"; - -import { TransactionTriggerAction } from "./transaction-trigger-action.js"; - -export class ApplyTransactionAction extends TransactionTriggerAction { - public async execute(arguments_: Types.ActionArguments): Promise { - const handler: Contracts.Transactions.TransactionHandler = arguments_.handler; - const transaction: Contracts.Crypto.Transaction = arguments_.transaction; - - await handler.apply( - { - evm: this.mockEvmContext(), - }, - transaction, - ); - } -} diff --git a/packages/transaction-pool-service/source/actions/index.ts b/packages/transaction-pool-service/source/actions/index.ts index 0083cdebb..438af59fb 100644 --- a/packages/transaction-pool-service/source/actions/index.ts +++ b/packages/transaction-pool-service/source/actions/index.ts @@ -1,3 +1,2 @@ -export { ApplyTransactionAction } from "./apply-transaction.js"; -export { ThrowIfCannotEnterPoolAction } from "./throw-if-cannot-enter-pool.js"; -export { VerifyTransactionAction } from "./verify-transaction.js"; +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-enter-pool.ts b/packages/transaction-pool-service/source/actions/throw-if-cannot-be-applied.ts similarity index 53% rename from packages/transaction-pool-service/source/actions/throw-if-cannot-enter-pool.ts rename to packages/transaction-pool-service/source/actions/throw-if-cannot-be-applied.ts index c7b3a4a06..ff80135da 100644 --- a/packages/transaction-pool-service/source/actions/throw-if-cannot-enter-pool.ts +++ b/packages/transaction-pool-service/source/actions/throw-if-cannot-be-applied.ts @@ -1,13 +1,12 @@ import { Contracts } from "@mainsail/contracts"; -import { Types } from "@mainsail/kernel"; +import { Services, Types } from "@mainsail/kernel"; -import { TransactionTriggerAction } from "./transaction-trigger-action.js"; - -export class ThrowIfCannotEnterPoolAction extends TransactionTriggerAction { +export class ThrowIfCannotBeAppliedAction extends Services.Triggers.Action { public async execute(arguments_: Types.ActionArguments): Promise { const handler: Contracts.Transactions.TransactionHandler = arguments_.handler; const transaction: Contracts.Crypto.Transaction = arguments_.transaction; + const sender: Contracts.State.Wallet = arguments_.sender; - return handler.throwIfCannotEnterPool(transaction); + await handler.throwIfCannotBeApplied(transaction, sender); } } diff --git a/packages/transaction-pool-service/source/actions/transaction-trigger-action.ts b/packages/transaction-pool-service/source/actions/transaction-trigger-action.ts deleted file mode 100644 index 004439ae5..000000000 --- a/packages/transaction-pool-service/source/actions/transaction-trigger-action.ts +++ /dev/null @@ -1,21 +0,0 @@ -import { inject, tagged } from "@mainsail/container"; -import { Contracts, Identifiers } from "@mainsail/contracts"; -import { Services } from "@mainsail/kernel"; - -export abstract class TransactionTriggerAction extends Services.Triggers.Action { - @inject(Identifiers.Evm.Instance) - @tagged("instance", "mock") - protected readonly evm!: Contracts.Evm.Instance; - - protected mockEvmContext(): { instance: Contracts.Evm.Instance; blockContext: Contracts.Evm.BlockContext } { - return { - blockContext: { - commitKey: { height: BigInt(0), round: BigInt(0) }, - gasLimit: BigInt(0), - timestamp: BigInt(0), - validatorAddress: "0x0000000000000000000000000000000000000000", - }, - instance: this.evm, - }; - } -} diff --git a/packages/transaction-pool-service/source/actions/verify-transaction.ts b/packages/transaction-pool-service/source/actions/verify-transaction.ts index a59d5e43e..eb91842aa 100644 --- a/packages/transaction-pool-service/source/actions/verify-transaction.ts +++ b/packages/transaction-pool-service/source/actions/verify-transaction.ts @@ -1,9 +1,7 @@ import { Contracts } from "@mainsail/contracts"; -import { Types } from "@mainsail/kernel"; +import { Services, Types } from "@mainsail/kernel"; -import { TransactionTriggerAction } from "./transaction-trigger-action.js"; - -export class VerifyTransactionAction extends TransactionTriggerAction { +export class VerifyTransactionAction extends Services.Triggers.Action { public async execute(arguments_: Types.ActionArguments): Promise { const handler: Contracts.Transactions.TransactionHandler = arguments_.handler; const transaction: Contracts.Crypto.Transaction = arguments_.transaction; diff --git a/packages/transaction-pool-service/source/mempool.ts b/packages/transaction-pool-service/source/mempool.ts index db0ffc79a..a39883c50 100644 --- a/packages/transaction-pool-service/source/mempool.ts +++ b/packages/transaction-pool-service/source/mempool.ts @@ -20,12 +20,12 @@ export class Mempool implements Contracts.TransactionPool.Mempool { return [...this.#senderMempools.values()].reduce((sum, p) => sum + p.getSize(), 0); } - public hasSenderMempool(senderPublicKey: string): boolean { - return this.#senderMempools.has(senderPublicKey); + public hasSenderMempool(address: string): boolean { + return this.#senderMempools.has(address); } - public getSenderMempool(senderPublicKey: string): Contracts.TransactionPool.SenderMempool { - const senderMempool = this.#senderMempools.get(senderPublicKey); + public getSenderMempool(address: string): Contracts.TransactionPool.SenderMempool { + const senderMempool = this.#senderMempools.get(address); if (!senderMempool) { throw new Error("Unknown sender"); } @@ -39,10 +39,10 @@ export class Mempool implements Contracts.TransactionPool.Mempool { public async fixInvalidStates(): Promise { const removedTransactions: Contracts.Crypto.Transaction[] = []; - for (const senderPublicKey of this.#brokenSenders) { - const transactionsForReadd = [...this.getSenderMempool(senderPublicKey).getFromEarliest()]; + for (const address of this.#brokenSenders) { + const transactionsForReadd = [...this.getSenderMempool(address).getFromEarliest()]; - const newSenderMempool = await this.createSenderMempool.call(this, senderPublicKey); + const newSenderMempool = await this.createSenderMempool.call(this, address); for (let index = 0; index < transactionsForReadd.length; index++) { const transaction = transactionsForReadd[index]; @@ -57,9 +57,9 @@ export class Mempool implements Contracts.TransactionPool.Mempool { } } - this.#senderMempools.delete(senderPublicKey); + this.#senderMempools.delete(address); if (newSenderMempool.getSize()) { - this.#senderMempools.set(senderPublicKey, newSenderMempool); + this.#senderMempools.set(address, newSenderMempool); } } @@ -68,39 +68,39 @@ export class Mempool implements Contracts.TransactionPool.Mempool { } public async addTransaction(transaction: Contracts.Crypto.Transaction): Promise { - let senderMempool = this.#senderMempools.get(transaction.data.senderPublicKey); + const address = await this.addressFactory.fromPublicKey(transaction.data.senderPublicKey); + + let senderMempool = this.#senderMempools.get(address); if (!senderMempool) { - senderMempool = await this.createSenderMempool.call(this, transaction.data.senderPublicKey); - this.#senderMempools.set(transaction.data.senderPublicKey, senderMempool); - this.logger.debug( - `${await this.addressFactory.fromPublicKey(transaction.data.senderPublicKey)} state created`, - ); + senderMempool = await this.createSenderMempool.call(this, address); + this.#senderMempools.set(address, senderMempool); + this.logger.debug(`${address} state created`); } try { await senderMempool.addTransaction(transaction); } finally { - await this.#removeDisposableMempool(transaction.data.senderPublicKey); + await this.#removeDisposableMempool(address); } } - public async removeTransaction(senderPublicKey: string, id: string): Promise { - const senderMempool = this.#senderMempools.get(senderPublicKey); + public async removeTransaction(address: string, id: string): Promise { + const senderMempool = this.#senderMempools.get(address); if (!senderMempool) { return []; } const transactions = senderMempool.removeTransaction(id); - if (transactions.length > 0 && !(await this.#removeDisposableMempool(senderPublicKey))) { - this.#brokenSenders.add(senderPublicKey); + if (transactions.length > 0 && !(await this.#removeDisposableMempool(address))) { + this.#brokenSenders.add(address); } return transactions; } - public async removeForgedTransaction(senderPublicKey: string, id: string): Promise { - const senderMempool = this.#senderMempools.get(senderPublicKey); + public async removeForgedTransaction(address: string, id: string): Promise { + const senderMempool = this.#senderMempools.get(address); if (!senderMempool) { return []; } @@ -108,11 +108,11 @@ export class Mempool implements Contracts.TransactionPool.Mempool { const transaction = senderMempool.removeForgedTransaction(id); if (!transaction) { - this.#brokenSenders.add(senderPublicKey); + this.#brokenSenders.add(address); return []; } - await this.#removeDisposableMempool(senderPublicKey); + await this.#removeDisposableMempool(address); return [transaction]; } @@ -121,12 +121,12 @@ export class Mempool implements Contracts.TransactionPool.Mempool { this.#senderMempools.clear(); } - async #removeDisposableMempool(senderPublicKey: string): Promise { - const senderMempool = this.#senderMempools.get(senderPublicKey); + async #removeDisposableMempool(address: string): Promise { + const senderMempool = this.#senderMempools.get(address); if (senderMempool && senderMempool.isDisposable()) { - this.#senderMempools.delete(senderPublicKey); - this.logger.debug(`${await this.addressFactory.fromPublicKey(senderPublicKey)} state disposed`); + this.#senderMempools.delete(address); + this.logger.debug(`${address} state disposed`); return true; } diff --git a/packages/transaction-pool-service/source/sender-mempool.ts b/packages/transaction-pool-service/source/sender-mempool.ts index eae61c240..93e703828 100644 --- a/packages/transaction-pool-service/source/sender-mempool.ts +++ b/packages/transaction-pool-service/source/sender-mempool.ts @@ -8,6 +8,10 @@ export class SenderMempool implements Contracts.TransactionPool.SenderMempool { @tagged("plugin", "transaction-pool-service") private readonly configuration!: Providers.PluginConfiguration; + @inject(Identifiers.Cryptography.Identity.Address.Factory) + @tagged("type", "wallet") + private readonly addressFactory!: Contracts.Crypto.AddressFactory; + @inject(Identifiers.TransactionPool.SenderState) private readonly senderState!: Contracts.TransactionPool.SenderState; @@ -17,8 +21,8 @@ export class SenderMempool implements Contracts.TransactionPool.SenderMempool { readonly #transactions: Contracts.Crypto.Transaction[] = []; - public async configure(publicKey: string): Promise { - await this.senderState.configure(publicKey); + public async configure(address: string): Promise { + await this.senderState.configure(address); return this; } @@ -47,7 +51,11 @@ export class SenderMempool implements Contracts.TransactionPool.SenderMempool { this.configuration.getRequired("maxTransactionsPerSender"); if (this.#transactions.length >= maxTransactionsPerSender) { const allowedSenders: string[] = this.configuration.getOptional("allowedSenders", []); - if (!allowedSenders.includes(transaction.data.senderPublicKey)) { + if ( + !allowedSenders.includes( + await this.addressFactory.fromPublicKey(transaction.data.senderPublicKey), + ) + ) { throw new Exceptions.SenderExceededMaximumTransactionCountError( transaction, maxTransactionsPerSender, diff --git a/packages/transaction-pool-service/source/sender-state.ts b/packages/transaction-pool-service/source/sender-state.ts index ce734ac93..6732f034b 100644 --- a/packages/transaction-pool-service/source/sender-state.ts +++ b/packages/transaction-pool-service/source/sender-state.ts @@ -1,9 +1,13 @@ import { inject, injectable, tagged } from "@mainsail/container"; import { Contracts, Events, Exceptions, Identifiers } from "@mainsail/contracts"; import { Providers, Services } from "@mainsail/kernel"; +import { Wallets } from "@mainsail/state"; @injectable() export class SenderState implements Contracts.TransactionPool.SenderState { + @inject(Identifiers.Application.Instance) + private readonly app!: Contracts.Kernel.Application; + @inject(Identifiers.ServiceProvider.Configuration) @tagged("plugin", "transaction-pool-service") private readonly configuration!: Providers.PluginConfiguration; @@ -14,6 +18,9 @@ export class SenderState implements Contracts.TransactionPool.SenderState { @inject(Identifiers.Transaction.Handler.Registry) private readonly handlerRegistry!: Contracts.Transactions.TransactionHandlerRegistry; + @inject(Identifiers.Evm.Gas.FeeCalculator) + protected readonly gasFeeCalculator!: Contracts.Evm.GasFeeCalculator; + @inject(Identifiers.TransactionPool.ExpirationService) private readonly expirationService!: Contracts.TransactionPool.ExpirationService; @@ -24,8 +31,11 @@ export class SenderState implements Contracts.TransactionPool.SenderState { private readonly events!: Contracts.Kernel.EventDispatcher; #corrupt = false; + #wallet!: Contracts.State.Wallet; + + public async configure(address: string): Promise { + this.#wallet = await this.app.resolve(Wallets.Wallet).init(address); - public async configure(publicKey): Promise { return this; } @@ -63,17 +73,17 @@ export class SenderState implements Contracts.TransactionPool.SenderState { } try { - await this.triggers.call("throwIfCannotEnterPool", { - handler, - transaction, - }); - await this.triggers.call("applyTransaction", { + await this.triggers.call("throwIfCannotBeApplied", { handler, + sender: this.#wallet, transaction, }); } catch (error) { throw new Exceptions.TransactionFailedToApplyError(transaction, error); } + + this.#wallet.increaseNonce(); + this.#wallet.decreaseBalance(transaction.data.amount.plus(this.gasFeeCalculator.calculate(transaction))); } else { throw new Exceptions.TransactionFailedToVerifyError(transaction); } diff --git a/packages/transaction-pool-service/source/service-provider.ts b/packages/transaction-pool-service/source/service-provider.ts index 3c30e7548..168bdc4e8 100644 --- a/packages/transaction-pool-service/source/service-provider.ts +++ b/packages/transaction-pool-service/source/service-provider.ts @@ -2,7 +2,7 @@ import { Identifiers } from "@mainsail/contracts"; import { Providers, Services } from "@mainsail/kernel"; import Joi from "joi"; -import { ApplyTransactionAction, ThrowIfCannotEnterPoolAction, VerifyTransactionAction } from "./actions/index.js"; +import { ThrowIfCannotBeAppliedAction, VerifyTransactionAction } from "./actions/index.js"; import { ExpirationService } from "./expiration-service.js"; import { Mempool } from "./mempool.js"; import { Processor } from "./processor.js"; @@ -63,14 +63,10 @@ export class ServiceProvider extends Providers.ServiceProvider { #registerActions(): void { this.app .get(Identifiers.Services.Trigger.Service) - .bind("applyTransaction", this.app.resolve(ApplyTransactionAction)); - - this.app - .get(Identifiers.Services.Trigger.Service) - .bind("throwIfCannotEnterPool", this.app.resolve(ThrowIfCannotEnterPoolAction)); + .bind("verifyTransaction", this.app.resolve(VerifyTransactionAction)); this.app .get(Identifiers.Services.Trigger.Service) - .bind("verifyTransaction", this.app.resolve(VerifyTransactionAction)); + .bind("throwIfCannotBeApplied", this.app.resolve(ThrowIfCannotBeAppliedAction)); } } diff --git a/packages/transaction-pool-service/source/service.ts b/packages/transaction-pool-service/source/service.ts index 8a5b267c5..f3d8a132d 100644 --- a/packages/transaction-pool-service/source/service.ts +++ b/packages/transaction-pool-service/source/service.ts @@ -8,6 +8,10 @@ export class Service implements Contracts.TransactionPool.Service { @tagged("plugin", "transaction-pool-service") private readonly pluginConfiguration!: Providers.PluginConfiguration; + @inject(Identifiers.Cryptography.Identity.Address.Factory) + @tagged("type", "wallet") + private readonly addressFactory!: Contracts.Crypto.AddressFactory; + @inject(Identifiers.State.Store) private readonly stateStore!: Contracts.State.Store; @@ -65,7 +69,7 @@ export class Service implements Contracts.TransactionPool.Service { for (const transaction of block.transactions) { const transactions = await this.mempool.removeForgedTransaction( - transaction.data.senderPublicKey, + await this.addressFactory.fromPublicKey(transaction.data.senderPublicKey), transaction.id, ); @@ -78,7 +82,7 @@ export class Service implements Contracts.TransactionPool.Service { for (const transaction of failedTransactions) { const transactions = await this.mempool.removeTransaction( - transaction.data.senderPublicKey, + await this.addressFactory.fromPublicKey(transaction.data.senderPublicKey), transaction.id, ); @@ -209,7 +213,10 @@ export class Service implements Contracts.TransactionPool.Service { const expiredHeight: number = lastHeight - maxTransactionAge; for (const { senderPublicKey, id } of this.storage.getOldTransactions(expiredHeight)) { - const removedTransactions = await this.mempool.removeTransaction(senderPublicKey, id); + const removedTransactions = await this.mempool.removeTransaction( + await this.addressFactory.fromPublicKey(senderPublicKey), + id, + ); for (const removedTransaction of removedTransactions) { this.storage.removeTransaction(removedTransaction.id); @@ -224,7 +231,7 @@ export class Service implements Contracts.TransactionPool.Service { for (const transaction of await this.poolQuery.getAll().all()) { if (await this.expirationService.isExpired(transaction)) { const removedTransactions = await this.mempool.removeTransaction( - transaction.data.senderPublicKey, + await this.addressFactory.fromPublicKey(transaction.data.senderPublicKey), transaction.id, ); @@ -245,7 +252,7 @@ export class Service implements Contracts.TransactionPool.Service { const transaction = await this.poolQuery.getFromLowestPriority().first(); const removedTransactions = await this.mempool.removeTransaction( - transaction.data.senderPublicKey, + await this.addressFactory.fromPublicKey(transaction.data.senderPublicKey), transaction.id, ); diff --git a/packages/transactions/package.json b/packages/transactions/package.json index c124753e9..390a2ab94 100644 --- a/packages/transactions/package.json +++ b/packages/transactions/package.json @@ -25,11 +25,11 @@ "@mainsail/container": "workspace:*", "@mainsail/contracts": "workspace:*", "@mainsail/crypto-transaction": "workspace:*", - "@mainsail/kernel": "workspace:*", - "@mainsail/utils": "workspace:*" + "@mainsail/kernel": "workspace:*" }, "devDependencies": { "@mainsail/crypto-config": "workspace:*", + "@mainsail/utils": "workspace:*", "dayjs": "1.11.10", "uvu": "^0.5.6" }, diff --git a/packages/transactions/source/handlers/transaction.ts b/packages/transactions/source/handlers/transaction.ts index 45717afc7..ab61a1d34 100644 --- a/packages/transactions/source/handlers/transaction.ts +++ b/packages/transactions/source/handlers/transaction.ts @@ -1,13 +1,15 @@ import { inject, injectable } from "@mainsail/container"; import { Contracts, Exceptions, Identifiers } from "@mainsail/contracts"; import { Utils as AppUtils } from "@mainsail/kernel"; -import { BigNumber } from "@mainsail/utils"; @injectable() export abstract class TransactionHandler implements Contracts.Transactions.TransactionHandler { @inject(Identifiers.Application.Instance) protected readonly app!: Contracts.Kernel.Application; + @inject(Identifiers.Evm.Gas.FeeCalculator) + protected readonly gasFeeCalculator!: Contracts.Evm.GasFeeCalculator; + @inject(Identifiers.Services.Log.Service) protected readonly logger!: Contracts.Kernel.Logger; @@ -25,7 +27,6 @@ export abstract class TransactionHandler implements Contracts.Transactions.Trans public async verify(transaction: Contracts.Crypto.Transaction): Promise { AppUtils.assert.defined(transaction.data.senderPublicKey); - return this.verifier.verifyHash(transaction.data); } @@ -33,56 +34,24 @@ export abstract class TransactionHandler implements Contracts.Transactions.Trans transaction: Contracts.Crypto.Transaction, sender: Contracts.State.Wallet, ): Promise { - // @TODO: enforce fees here to support dynamic cases - - //this.#verifyTransactionNonceApply(sender, transaction); - - //this.verifyTransactionFee(context, transaction, sender); + if (!sender.getNonce().isEqualTo(transaction.data.nonce)) { + throw new Exceptions.UnexpectedNonceError(transaction.data.nonce, sender); + } if ( - sender.getBalance().minus(transaction.data.amount).minus(transaction.data.fee).isNegative() && + sender + .getBalance() + .minus(transaction.data.amount) + .minus(this.gasFeeCalculator.calculate(transaction)) + .isNegative() && this.configuration.getHeight() > 0 ) { throw new Exceptions.InsufficientBalanceError(); } - - // if (transaction.data.senderPublicKey !== sender.getPublicKey()) { - // throw new Exceptions.SenderWalletMismatchError(); - // } - } - - public async apply( - context: Contracts.Transactions.TransactionHandlerContext, - transaction: Contracts.Crypto.Transaction, - ): Promise { - const senderResult = await this.applyToSender(context, transaction); - const recipientResult = await this.applyToRecipient(context, transaction); - - // Merge results; effectively only one is ever set depending on the transaction type. - return { - gasUsed: senderResult.gasUsed + recipientResult.gasUsed, - receipt: recipientResult.receipt, - }; - } - - public async applyToSender( - context: Contracts.Transactions.TransactionHandlerContext, - transaction: Contracts.Crypto.Transaction, - ): Promise { - return { gasUsed: 0 }; - } - - public async applyToRecipient( - context: Contracts.Transactions.TransactionHandlerContext, - transaction: Contracts.Crypto.Transaction, - ): Promise { - return { gasUsed: 0 }; } public emitEvents(transaction: Contracts.Crypto.Transaction): void {} - public async throwIfCannotEnterPool(transaction: Contracts.Crypto.Transaction): Promise {} - public async verifySignatures( wallet: Contracts.State.Wallet, transaction: Contracts.Crypto.TransactionData, @@ -91,14 +60,6 @@ export abstract class TransactionHandler implements Contracts.Transactions.Trans return this.verifier.verifySignatures(transaction, multiSignature); } - // #verifyTransactionNonceApply(wallet: Contracts.State.Wallet, transaction: Contracts.Crypto.Transaction): void { - // const nonce: BigNumber = transaction.data.nonce || BigNumber.ZERO; - - // if (!wallet.getNonce().isEqualTo(nonce)) { - // throw new Exceptions.UnexpectedNonceError(nonce, wallet, false); - // } - // } - protected allTransactions(transactions: Contracts.Crypto.Transaction[]): Contracts.Crypto.TransactionData[] { return transactions .filter( @@ -108,21 +69,10 @@ export abstract class TransactionHandler implements Contracts.Transactions.Trans .map(({ data }) => data); } - protected verifyTransactionFee(transaction: Contracts.Crypto.Transaction, sender: Contracts.State.Wallet): void { - if ( - sender.getBalance().minus(transaction.data.amount).minus(transaction.data.fee).isNegative() && - this.configuration.getHeight() > 0 - ) { - throw new Exceptions.InsufficientBalanceError(); - } - } - - protected applyFeeToSender(transaction: Contracts.Crypto.Transaction, sender: Contracts.State.Wallet): void { - const data: Contracts.Crypto.TransactionData = transaction.data; - - const newBalance: BigNumber = sender.getBalance().minus(data.fee); - sender.setBalance(newBalance); - } + public abstract apply( + context: Contracts.Transactions.TransactionHandlerContext, + transaction: Contracts.Crypto.Transaction, + ): Promise; public abstract getConstructor(): Contracts.Crypto.TransactionConstructor; diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index dc30b8f78..d3bf807fa 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -2562,6 +2562,9 @@ importers: '@mainsail/kernel': specifier: workspace:* version: link:../kernel + '@mainsail/state': + specifier: workspace:* + version: link:../state better-sqlite3: specifier: 11.2.1 version: 11.2.1 @@ -2624,13 +2627,13 @@ importers: '@mainsail/kernel': specifier: workspace:* version: link:../kernel - '@mainsail/utils': - specifier: workspace:* - version: link:../utils devDependencies: '@mainsail/crypto-config': specifier: workspace:* version: link:../crypto-config + '@mainsail/utils': + specifier: workspace:* + version: link:../utils dayjs: specifier: 1.11.10 version: 1.11.10