diff --git a/packages/constants/source/identifiers.ts b/packages/constants/source/identifiers.ts index be87f2ae7..041bfba63 100644 --- a/packages/constants/source/identifiers.ts +++ b/packages/constants/source/identifiers.ts @@ -280,10 +280,6 @@ export const Identifiers = { }, Transaction: { Handler: Symbol("Transaction"), - Validator: { - Factory: Symbol("Transaction"), - Instance: Symbol("Transaction"), - }, }, TransactionPool: { API: { diff --git a/packages/contracts/source/contracts/transactions.ts b/packages/contracts/source/contracts/transactions.ts index 906017769..b4c2dd3d4 100644 --- a/packages/contracts/source/contracts/transactions.ts +++ b/packages/contracts/source/contracts/transactions.ts @@ -1,5 +1,5 @@ import type { Transaction, TransactionData } from "./crypto/index.js"; -import type { BlockContext, CommitKey, Instance, TransactionReceipt } from "./evm/index.js"; +import type { BlockContext, Instance, TransactionReceipt } from "./evm/index.js"; import type { Wallet } from "./state/index.js"; export type TransactionHandlerConstructor = new () => TransactionHandler; @@ -36,17 +36,3 @@ export interface TransactionHandlerProvider { registerHandlers(): void; } - -export interface TransactionValidatorContext { - commitKey: CommitKey; - gasLimit: number; - timestamp: number; - generatorAddress: string; -} - -export interface TransactionValidator { - getEvm(): Instance; - validate(context: TransactionValidatorContext, transaction: Transaction): Promise; -} - -export type TransactionValidatorFactory = () => TransactionValidator; diff --git a/packages/forger/source/transaction-forger.ts b/packages/forger/source/transaction-forger.ts index f138c3931..f3a109f75 100644 --- a/packages/forger/source/transaction-forger.ts +++ b/packages/forger/source/transaction-forger.ts @@ -26,6 +26,10 @@ export class TransactionForger implements Contracts.Forger.TransactionForger { @inject(Identifiers.Cryptography.Configuration) private readonly cryptoConfiguration!: Contracts.Crypto.Configuration; + @inject(Identifiers.Evm.Instance) + @tagged("instance", "validator") + private readonly evm!: Contracts.Evm.Instance; + @inject(Identifiers.State.Store) protected readonly stateStore!: Contracts.State.Store; @@ -35,8 +39,8 @@ export class TransactionForger implements Contracts.Forger.TransactionForger { @inject(Identifiers.BlockchainUtils.RoundCalculator) private readonly roundCalculator!: Contracts.BlockchainUtils.RoundCalculator; - @inject(Identifiers.Transaction.Validator.Factory) - private readonly createTransactionValidator!: Contracts.Transactions.TransactionValidatorFactory; + @inject(Identifiers.Transaction.Handler) + private readonly transactionHandler!: Contracts.Transactions.TransactionHandler; @inject(Identifiers.Services.Log.Service) private readonly logger!: Contracts.Kernel.Logger; @@ -50,8 +54,6 @@ export class TransactionForger implements Contracts.Forger.TransactionForger { #generatorAddress!: string; #timestamp!: number; #commitKey!: Contracts.Evm.CommitKey; - #validator!: Contracts.Transactions.TransactionValidator; - #evm!: Contracts.Evm.Instance; #milestone!: Contracts.Crypto.Milestone; #previousBlock!: Contracts.Crypto.Block; #timeLimit!: number; @@ -67,9 +69,6 @@ export class TransactionForger implements Contracts.Forger.TransactionForger { this.#timestamp = timestamp; this.#commitKey = commitKey; - this.#validator = this.createTransactionValidator(); - this.#evm = this.#validator.getEvm(); - this.#milestone = this.cryptoConfiguration.getMilestone(); this.#previousBlock = this.stateStore.getLastBlock(); @@ -90,8 +89,8 @@ export class TransactionForger implements Contracts.Forger.TransactionForger { fee: bigint; }> { try { - await this.#evm.initializeGenesis(this.genesisInfo); - await this.#evm.prepareNextCommit({ commitKey: this.#commitKey }); + await this.evm.initializeGenesis(this.genesisInfo); + await this.evm.prepareNextCommit({ commitKey: this.#commitKey }); const { fee, gasUsed, transactions } = await this.#processTransactions(); @@ -101,12 +100,12 @@ export class TransactionForger implements Contracts.Forger.TransactionForger { return { fee, gasUsed, - logsBloom: await this.#evm.logsBloom(this.#commitKey), - stateRoot: await this.#evm.stateRoot(this.#commitKey, this.#previousBlock.stateRoot), + logsBloom: await this.evm.logsBloom(this.#commitKey), + stateRoot: await this.evm.stateRoot(this.#commitKey, this.#previousBlock.stateRoot), transactions, }; } finally { - await this.#evm.dispose(); + await this.evm.dispose(); } } @@ -157,7 +156,7 @@ export class TransactionForger implements Contracts.Forger.TransactionForger { `attempting optimistic execution of tx ${transaction.hash} (tx.gas=${transaction.gasLimit} gasLeft=${result.gasLeft})`, ); - await this.#evm.snapshot(this.#commitKey); + await this.evm.snapshot(this.#commitKey); } const validation = await this.#validateTransaction(transaction); @@ -171,7 +170,7 @@ export class TransactionForger implements Contracts.Forger.TransactionForger { ); if (optimisticExecution) { - await this.#evm.rollback(this.#commitKey); + await this.evm.rollback(this.#commitKey); return; } else { // In practice, this should never happen since the validator should reject transactions that exceed the block gas limit, but we check just in case. @@ -190,12 +189,17 @@ export class TransactionForger implements Contracts.Forger.TransactionForger { } async #validateTransaction(transaction: Contracts.Crypto.Transaction): Promise { - return this.#validator.validate( + return await this.transactionHandler.apply( { - commitKey: this.#commitKey, - gasLimit: this.#milestone.block.maxGasLimit, - generatorAddress: this.#generatorAddress, - timestamp: this.#timestamp, + evm: { + blockContext: { + commitKey: this.#commitKey, + gasLimit: BigInt(this.#milestone.block.maxGasLimit), + timestamp: BigInt(this.#timestamp), + validatorAddress: this.#generatorAddress, + }, + instance: this.evm, + }, }, transaction, ); @@ -209,7 +213,7 @@ export class TransactionForger implements Contracts.Forger.TransactionForger { } async #updateRewardsAndVotes(): Promise { - await this.#evm.updateRewardsAndVotes({ + await this.evm.updateRewardsAndVotes({ blockReward: BigInt(this.#milestone.reward), commitKey: this.#commitKey, specId: this.#milestone.evmSpec, @@ -222,7 +226,7 @@ export class TransactionForger implements Contracts.Forger.TransactionForger { if (this.roundCalculator.isNewRound(this.#previousBlock.number + 2)) { const { roundValidators } = this.cryptoConfiguration.getMilestone(this.#previousBlock.number + 2); - await this.#evm.calculateRoundValidators({ + await this.evm.calculateRoundValidators({ commitKey: this.#commitKey, roundValidators: BigInt(roundValidators), specId: this.#milestone.evmSpec, diff --git a/packages/forger/test/helpers/prepare-sandbox.ts b/packages/forger/test/helpers/prepare-sandbox.ts index 70950410a..8ba87c292 100644 --- a/packages/forger/test/helpers/prepare-sandbox.ts +++ b/packages/forger/test/helpers/prepare-sandbox.ts @@ -73,22 +73,22 @@ export const prepareSandbox = async (context: { app?: Application }): Promise [], }); - const validator = { - getEvm: () => ({ - dispose: async () => {}, - initializeGenesis: async () => {}, - logsBloom: async () => "0".repeat(512), - prepareNextCommit: async () => {}, - rollback: async () => {}, - snapshot: async () => {}, - stateRoot: async () => "0000000000000000000000000000000000000000000000000000000000000000", - updateRewardsAndVotes: async () => {}, - }), - validate: async () => true, + const evm = { + dispose: async () => {}, + initializeGenesis: async () => {}, + logsBloom: async () => "0".repeat(512), + prepareNextCommit: async () => {}, + rollback: async () => {}, + snapshot: async () => {}, + stateRoot: async () => "0000000000000000000000000000000000000000000000000000000000000000", + updateRewardsAndVotes: async () => {}, }; - context.app.rebind(Identifiers.Transaction.Validator.Factory).toConstantValue(() => validator); + context.app.bind(Identifiers.Evm.Instance).toConstantValue(evm).whenTagged("instance", "validator"); + + context.app.rebind(Identifiers.Transaction.Handler).toConstantValue({ + apply: async () => ({ gasRefunded: 0n, gasUsed: 0n, logs: [], status: 1 }), + }); - context.app.bind(Identifiers.Evm.Instance).toConstantValue(() => {}); context.app.bind(EvmConsensusIdentifiers.Internal.GenesisInfo).toConstantValue({}); context.app.bind(Identifiers.State.Store).toConstantValue({ diff --git a/packages/transactions/source/service-provider.ts b/packages/transactions/source/service-provider.ts index 4d540e331..874c45f10 100644 --- a/packages/transactions/source/service-provider.ts +++ b/packages/transactions/source/service-provider.ts @@ -1,25 +1,13 @@ -import type { Contracts } from "@mainsail/contracts"; - 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"; @injectable() export class ServiceProvider extends Providers.ServiceProvider { public async register(): Promise { - this.app.bind(Identifiers.Transaction.Validator.Instance).to(TransactionValidator); - this.app.bind(Identifiers.Transaction.Handler).to(TransactionHandler); - - this.app - .bind<() => TransactionValidator>(Identifiers.Transaction.Validator.Factory) - .toFactory( - (context: Contracts.Kernel.Container.ResolutionContext) => () => - context.get(Identifiers.Transaction.Validator.Instance), - ); } public async required(): Promise { diff --git a/packages/transactions/source/transaction-validator.test.ts b/packages/transactions/source/transaction-validator.test.ts deleted file mode 100644 index c0e1a5370..000000000 --- a/packages/transactions/source/transaction-validator.test.ts +++ /dev/null @@ -1,46 +0,0 @@ -// import { Utils } from "@mainsail/kernel"; -// import { AssertionError } from "assert"; -// import { SinonSpy } from "sinon"; - -// import { describeSkip } from "../../test-runner"; -// import { makeVoteTransactions } from "../test/make-vote-transactions"; -// import { setUp } from "../test/setup"; -// import { TransactionValidator } from "./transaction-validator"; - -// describeSkip<{ -// transactionValidator: TransactionValidator; -// applySpy: SinonSpy; -// }>("Transaction Validator", ({ it, beforeAll, afterEach, assert }) => { -// beforeAll(async (context) => { -// const environment = await setUp(); - -// context.transactionValidator = environment.transactionValidator; -// context.applySpy = environment.spies.applySpy; -// }); - -// afterEach((context) => { -// context.applySpy.resetHistory(); -// }); - -// it("should validate transactions", async (context) => { -// const transaction = makeVoteTransactions(1, [ -// `+${"03287bfebba4c7881a0509717e71b34b63f31e40021c321f89ae04f84be6d6ac37"}`, -// ]); - -// await context.transactionValidator.validate(transaction[0]); - -// assert.true(context.applySpy.calledWith(transaction[0])); -// }); - -// it("should throw when transaction id doesn't match deserialised", (context) => { -// const transaction = makeVoteTransactions(1, [ -// `+${"03287bfebba4c7881a0509717e71b34b63f31e40021c321f89ae04f84be6d6ac37"}`, -// ]); -// const copiedTransaction = Utils.cloneObject(transaction[0]) as any; -// copiedTransaction.id = "wrong"; - -// context.transactionValidator -// .validate(copiedTransaction) -// .catch((error) => assert.instance(error, AssertionError)); -// }); -// }); diff --git a/packages/transactions/source/transaction-validator.ts b/packages/transactions/source/transaction-validator.ts deleted file mode 100644 index 0451527f9..000000000 --- a/packages/transactions/source/transaction-validator.ts +++ /dev/null @@ -1,54 +0,0 @@ -import type { Contracts } from "@mainsail/contracts"; - -import { Identifiers } from "@mainsail/constants"; -import { inject, injectable, tagged } from "@mainsail/container"; -import { assert } from "@mainsail/utils"; -import { strictEqual } from "assert"; - -@injectable() -export class TransactionValidator implements Contracts.Transactions.TransactionValidator { - @inject(Identifiers.Evm.Instance) - @tagged("instance", "validator") - private readonly evm!: Contracts.Evm.Instance; - - @inject(Identifiers.Transaction.Handler) - private readonly transactionHandler!: Contracts.Transactions.TransactionHandler; - - @inject(Identifiers.Cryptography.Transaction.Factory) - private readonly transactionFactory!: Contracts.Crypto.TransactionFactory; - - public getEvm(): Contracts.Evm.Instance { - return this.evm; - } - - public async validate( - context: Contracts.Transactions.TransactionValidatorContext, - transaction: Contracts.Crypto.Transaction, - ): Promise { - const deserialized: Contracts.Crypto.Transaction = await this.transactionFactory.fromBytes( - transaction.serialized, - ); - strictEqual(transaction.hash, deserialized.hash); - - const { commitKey, gasLimit, generatorAddress, timestamp } = context; - - const receipt = await this.transactionHandler.apply( - { - evm: { - blockContext: { - commitKey, - gasLimit: BigInt(gasLimit), - timestamp: BigInt(timestamp), - validatorAddress: generatorAddress, - }, - instance: this.evm, - }, - }, - transaction, - ); - - assert.string(transaction.from); - - return receipt; - } -} diff --git a/packages/validator/test/helpers/prepare-sandbox.ts b/packages/validator/test/helpers/prepare-sandbox.ts index 952b90971..776eaec27 100644 --- a/packages/validator/test/helpers/prepare-sandbox.ts +++ b/packages/validator/test/helpers/prepare-sandbox.ts @@ -78,22 +78,22 @@ export const prepareSandbox = async (context: { app?: Application }): Promise ({ - dispose: async () => {}, - initializeGenesis: async () => {}, - logsBloom: async () => "0".repeat(512), - prepareNextCommit: async () => {}, - rollback: async () => {}, - snapshot: async () => {}, - stateRoot: async () => "0000000000000000000000000000000000000000000000000000000000000000", - updateRewardsAndVotes: async () => {}, - }), - validate: async () => true, + const evm = { + dispose: async () => {}, + initializeGenesis: async () => {}, + logsBloom: async () => "0".repeat(512), + prepareNextCommit: async () => {}, + rollback: async () => {}, + snapshot: async () => {}, + stateRoot: async () => "0000000000000000000000000000000000000000000000000000000000000000", + updateRewardsAndVotes: async () => {}, }; - context.app.rebind(Identifiers.Transaction.Validator.Factory).toConstantValue(() => validator); + context.app.bind(Identifiers.Evm.Instance).toConstantValue(evm).whenTagged("instance", "validator"); + + context.app.rebind(Identifiers.Transaction.Handler).toConstantValue({ + apply: async () => ({ gasRefunded: 0n, gasUsed: 0n, logs: [], status: 1 }), + }); - context.app.bind(Identifiers.Evm.Instance).toConstantValue(() => {}); context.app.bind(EvmConsensusIdentifiers.Internal.GenesisInfo).toConstantValue({}); context.app.bind(Identifiers.State.Store).toConstantValue({ diff --git a/tests/functional/consensus/source/custom-proposal.ts b/tests/functional/consensus/source/custom-proposal.ts index 42e6164f4..058f674d3 100644 --- a/tests/functional/consensus/source/custom-proposal.ts +++ b/tests/functional/consensus/source/custom-proposal.ts @@ -1,6 +1,7 @@ import type { Consensus } from "@mainsail/consensus/distribution/consensus.js"; -import { Identifiers } from "@mainsail/constants"; import type { Contracts } from "@mainsail/contracts"; + +import { Identifiers } from "@mainsail/constants"; import { Proposal } from "@mainsail/crypto-proposal"; import { assert } from "@mainsail/utils"; import { randomBytes } from "crypto"; @@ -29,10 +30,8 @@ export const makeCustomProposal = async ( const cryptoConfiguration = app.get(Identifiers.Cryptography.Configuration); const milestone = cryptoConfiguration.getMilestone(); - const transactionValidatorFactory = app.get( - Identifiers.Transaction.Validator.Factory, - ); - const transactionValidator = transactionValidatorFactory(); + const transactionHandler = app.get(Identifiers.Transaction.Handler); + const evm = app.getTagged(Identifiers.Evm.Instance, "instance", "validator"); // 2) const round = app.get(Identifiers.Consensus.Service).getRound(); @@ -65,12 +64,17 @@ export const makeCustomProposal = async ( let result = { gasRefunded: 0n, gasUsed: 0n, logs: [] as any, status: 0 }; try { - result = await transactionValidator.validate( + result = await transactionHandler.apply( { - commitKey, - gasLimit: milestone.block.maxGasLimit, - generatorAddress: validators[0].publicKey, - timestamp: dayjs().valueOf(), + evm: { + blockContext: { + commitKey, + gasLimit: BigInt(milestone.block.maxGasLimit), + timestamp: BigInt(dayjs().valueOf()), + validatorAddress: validators[0].publicKey, + }, + instance: evm, + }, }, transaction, ); @@ -95,7 +99,7 @@ export const makeCustomProposal = async ( payloadSize += transaction.serialized.byteLength + 2; } - await transactionValidator.getEvm().dispose(); + await evm.dispose(); const hashFactory = app.get(Identifiers.Cryptography.Hash.Factory); const blockFactory = app.get(Identifiers.Cryptography.Block.Factory); @@ -131,8 +135,8 @@ export const makeCustomProposal = async ( const serializedProposal = await messageSerializer.serializeProposalUnsigned({ payloadSerialized: proposedBytes.toString("hex"), round, - validRound: undefined, validatorIndex: 0, + validRound: undefined, }); const proposalSignature = await app