diff --git a/packages/api/src/graphql/modules/BlockResolver.ts b/packages/api/src/graphql/modules/BlockResolver.ts index 036184873..0acb876ca 100644 --- a/packages/api/src/graphql/modules/BlockResolver.ts +++ b/packages/api/src/graphql/modules/BlockResolver.ts @@ -4,7 +4,7 @@ import { Arg, Field, ObjectType, Query } from "type-graphql"; import { GraphqlModule, graphqlModule } from "../GraphqlModule"; -import { BatchTransactionModel } from "./model/BatchTransactionModel"; +import { TransactionExecutionResultModel } from "./model/TransactionExecutionResultModel"; @ObjectType() export class BlockModel { @@ -12,9 +12,9 @@ export class BlockModel { return new BlockModel( Number(block.networkState.during.block.height.toBigInt()), block.transactions.map((tx) => - BatchTransactionModel.fromServiceLayerModel({ + TransactionExecutionResultModel.fromServiceLayerModel({ tx: tx.tx, - status: tx.status.toBoolean(), + status: tx.status, statusMessage: tx.statusMessage, }) ), @@ -33,15 +33,15 @@ export class BlockModel { @Field() height: number; - @Field(() => [BatchTransactionModel]) - txs: BatchTransactionModel[]; + @Field(() => [TransactionExecutionResultModel]) + txs: TransactionExecutionResultModel[]; @Field() transactionsHash: string; private constructor( height: number, - txs: BatchTransactionModel[], + txs: TransactionExecutionResultModel[], transactionsHash: string, hash: string, previousBlockHash: string | undefined diff --git a/packages/api/src/graphql/modules/model/BatchTransactionModel.ts b/packages/api/src/graphql/modules/model/TransactionExecutionResultModel.ts similarity index 69% rename from packages/api/src/graphql/modules/model/BatchTransactionModel.ts rename to packages/api/src/graphql/modules/model/TransactionExecutionResultModel.ts index 316218778..0998e59c2 100644 --- a/packages/api/src/graphql/modules/model/BatchTransactionModel.ts +++ b/packages/api/src/graphql/modules/model/TransactionExecutionResultModel.ts @@ -1,16 +1,18 @@ import { ObjectType, Field } from "type-graphql"; -import { BatchTransaction } from "@proto-kit/sequencer"; import { IsBoolean } from "class-validator"; +import { TransactionExecutionResult } from "@proto-kit/sequencer"; import { TransactionObject } from "../MempoolResolver"; @ObjectType() -export class BatchTransactionModel { - public static fromServiceLayerModel(cbt: BatchTransaction) { +export class TransactionExecutionResultModel { + public static fromServiceLayerModel( + cbt: Pick + ) { const { tx, status, statusMessage } = cbt; - return new BatchTransactionModel( + return new TransactionExecutionResultModel( TransactionObject.fromServiceLayerModel(tx), - status, + status.toBoolean(), statusMessage ); } diff --git a/packages/persistance/prisma/migrations/20260122210623_transaction_input_paths/migration.sql b/packages/persistance/prisma/migrations/20260122210623_transaction_input_paths/migration.sql new file mode 100644 index 000000000..8f9d6cd06 --- /dev/null +++ b/packages/persistance/prisma/migrations/20260122210623_transaction_input_paths/migration.sql @@ -0,0 +1,10 @@ +-- CreateTable +CREATE TABLE "SkippedTransactionInputPaths" ( + "transactionHash" TEXT NOT NULL, + "paths" DECIMAL(78,0)[], + + CONSTRAINT "SkippedTransactionInputPaths_pkey" PRIMARY KEY ("transactionHash") +); + +-- AddForeignKey +ALTER TABLE "SkippedTransactionInputPaths" ADD CONSTRAINT "SkippedTransactionInputPaths_transactionHash_fkey" FOREIGN KEY ("transactionHash") REFERENCES "Transaction"("hash") ON DELETE RESTRICT ON UPDATE CASCADE; diff --git a/packages/persistance/prisma/schema.prisma b/packages/persistance/prisma/schema.prisma index a5abc4338..3613a00a6 100644 --- a/packages/persistance/prisma/schema.prisma +++ b/packages/persistance/prisma/schema.prisma @@ -56,6 +56,8 @@ model Transaction { IncomingMessageBatchTransaction IncomingMessageBatchTransaction[] priority TransactionPriority? + + inputPaths SkippedTransactionInputPaths? } model TransactionPriority { @@ -68,6 +70,14 @@ model TransactionPriority { @@id([transactionHash]) } +model SkippedTransactionInputPaths { + transactionHash String @id + + paths Decimal[] @db.Decimal(78, 0) + + transaction Transaction @relation(fields: [transactionHash], references: [hash]) +} + model TransactionExecutionResult { // TODO Make StateTransitionBatch and StateTransition Table stateTransitions Json @db.Json diff --git a/packages/persistance/src/services/prisma/PrismaTransactionStorage.ts b/packages/persistance/src/services/prisma/PrismaTransactionStorage.ts index 8f49f0384..5722421ed 100644 --- a/packages/persistance/src/services/prisma/PrismaTransactionStorage.ts +++ b/packages/persistance/src/services/prisma/PrismaTransactionStorage.ts @@ -9,6 +9,7 @@ import { import type { PrismaConnection } from "../../PrismaDatabaseConnection"; import { TransactionMapper } from "./mappers/TransactionMapper"; +import { Decimal } from "./PrismaStateService"; @injectable() export class PrismaTransactionStorage implements TransactionStorage { @@ -33,6 +34,9 @@ export class PrismaTransactionStorage implements TransactionStorage { isMessage: { equals: false, }, + inputPaths: { + is: null, + }, }, orderBy: { priority: { @@ -127,4 +131,29 @@ export class PrismaTransactionStorage implements TransactionStorage { batch, }; } + + public async reportSkippedTransactions( + paths: Record + ): Promise { + const { prismaClient } = this.connection; + + await prismaClient.skippedTransactionInputPaths.createMany({ + data: Object.entries(paths).map(([transactionHash, pathArray]) => ({ + transactionHash, + paths: pathArray.map((path) => new Decimal(path.toString())), + })), + }); + } + + public async reportChangedPaths(paths: bigint[]): Promise { + const { prismaClient } = this.connection; + + await prismaClient.skippedTransactionInputPaths.deleteMany({ + where: { + paths: { + hasSome: paths.map((path) => new Decimal(path.toString())), + }, + }, + }); + } } diff --git a/packages/protocol/src/hooks/AccountStateHook.ts b/packages/protocol/src/hooks/AccountStateHook.ts index cb5029d87..021b67999 100644 --- a/packages/protocol/src/hooks/AccountStateHook.ts +++ b/packages/protocol/src/hooks/AccountStateHook.ts @@ -14,8 +14,12 @@ export class AccountState extends Struct({ nonce: UInt64, }) {} +export type AccountStateHookConfig = { + maximumNonceLookahead?: number; +}; + @injectable() -export class AccountStateHook extends ProvableTransactionHook { +export class AccountStateHook extends ProvableTransactionHook { @state() public accountState = StateMap.from( PublicKey, AccountState @@ -67,6 +71,10 @@ export class AccountStateHook extends ProvableTransactionHook { const currentNonce = accountState.nonce; - return transaction.nonce.value.lessThan(currentNonce).toBoolean(); + const exceedsMaximumLookahead = transaction.nonce.value.greaterThan( + currentNonce.add(this.config.maximumNonceLookahead ?? 10) + ); + const nonceIsInPast = transaction.nonce.value.lessThan(currentNonce); + return nonceIsInPast.or(exceedsMaximumLookahead).toBoolean(); } } diff --git a/packages/sequencer/src/protocol/production/sequencing/BlockBuilder.ts b/packages/sequencer/src/protocol/production/sequencing/BlockBuilder.ts index b3f535452..6ebe0b43c 100644 --- a/packages/sequencer/src/protocol/production/sequencing/BlockBuilder.ts +++ b/packages/sequencer/src/protocol/production/sequencing/BlockBuilder.ts @@ -22,7 +22,7 @@ import { TransactionExecutionResultStatus, TransactionExecutionService, } from "./TransactionExecutionService"; -import { Ordering } from "./Ordering"; +import { Ordering, OrderingMetadata } from "./Ordering"; // TODO Allow user overriding of the blockbuilder @injectable() @@ -67,6 +67,7 @@ export class BlockBuilder { ): Promise<{ blockState: BlockTrackers; executionResults: TransactionExecutionResultStatus[]; + orderingMetadata: OrderingMetadata; }> { let blockState = state; const exceptionExecutionResults: TransactionExecutionResultStatus[] = []; @@ -139,11 +140,13 @@ export class BlockBuilder { } } - const orderingResults = ordering.getResults(); + const { results: orderingResults, orderingMetadata } = + ordering.getResults(); return { blockState, executionResults: orderingResults.concat(...exceptionExecutionResults), + orderingMetadata, }; } } diff --git a/packages/sequencer/src/protocol/production/sequencing/BlockProducerModule.ts b/packages/sequencer/src/protocol/production/sequencing/BlockProducerModule.ts index 5e441d6ae..278de217a 100644 --- a/packages/sequencer/src/protocol/production/sequencing/BlockProducerModule.ts +++ b/packages/sequencer/src/protocol/production/sequencing/BlockProducerModule.ts @@ -25,6 +25,7 @@ import { Database } from "../../../storage/Database"; import { Tracer } from "../../../logging/Tracer"; import { trace } from "../../../logging/trace"; import { AsyncLinkedLeafStore } from "../../../state/async/AsyncLinkedLeafStore"; +import { TransactionStorage } from "../../../storage/repositories/TransactionStorage"; import { ensureNotBusy } from "../../../helpers/BusyGuard"; import { BlockProductionService } from "./BlockProductionService"; @@ -45,6 +46,8 @@ export class BlockProducerModule extends SequencerModule { private readonly unprovenLinkedLeafStore: AsyncLinkedLeafStore, @inject("BlockQueue") private readonly blockQueue: BlockQueue, + @inject("TransactionStorage") + private readonly transactionStorage: TransactionStorage, @inject("BlockTreeStore") private readonly blockTreeStore: AsyncMerkleTreeStore, private readonly productionService: BlockProductionService, @@ -194,7 +197,7 @@ export class BlockProducerModule extends SequencerModule { ); if (blockResult !== undefined) { - const { block, stateChanges } = blockResult; + const { block, stateChanges, orderingMetadata } = blockResult; // Skip production if no transactions are available for now if (block.transactions.length === 0 && !this.allowEmptyBlock()) { @@ -218,6 +221,13 @@ export class BlockProducerModule extends SequencerModule { .filter((x) => x.type === "shouldRemove") .map((x) => x.hash) ); + + await this.transactionStorage.reportChangedPaths( + orderingMetadata.allChangedPaths + ); + await this.transactionStorage.reportSkippedTransactions( + orderingMetadata.skippedPaths + ); }); }, { diff --git a/packages/sequencer/src/protocol/production/sequencing/BlockProductionService.ts b/packages/sequencer/src/protocol/production/sequencing/BlockProductionService.ts index 852b5eb40..95ad398a4 100644 --- a/packages/sequencer/src/protocol/production/sequencing/BlockProductionService.ts +++ b/packages/sequencer/src/protocol/production/sequencing/BlockProductionService.ts @@ -35,6 +35,7 @@ import { TransactionExecutionResultStatus, } from "./TransactionExecutionService"; import { BlockBuilder } from "./BlockBuilder"; +import { OrderingMetadata } from "./Ordering"; function isIncludedTxs(x: TransactionExecutionResultStatus): x is { status: "included"; @@ -111,6 +112,7 @@ export class BlockProductionService { hash: string; type: "included" | "skipped" | "shouldRemove"; }[]; + orderingMetadata: OrderingMetadata; } | undefined > { @@ -142,13 +144,16 @@ export class BlockProductionService { UntypedStateTransition.fromStateTransition(transition) ); - const { blockState: newBlockState, executionResults } = - await this.blockBuilder.buildBlock( - stateService, - networkState, - blockState, - maximumBlockSize - ); + const { + blockState: newBlockState, + executionResults, + orderingMetadata, + } = await this.blockBuilder.buildBlock( + stateService, + networkState, + blockState, + maximumBlockSize + ); const previousBlockHash = lastResult.blockHash === 0n ? undefined : Field(lastResult.blockHash); @@ -206,6 +211,7 @@ export class BlockProductionService { }, stateChanges: stateService, includedTxs, + orderingMetadata, }; } } diff --git a/packages/sequencer/src/protocol/production/sequencing/Ordering.ts b/packages/sequencer/src/protocol/production/sequencing/Ordering.ts index f9b7f1e73..9cc8132ae 100644 --- a/packages/sequencer/src/protocol/production/sequencing/Ordering.ts +++ b/packages/sequencer/src/protocol/production/sequencing/Ordering.ts @@ -19,6 +19,61 @@ export type OrderingReport = { shouldRemove: boolean; }; +export type OrderingMetadata = { + skippedPaths: { [p: string]: bigint[] }; + allChangedPaths: bigint[]; +}; + +export class PathResolution { + failedTxIds = new Map(); + + paths = new Map(); + + public resolvePaths(paths: bigint[]) { + const allSymbols = paths.flatMap((key) => { + const symbols = this.paths.get(key); + if (symbols !== undefined) { + this.paths.delete(key); + } + return symbols ?? []; + }); + + return allSymbols + .map((symbol) => { + const tx = this.failedTxIds.get(symbol); + this.failedTxIds.delete(symbol); + return tx; + }) + .filter(filterNonUndefined); + } + + public pushPaths(object: Object, paths: bigint[]) { + const symbol = Symbol("tx"); + this.failedTxIds.set(symbol, object); + + paths.forEach((path) => { + const symbols = this.paths.get(path) ?? []; + symbols.push(symbol); + this.paths.set(path, symbols); + }); + } + + // TODO I hate how inefficient this function is - the tradeoff here is + // lookup performance during block production vs. after + public retrieveUnresolved(key: (o: Object) => string) { + const paths = new Map(); + for (const [path, txs] of this.paths.entries()) { + txs.forEach((tx) => { + const hash = key(this.failedTxIds.get(tx)!); + const thisPaths = paths.get(hash) ?? []; + thisPaths.push(path); + paths.set(hash, thisPaths); + }); + } + return Object.fromEntries(paths.entries()); + } +} + export class Ordering { public constructor( private readonly mempool: Mempool, @@ -36,42 +91,26 @@ export class Ordering { userTxOffset = 0; // For dependency resolution - failedTxIds = new Map(); + pathResolution = new PathResolution(); - paths = new Map(); + allChangedPaths = new Set(); public resolvePaths(result: TransactionExecutionResult) { - const keys = allKeys(result.stateTransitions[0].stateTransitions); - - const allSymbols = keys.flatMap((key) => { - const symbols = this.paths.get(key); - if (symbols !== undefined) { - this.paths.delete(key); - } - return symbols ?? []; - }); + const paths = allKeys( + result.stateTransitions.flatMap((x) => x.stateTransitions) + ); - const txs = allSymbols - .map((symbol) => { - const tx = this.failedTxIds.get(symbol); - this.failedTxIds.delete(symbol); - return tx; - }) - .filter(filterNonUndefined); + const txs = this.pathResolution.resolvePaths(paths); this.transactionQueue.push(...txs); + + paths.forEach((path) => this.allChangedPaths.add(path)); } private pushFailed(result: TransactionExecutionResult) { - const symbol = Symbol("tx"); - this.failedTxIds.set(symbol, result.tx); - const keys = allKeys(result.stateTransitions[0].stateTransitions); - keys.forEach((key) => { - const symbols = this.paths.get(key) ?? []; - symbols.push(symbol); - this.paths.set(key, symbols); - }); + + this.pathResolution.pushPaths(result.tx, keys); } public reportResult({ result, shouldRemove }: OrderingReport) { @@ -100,29 +139,41 @@ export class Ordering { } } + private space() { + return this.sizeLimit - this.ordered; + } + + private mandoQueue: PendingTransaction[] = []; + public async requestNextTransaction() { - if (this.transactionQueue.length === 0) { - // Fetch messages - if (!this.mandatoryTransactionsCompleted) { - const mandos = await this.mempool.getMandatoryTxs(); - this.transactionQueue.push(...mandos); - this.mandatoryTransactionsCompleted = true; - } + // Fetch messages + if (!this.mandatoryTransactionsCompleted) { + const mandos = await this.mempool.getMandatoryTxs(); + this.mandoQueue.push(...mandos); + this.mandatoryTransactionsCompleted = true; + } + + if (this.mandoQueue.length > 0) { + return this.mandoQueue.shift(); + } - // Fetch as much txs as space is available - const space = this.sizeLimit - this.ordered; - if (space > 0) { + const space = this.space(); + if (space > 0) { + if (this.transactionQueue.length === 0) { + // Fetch as many txs as space is availabe const newTxs = await this.mempool.getTxs(this.userTxOffset, space); this.userTxOffset += space; this.transactionQueue.push(...newTxs); } - } - return this.transactionQueue.shift(); + return this.transactionQueue.shift(); + } else { + return undefined; + } } public getResults() { - return this.results + const results = this.results .reverse() .filter( distinctByPredicate( @@ -130,5 +181,15 @@ export class Ordering { ) ) .reverse(); + + return { + results, + orderingMetadata: { + skippedPaths: this.pathResolution.retrieveUnresolved((tx) => + tx.hash().toString() + ), + allChangedPaths: Array.from(this.allChangedPaths), + }, + }; } } diff --git a/packages/sequencer/src/storage/adapters/PostgresStateModule.ts b/packages/sequencer/src/storage/adapters/PostgresStateModule.ts deleted file mode 100644 index 7e9c22e24..000000000 --- a/packages/sequencer/src/storage/adapters/PostgresStateModule.ts +++ /dev/null @@ -1,11 +0,0 @@ -import { - sequencerModule, - SequencerModule, -} from "../../sequencer/builder/SequencerModule"; - -@sequencerModule() -export class PostgresStateModule extends SequencerModule { - public async start(): Promise { - return undefined; - } -} diff --git a/packages/sequencer/src/storage/inmemory/InMemoryTransactionStorage.ts b/packages/sequencer/src/storage/inmemory/InMemoryTransactionStorage.ts index 90732770c..0d672d6bc 100644 --- a/packages/sequencer/src/storage/inmemory/InMemoryTransactionStorage.ts +++ b/packages/sequencer/src/storage/inmemory/InMemoryTransactionStorage.ts @@ -1,9 +1,11 @@ import { inject, injectable } from "tsyringe"; import { Field } from "o1js"; +import { splitArray } from "@proto-kit/common"; import { TransactionStorage } from "../repositories/TransactionStorage"; import { PendingTransaction } from "../../mempool/PendingTransaction"; import { BlockStorage } from "../repositories/BlockStorage"; +import { PathResolution } from "../../protocol/production/sequencing/Ordering"; import { InMemoryBatchStorage } from "./InMemoryBatchStorage"; @@ -56,7 +58,10 @@ export class InMemoryTransactionStorage implements TransactionStorage { this.sortQueue(); const from = offset ?? 0; - const to = limit !== undefined ? from + limit : undefined; + const to = + limit !== undefined + ? Math.min(from + limit, this.queue.length) + : undefined; return this.queue.slice(from, to).map(({ tx }) => tx); } @@ -131,4 +136,38 @@ export class InMemoryTransactionStorage implements TransactionStorage { } return undefined; } + + private pathResolution = new PathResolution(); + + private unresolvedSet: { tx: PendingTransaction; sortingValue: number }[] = + []; + + public async reportSkippedTransactions( + paths: Record + ): Promise { + Object.entries(paths).forEach(([txHash, transactionPaths]) => { + this.pathResolution.pushPaths(txHash, transactionPaths); + }); + + // Remove all unresolved txs from queue and append them to the unresolvedSet + const unresolvedHashes = Object.keys(paths); + const split = splitArray(this.queue, (x) => + unresolvedHashes.includes(x.tx.hash().toString()) ? "unresolved" : "queue" + ); + this.queue = split.queue ?? []; + this.unresolvedSet.push(...(split.unresolved ?? [])); + } + + public async reportChangedPaths(paths: bigint[]): Promise { + const resolved = this.pathResolution.resolvePaths(paths); + + // Move resolved from unresolvedSet to queue, then sort queue + const resolvedSplit = splitArray(this.unresolvedSet, (x) => + resolved.includes(x.tx.hash().toString()) ? "resolved" : "unresolved" + ); + this.queue.push(...(resolvedSplit.resolved ?? [])); + this.unresolvedSet = resolvedSplit.unresolved ?? []; + + this.sortQueue(); + } } diff --git a/packages/sequencer/src/storage/model/Batch.ts b/packages/sequencer/src/storage/model/Batch.ts index 731b14718..65e325a5b 100644 --- a/packages/sequencer/src/storage/model/Batch.ts +++ b/packages/sequencer/src/storage/model/Batch.ts @@ -1,14 +1,6 @@ import { JsonProof } from "o1js"; import { NetworkState } from "@proto-kit/protocol"; -import { PendingTransaction } from "../../mempool/PendingTransaction"; - -export interface BatchTransaction { - tx: PendingTransaction; - status: boolean; - statusMessage?: string; -} - export interface Batch { proof: JsonProof; blockHashes: string[]; diff --git a/packages/sequencer/src/storage/model/Block.ts b/packages/sequencer/src/storage/model/Block.ts index da2e44802..2a9ea57d5 100644 --- a/packages/sequencer/src/storage/model/Block.ts +++ b/packages/sequencer/src/storage/model/Block.ts @@ -85,11 +85,6 @@ export interface BlockWithMaybeResult { // eslint-disable-next-line @typescript-eslint/no-redeclare export const BlockWithResult = { - // toBlockProverState: ({ block, result }: BlockWithResult) => ({ - // stateRoot: result.stateRoot, - // - // } satisfies BlockProverStateCommitments), - createEmpty: () => ({ block: { diff --git a/packages/sequencer/src/storage/repositories/TransactionStorage.ts b/packages/sequencer/src/storage/repositories/TransactionStorage.ts index 0a8696bcd..9106b72d2 100644 --- a/packages/sequencer/src/storage/repositories/TransactionStorage.ts +++ b/packages/sequencer/src/storage/repositories/TransactionStorage.ts @@ -28,4 +28,13 @@ export interface TransactionStorage { } | undefined >; + + /** + * Mapping hash => path[] + */ + reportSkippedTransactions: (paths: Record) => Promise; + + reportChangedPaths: (paths: bigint[]) => Promise; + + // TODO Add a method to retrieve all conflict transactions and expose it through the APIs } diff --git a/packages/sequencer/test/integration/Block-order.test.ts b/packages/sequencer/test/integration/Block-order.test.ts index e13aaf566..6d7a1819f 100644 --- a/packages/sequencer/test/integration/Block-order.test.ts +++ b/packages/sequencer/test/integration/Block-order.test.ts @@ -4,7 +4,7 @@ import { Protocol } from "@proto-kit/protocol"; import { Bool, PrivateKey, UInt64 } from "o1js"; import "reflect-metadata"; import { container } from "tsyringe"; -import { afterEach } from "@jest/globals"; +import { afterEach, expect, jest } from "@jest/globals"; import { InMemoryDatabase, @@ -122,6 +122,8 @@ describe.each([["InMemory", InMemoryDatabase]])( afterEach(async () => { await appChain.close(); + + jest.restoreAllMocks(); }); it("transactions are returned in right order - simple", async () => { @@ -243,5 +245,52 @@ describe.each([["InMemory", InMemoryDatabase]])( expect(txs[5].nonce.toBigInt()).toStrictEqual(1n); expect(txs[5].sender).toStrictEqual(user3PublicKey); }); + + it("transactions are returned in right order in multiple distinct blocks - hardest", async () => { + expect.assertions(18); + + sequencer.resolve("BlockProducerModule").config.maximumBlockSize = 3; + const txStorage = sequencer.resolve("TransactionStorage"); + const getTxsSpy = jest.spyOn(txStorage, "getPendingUserTransactions"); + + await mempoolAddTransactions(user1PrivateKey, 0); + await mempoolAddTransactions(user1PrivateKey, 4); + await mempoolAddTransactions(user1PrivateKey, 5); + await mempoolAddTransactions(user2PrivateKey, 1); + await mempoolAddTransactions(user3PrivateKey, 1); + await mempoolAddTransactions(user2PrivateKey, 0); + await mempoolAddTransactions(user3PrivateKey, 0); + await mempoolAddTransactions(user1PrivateKey, 1); + + let block = await trigger.produceBlock(); + expectDefined(block); + + let txs = block.transactions.map((x) => x.tx); + expect(txs).toHaveLength(3); + + expect(txs[0].nonce.toBigInt()).toStrictEqual(0n); + expect(txs[0].sender).toStrictEqual(user1PublicKey); + expect(txs[1].nonce.toBigInt()).toStrictEqual(0n); + expect(txs[1].sender).toStrictEqual(user2PublicKey); + expect(txs[2].nonce.toBigInt()).toStrictEqual(0n); + expect(txs[2].sender).toStrictEqual(user3PublicKey); + + expect(getTxsSpy).toHaveBeenCalledTimes(3); + + block = await trigger.produceBlock(); + expectDefined(block); + + txs = block.transactions.map((x) => x.tx); + expect(txs).toHaveLength(3); + + expect(txs[0].nonce.toBigInt()).toStrictEqual(1n); + expect(txs[0].sender).toStrictEqual(user2PublicKey); + expect(txs[1].nonce.toBigInt()).toStrictEqual(1n); + expect(txs[1].sender).toStrictEqual(user3PublicKey); + expect(txs[2].nonce.toBigInt()).toStrictEqual(1n); + expect(txs[2].sender).toStrictEqual(user1PublicKey); + + expect(getTxsSpy).toHaveBeenCalledTimes(4); + }); } ); diff --git a/packages/sequencer/test/integration/MempoolTxRemoved.test.ts b/packages/sequencer/test/integration/MempoolTxRemoved.test.ts index 6d9c4829d..02fae71f5 100644 --- a/packages/sequencer/test/integration/MempoolTxRemoved.test.ts +++ b/packages/sequencer/test/integration/MempoolTxRemoved.test.ts @@ -110,7 +110,7 @@ describe("mempool removal mechanism", () => { expectDefined(block); expect(block.transactions).toHaveLength(1); - await expect(mempool.getTxs()).resolves.toHaveLength(1); + await expect(mempool.getTxs()).resolves.toHaveLength(0); }); it("check only one is included, other is removed", async () => {