Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix(transaction-pool-service): add senders nonce and balance checks #716

Merged
merged 19 commits into from
Sep 30, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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
Loading