Skip to content

Commit

Permalink
fix(transaction-pool-service): add senders nonce and balance checks (#…
Browse files Browse the repository at this point in the history
…716)

* Remove throwIfCannotEnterPool

* Drop applyToSender&Recipient

* Remove throwIfCannotBeApplied super call

* Register throwIfCannotBeApplied action

* Replace publicKey with address

* Set wallet

* Use address

* Remove applyFeeToSender

* Verify nonce

* Verify balance

* Remove verifyTransactionFee

* Apply changes to sender

* Remove applyTransaction

* style: resolve style guide violations

* Move logs to transaction processor

* Fix deps

* style: resolve style guide violations

---------

Co-authored-by: sebastijankuzner <[email protected]>
  • Loading branch information
sebastijankuzner and sebastijankuzner authored Sep 30, 2024
1 parent eb4c80b commit e91b157
Show file tree
Hide file tree
Showing 22 changed files with 145 additions and 243 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -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<SenderMempool>;

addTransaction(transaction: Transaction): Promise<void>;
removeTransaction(senderPublicKey: string, id: string): Promise<Transaction[]>;
removeForgedTransaction(senderPublicKey: string, id: string): Promise<Transaction[]>;
removeTransaction(address: string, id: string): Promise<Transaction[]>;
removeForgedTransaction(address: string, id: string): Promise<Transaction[]>;

fixInvalidStates(): Promise<Transaction[]>;

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,4 +12,4 @@ export interface SenderMempool {
removeForgedTransaction(id: string): Transaction | undefined;
}

export type SenderMempoolFactory = (publicKey: string) => Promise<SenderMempool>;
export type SenderMempoolFactory = (address: string) => Promise<SenderMempool>;
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { Transaction } from "../crypto/transactions.js";

export interface SenderState {
configure(publicKey: string): Promise<SenderState>;
configure(address: string): Promise<SenderState>;
apply(transaction: Transaction): Promise<void>;
}
8 changes: 1 addition & 7 deletions packages/contracts/source/contracts/transactions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,22 +13,16 @@ export type TransactionHandlerContext = {

export interface TransactionApplyResult {
gasUsed: number;
receipt?: TransactionReceipt;
receipt: TransactionReceipt;
}

export interface TransactionHandler {
verify(transaction: Transaction): Promise<boolean>;

throwIfCannotBeApplied(transaction: Transaction, sender: Wallet): Promise<void>;

throwIfCannotEnterPool(transaction: Transaction): Promise<void>;

apply(context: TransactionHandlerContext, transaction: Transaction): Promise<TransactionApplyResult>;

applyToSender(context: TransactionHandlerContext, transaction: Transaction): Promise<TransactionApplyResult>;

applyToRecipient(context: TransactionHandlerContext, transaction: Transaction): Promise<TransactionApplyResult>;

emitEvents(transaction: Transaction): void;

verifySignatures(
Expand Down
5 changes: 2 additions & 3 deletions packages/contracts/source/exceptions/crypto.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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()}.`,
);
}
Expand Down
59 changes: 7 additions & 52 deletions packages/crypto-transaction-evm-call/source/handlers/evm-call.ts
Original file line number Diff line number Diff line change
@@ -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";
Expand All @@ -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;

Expand All @@ -32,26 +29,7 @@ export class EvmCallTransactionHandler extends Handlers.TransactionHandler {
return true;
}

public async throwIfCannotBeApplied(
transaction: Contracts.Crypto.Transaction,
wallet: Contracts.State.Wallet,
): Promise<void> {
return super.throwIfCannotBeApplied(transaction, wallet);
}

public async throwIfCannotEnterPool(transaction: Contracts.Crypto.Transaction): Promise<void> {}

public async applyToSender(
context: Contracts.Transactions.TransactionHandlerContext,
transaction: Contracts.Crypto.Transaction,
): Promise<Contracts.Transactions.TransactionApplyResult> {
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<Contracts.Transactions.TransactionApplyResult> {
Expand Down Expand Up @@ -80,41 +58,18 @@ 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) {
return this.app.terminate("invalid EVM call", error);
}
}

protected verifyTransactionFee(transaction: Contracts.Crypto.Transaction, sender: Contracts.State.Wallet): void {
Utils.assert.defined<Contracts.Crypto.EvmCallAsset>(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<T>(event: Contracts.Kernel.EventName, data?: T): Promise<void> {
if (this.state.isBootstrap()) {
return;
Expand Down
18 changes: 16 additions & 2 deletions packages/processor/source/transaction-processor.ts
Original file line number Diff line number Diff line change
@@ -1,13 +1,19 @@
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 {
@inject(Identifiers.Evm.Instance)
@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;

Expand Down Expand Up @@ -46,7 +52,15 @@ export class TransactionProcessor implements Contracts.Processor.TransactionProc
}

const result = await transactionHandler.apply(transactionHandlerContext, transaction);
AppUtils.assert.defined<string>(transaction.data.senderPublicKey);
Utils.assert.defined<Contracts.Evm.TransactionReceipt>(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 };
}
Expand Down
14 changes: 11 additions & 3 deletions packages/state/source/wallets/wallet.ts
Original file line number Diff line number Diff line change
@@ -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<Wallet> {
this.address = address;

const accountInfo = await this.evm.getAccountInfo(address);
this.balance = BigNumber.make(accountInfo.balance);
this.nonce = BigNumber.make(accountInfo.nonce);
return this;
}

Expand Down
1 change: 1 addition & 0 deletions packages/transaction-pool-service/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down

This file was deleted.

5 changes: 2 additions & 3 deletions packages/transaction-pool-service/source/actions/index.ts
Original file line number Diff line number Diff line change
@@ -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";
Original file line number Diff line number Diff line change
@@ -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<void> {
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);
}
}

This file was deleted.

Original file line number Diff line number Diff line change
@@ -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<boolean> {
const handler: Contracts.Transactions.TransactionHandler = arguments_.handler;
const transaction: Contracts.Crypto.Transaction = arguments_.transaction;
Expand Down
Loading

0 comments on commit e91b157

Please sign in to comment.