From cba5f2f8beb3a4b724c9d4935cdfd4c03de7615c Mon Sep 17 00:00:00 2001 From: Raphael Panic Date: Tue, 28 Apr 2026 14:23:14 +0200 Subject: [PATCH 1/7] Implemented BatchMergingFlow --- .../production/flow/ReductionTaskFlow.ts | 34 +++++++---- .../src/settlement/tasks/BatchMergingFlow.ts | 57 +++++++++++++++++++ 2 files changed, 79 insertions(+), 12 deletions(-) create mode 100644 packages/sequencer/src/settlement/tasks/BatchMergingFlow.ts diff --git a/packages/sequencer/src/protocol/production/flow/ReductionTaskFlow.ts b/packages/sequencer/src/protocol/production/flow/ReductionTaskFlow.ts index e4d950ad5..d8155c5e4 100644 --- a/packages/sequencer/src/protocol/production/flow/ReductionTaskFlow.ts +++ b/packages/sequencer/src/protocol/production/flow/ReductionTaskFlow.ts @@ -38,9 +38,10 @@ export class ReductionTaskFlow { private readonly options: { name: string; inputLength: number; - mappingTask: Task; + mappingTask?: Task; reductionTask: Task, Output>; mergableFunction: (a: Output, b: Output) => boolean; + mappingFunction?: (input: Input) => Promise; }, flowCreator: FlowCreator ) { @@ -202,18 +203,27 @@ export class ReductionTaskFlow { }); } + private async handleMappingResult(result: Output) { + if (this.options.inputLength === 1) { + this.flow.resolve(result); + } else { + this.flow.state.queue.push(result); + await this.resolveReduction(); + } + } + public async pushInput(input: Input) { - await this.flow.pushTask( - this.options.mappingTask, - input, - async (result) => { - if (this.options.inputLength === 1) { - this.flow.resolve(result); - } else { - this.flow.state.queue.push(result); - await this.resolveReduction(); + if (this.options.mappingTask !== undefined) { + await this.flow.pushTask( + this.options.mappingTask, + input, + async (result) => { + await this.handleMappingResult(result); } - } - ); + ); + } else if (this.options.mappingFunction !== undefined) { + const result = await this.options.mappingFunction(input); + await this.handleMappingResult(result); + } } } diff --git a/packages/sequencer/src/settlement/tasks/BatchMergingFlow.ts b/packages/sequencer/src/settlement/tasks/BatchMergingFlow.ts new file mode 100644 index 000000000..6656741e4 --- /dev/null +++ b/packages/sequencer/src/settlement/tasks/BatchMergingFlow.ts @@ -0,0 +1,57 @@ +import { injectable } from "tsyringe"; +import { JsonProof } from "o1js"; +import { mapSequential } from "@proto-kit/common"; +import { BlockProof } from "@proto-kit/protocol"; + +import { Batch } from "../../storage/model/Batch"; +import { FlowCreator } from "../../worker/flow/Flow"; +import { BlockReductionTask } from "../../protocol/production/tasks/BlockReductionTask"; +import { BlockProofSerializer } from "../../protocol/production/tasks/serializers/BlockProofSerializer"; +import { ReductionTaskFlow } from "../../protocol/production/flow/ReductionTaskFlow"; + +@injectable() +export class BatchMergingFlow { + public constructor( + private readonly flowCreator: FlowCreator, + private readonly blockReductionTask: BlockReductionTask, + private readonly blockProofSerializer: BlockProofSerializer + ) {} + + public async mergeBatches(batches: Batch[]): Promise { + const serializer = this.blockProofSerializer.getBlockProofSerializer(); + + const flow = new ReductionTaskFlow( + { + name: `batch-merge-${batches[0].height}`, + inputLength: batches.length, + mappingFunction: async (input: JsonProof) => + await serializer.fromJSONProof(input), + reductionTask: this.blockReductionTask, + mergableFunction: (a, b) => { + return a.publicOutput.stateRoot + .equals(b.publicInput.stateRoot) + .toBoolean(); + }, + }, + this.flowCreator + ); + + const resultPromise = new Promise((res, rej) => { + flow.onCompletion(async (result) => res(result)); + }); + + await mapSequential( + batches, + async (batch) => await flow.pushInput(batch.proof) + ); + + const result = await resultPromise; + + return { + proof: await serializer.toJSONProof(result), + height: batches[0].height, + blockHashes: batches.flatMap((batch) => batch.blockHashes), + createdAt: Date.now(), + }; + } +} From 0b11f2594e47d0e3bd1478688fe81272ab6cf261 Mon Sep 17 00:00:00 2001 From: Raphael Panic Date: Tue, 5 May 2026 21:53:16 +0200 Subject: [PATCH 2/7] Implemented dynamic batch merging on settlement --- .../production/BatchProducerModule.ts | 4 +++ .../production/trigger/BlockTrigger.ts | 6 ++-- .../production/trigger/ManualBlockTrigger.ts | 7 ++-- .../production/trigger/TimedBlockTrigger.ts | 32 +++++++++++-------- .../src/settlement/SettlementModule.ts | 18 +++++++++-- .../src/settlement/tasks/BatchMergingFlow.ts | 2 ++ .../storage/inmemory/InMemoryBatchStorage.ts | 18 +++++++++++ packages/sequencer/src/storage/model/Batch.ts | 5 ++- .../src/storage/repositories/BatchStorage.ts | 1 + 9 files changed, 69 insertions(+), 24 deletions(-) diff --git a/packages/sequencer/src/protocol/production/BatchProducerModule.ts b/packages/sequencer/src/protocol/production/BatchProducerModule.ts index bdfced5ad..d730fe1d4 100644 --- a/packages/sequencer/src/protocol/production/BatchProducerModule.ts +++ b/packages/sequencer/src/protocol/production/BatchProducerModule.ts @@ -97,6 +97,10 @@ export class BatchProducerModule extends SequencerModule { return batchWithStateDiff?.batch; } + public async getSettleableBatches() { + return await this.batchStorage.getUnsettledBatches(); + } + public async start(): Promise { noop(); } diff --git a/packages/sequencer/src/protocol/production/trigger/BlockTrigger.ts b/packages/sequencer/src/protocol/production/trigger/BlockTrigger.ts index c35fb539f..5be979bc4 100644 --- a/packages/sequencer/src/protocol/production/trigger/BlockTrigger.ts +++ b/packages/sequencer/src/protocol/production/trigger/BlockTrigger.ts @@ -93,7 +93,7 @@ export class BlockTriggerBase< } protected async settle( - batch: SettleableBatch, + batches: SettleableBatch[], config: SettlementTokenConfig // nonce?: number ) { @@ -103,10 +103,10 @@ export class BlockTriggerBase< ); return undefined; } - const settlement = await this.settlementModule.settleBatch(batch); + const settlement = await this.settlementModule.settleBatch(batches); const txs = await this.bridgingModule?.sendRollupTransactions( - [batch], + batches, config // TODO nonce override ); diff --git a/packages/sequencer/src/protocol/production/trigger/ManualBlockTrigger.ts b/packages/sequencer/src/protocol/production/trigger/ManualBlockTrigger.ts index e4512354d..72bace2b7 100644 --- a/packages/sequencer/src/protocol/production/trigger/ManualBlockTrigger.ts +++ b/packages/sequencer/src/protocol/production/trigger/ManualBlockTrigger.ts @@ -55,8 +55,11 @@ export class ManualBlockTrigger return await super.produceBatch(); } - public async settle(batch: SettleableBatch, config: SettlementTokenConfig) { - return await super.settle(batch, config); + public async settle( + batches: SettleableBatch[], + config: SettlementTokenConfig + ) { + return await super.settle(batches, config); } public async produceBlock(): Promise { diff --git a/packages/sequencer/src/protocol/production/trigger/TimedBlockTrigger.ts b/packages/sequencer/src/protocol/production/trigger/TimedBlockTrigger.ts index a821b2f06..ee8897927 100644 --- a/packages/sequencer/src/protocol/production/trigger/TimedBlockTrigger.ts +++ b/packages/sequencer/src/protocol/production/trigger/TimedBlockTrigger.ts @@ -15,6 +15,7 @@ import { ensureNotBusy } from "../../../helpers/BusyGuard"; import { SequencerStartupModule } from "../../../sequencer/SequencerStartupModule"; import { BlockProductionInstrumentation } from "../../../metrics/BlockProductionInstrumentation"; import { SequencerCoreModule } from "../../../sequencer/SequencerCoreModule"; +import { Batch } from "../../../storage/model/Batch"; import { BlockTriggerBase } from "./BlockTrigger"; @@ -35,6 +36,8 @@ export class TimedBlockTrigger { private intervals: NodeJS.Timeout[] = []; + private isFirstSettlement = true; + public constructor( @inject("BatchProducerModule", { isOptional: true }) batchProducerModule: BatchProducerModule | undefined, @@ -87,23 +90,15 @@ export class TimedBlockTrigger } const blockIntervalId = setInterval(async () => { - try { - // Trigger unproven blocks - await this.produceUnprovenBlock(); - } catch (error) { - log.error(error); - } + // Trigger unproven blocks + await this.produceUnprovenBlock(); }, blockInterval); this.intervals.push(blockIntervalId); if (settlementInterval !== undefined) { const settlementIntervalId = setInterval(async () => { - try { - // Trigger settlement - await this.tryProduceSettlement(); - } catch (error) { - log.error(error); - } + // Trigger settlement + await this.tryProduceSettlement(); }, settlementInterval); this.intervals.push(settlementIntervalId); } @@ -126,8 +121,17 @@ export class TimedBlockTrigger @ensureNotBusy() private async tryProduceSettlement(): Promise { const batch = await this.produceBatch(); - if (batch !== undefined) { - await this.settle(batch, this.config.settlementTokenConfig); + + let batches: Batch[] | undefined = undefined; + if (this.isFirstSettlement) { + batches = await this.batchProducerModule?.getSettleableBatches(); + this.isFirstSettlement = false; + } else if (batch !== undefined) { + batches = [batch]; + } + + if (batches !== undefined) { + await this.settle(batches, this.config.settlementTokenConfig); } } diff --git a/packages/sequencer/src/settlement/SettlementModule.ts b/packages/sequencer/src/settlement/SettlementModule.ts index dc3b9d375..5d949a2ba 100644 --- a/packages/sequencer/src/settlement/SettlementModule.ts +++ b/packages/sequencer/src/settlement/SettlementModule.ts @@ -40,6 +40,7 @@ import { AddressRegistry, InMemoryAddressRegistry, } from "./interactions/AddressRegistry"; +import { BatchMergingFlow } from "./tasks/BatchMergingFlow"; export type SettlementModuleConfig = { addresses?: { @@ -74,7 +75,8 @@ export class SettlementModule private readonly parentContainer: ModuleContainerLike, @inject("AddressRegistry") private readonly addressRegistry: AddressRegistry, - private readonly argsRegistry: ContractArgsRegistry + private readonly argsRegistry: ContractArgsRegistry, + private readonly batchMergingFlow: BatchMergingFlow ) { super(); this.utils = new SettlementUtils(this.baseLayer, this.signer); @@ -146,11 +148,23 @@ export class SettlementModule } public async settleBatch( - batch: SettleableBatch, + batches: SettleableBatch[], options: { nonce?: number; } = {} ): Promise { + let batch: SettleableBatch; + + if (batches.length === 0) { + throw new Error("No batches given for settlement"); + } else if (batches.length === 1) { + [batch] = batches; + } else if (batches.length > 1) { + log.info(`Merging ${batches.length} batch proofs`); + + batch = await this.batchMergingFlow.mergeBatches(batches); + } + log.debug("Preparing settlement"); const bridgingModule = this.bridgingModule(); diff --git a/packages/sequencer/src/settlement/tasks/BatchMergingFlow.ts b/packages/sequencer/src/settlement/tasks/BatchMergingFlow.ts index 6656741e4..4e8d7b1f5 100644 --- a/packages/sequencer/src/settlement/tasks/BatchMergingFlow.ts +++ b/packages/sequencer/src/settlement/tasks/BatchMergingFlow.ts @@ -52,6 +52,8 @@ export class BatchMergingFlow { height: batches[0].height, blockHashes: batches.flatMap((batch) => batch.blockHashes), createdAt: Date.now(), + fromNetworkState: batches[0].fromNetworkState, + toNetworkState: batches.at(-1)!.toNetworkState, }; } } diff --git a/packages/sequencer/src/storage/inmemory/InMemoryBatchStorage.ts b/packages/sequencer/src/storage/inmemory/InMemoryBatchStorage.ts index dfe21fddf..3338cf323 100644 --- a/packages/sequencer/src/storage/inmemory/InMemoryBatchStorage.ts +++ b/packages/sequencer/src/storage/inmemory/InMemoryBatchStorage.ts @@ -1,9 +1,17 @@ import { log } from "@proto-kit/common"; +import { inject, injectable } from "tsyringe"; import { BatchStorage } from "../repositories/BatchStorage"; import { Batch } from "../model/Batch"; +import { SettlementStorage } from "../repositories/SettlementStorage"; +@injectable() export class InMemoryBatchStorage implements BatchStorage { + public constructor( + @inject("SettlementStorage") + private readonly settlementStorage: SettlementStorage + ) {} + private readonly batches: Batch[] = []; public async getCurrentBatchHeight(): Promise { @@ -22,4 +30,14 @@ export class InMemoryBatchStorage implements BatchStorage { public async getLatestBatch(): Promise { return this.batches.at(-1); } + + public async getUnsettledBatches(): Promise { + const latestSettlement = await this.settlementStorage.getLatestSettlement(); + if (latestSettlement !== undefined) { + const highestBatchHeight = Math.max(...latestSettlement.batches); + return this.batches.filter((batch) => batch.height > highestBatchHeight); + } else { + return this.batches; + } + } } diff --git a/packages/sequencer/src/storage/model/Batch.ts b/packages/sequencer/src/storage/model/Batch.ts index aff22dee2..c777ded39 100644 --- a/packages/sequencer/src/storage/model/Batch.ts +++ b/packages/sequencer/src/storage/model/Batch.ts @@ -6,9 +6,8 @@ export interface Batch { blockHashes: string[]; height: number; createdAt: number; -} - -export interface SettleableBatch extends Batch { fromNetworkState: NetworkState; toNetworkState: NetworkState; } + +export interface SettleableBatch extends Batch {} diff --git a/packages/sequencer/src/storage/repositories/BatchStorage.ts b/packages/sequencer/src/storage/repositories/BatchStorage.ts index 7dc47e4b9..0b1de0504 100644 --- a/packages/sequencer/src/storage/repositories/BatchStorage.ts +++ b/packages/sequencer/src/storage/repositories/BatchStorage.ts @@ -5,4 +5,5 @@ export interface BatchStorage { getLatestBatch: () => Promise; pushBatch: (block: Batch) => Promise; getBatchAt: (height: number) => Promise; + getUnsettledBatches: () => Promise; } From 66c885531dce257b47927270158a6bf4a07e6fbd Mon Sep 17 00:00:00 2001 From: Raphael Panic Date: Wed, 6 May 2026 13:48:29 +0200 Subject: [PATCH 3/7] Added settleable batch recovery for batch merging --- .../src/services/prisma/PrismaBatchStore.ts | 27 ++++++++++++++----- .../production/BatchProducerModule.ts | 24 ++++++++++++++++- .../production/trigger/TimedBlockTrigger.ts | 15 ++++++++--- .../src/settlement/tasks/BatchMergingFlow.ts | 6 +++-- packages/sequencer/src/storage/model/Batch.ts | 5 ++-- 5 files changed, 62 insertions(+), 15 deletions(-) diff --git a/packages/persistance/src/services/prisma/PrismaBatchStore.ts b/packages/persistance/src/services/prisma/PrismaBatchStore.ts index 51de78d9f..75a51936b 100644 --- a/packages/persistance/src/services/prisma/PrismaBatchStore.ts +++ b/packages/persistance/src/services/prisma/PrismaBatchStore.ts @@ -71,11 +71,7 @@ export class PrismaBatchStore implements BatchStorage { height: Prisma.SortOrder.desc, }, include: { - blocks: { - select: { - hash: true, - }, - }, + blocks: {}, }, take: 1, }); @@ -84,7 +80,26 @@ export class PrismaBatchStore implements BatchStorage { } return this.batchMapper.mapIn([ batch, - batch.blocks.map((block) => block.hash), + batch.blocks.map(({ hash }) => hash), ]); } + + public async getUnsettledBatches(): Promise { + const batches = await this.connection.prismaClient.batch.findMany({ + include: { + blocks: { + select: { + hash: true, + }, + }, + }, + where: { + settlement: null, + }, + }); + + return batches.map((batch) => + this.batchMapper.mapIn([batch, batch.blocks.map(({ hash }) => hash)]) + ); + } } diff --git a/packages/sequencer/src/protocol/production/BatchProducerModule.ts b/packages/sequencer/src/protocol/production/BatchProducerModule.ts index d730fe1d4..abf1f0f6d 100644 --- a/packages/sequencer/src/protocol/production/BatchProducerModule.ts +++ b/packages/sequencer/src/protocol/production/BatchProducerModule.ts @@ -12,13 +12,14 @@ import { SequencerModule, } from "../../sequencer/builder/SequencerModule"; import { BatchStorage } from "../../storage/repositories/BatchStorage"; -import { SettleableBatch } from "../../storage/model/Batch"; +import { Batch, SettleableBatch } from "../../storage/model/Batch"; import { BlockWithResult } from "../../storage/model/Block"; import type { Database } from "../../storage/Database"; import { AsyncLinkedLeafStore } from "../../state/async/AsyncLinkedLeafStore"; import { CachedLinkedLeafStore } from "../../state/lmt/CachedLinkedLeafStore"; import { ensureNotBusy } from "../../helpers/BusyGuard"; import { AsyncMerkleTreeStore } from "../../state/async/AsyncMerkleTreeStore"; +import { BlockStorage } from "../../storage/repositories/BlockStorage"; import { BlockProofSerializer } from "./tasks/serializers/BlockProofSerializer"; import { BatchTracingService } from "./tracing/BatchTracingService"; @@ -52,6 +53,7 @@ export class BatchProducerModule extends SequencerModule { @inject("AsyncTreeStore") private readonly merkleStore: AsyncMerkleTreeStore, @inject("BatchStorage") private readonly batchStorage: BatchStorage, + @inject("BlockStorage") private readonly blockStorage: BlockStorage, @inject("Database") private readonly database: Database, private readonly batchFlow: BatchFlow, @@ -101,6 +103,26 @@ export class BatchProducerModule extends SequencerModule { return await this.batchStorage.getUnsettledBatches(); } + public async recoverSettleableBatch(batch: Batch) { + const firstBlock = await this.blockStorage.getBlock(batch.blockHashes[0]); + const lastBlock = await this.blockStorage.getBlock( + batch.blockHashes.at(-1)! + ); + + if (firstBlock === undefined || lastBlock === undefined) { + throw new Error("First or last block not found"); + } + const lastBlockResult = await this.blockStorage.getBlockWithResultAt( + parseInt(lastBlock.height.toString(), 10) + ); + + return { + ...batch, + fromNetworkState: firstBlock.networkState.before, + toNetworkState: lastBlockResult!.result.afterNetworkState, + }; + } + public async start(): Promise { noop(); } diff --git a/packages/sequencer/src/protocol/production/trigger/TimedBlockTrigger.ts b/packages/sequencer/src/protocol/production/trigger/TimedBlockTrigger.ts index ee8897927..f8b33a3ce 100644 --- a/packages/sequencer/src/protocol/production/trigger/TimedBlockTrigger.ts +++ b/packages/sequencer/src/protocol/production/trigger/TimedBlockTrigger.ts @@ -1,5 +1,5 @@ import { inject, injectable } from "tsyringe"; -import { dependencyFactory, log } from "@proto-kit/common"; +import { dependencyFactory, log, mapSequential } from "@proto-kit/common"; import { closeable, Closeable } from "../../../sequencer/builder/Closeable"; import { BatchProducerModule } from "../BatchProducerModule"; @@ -15,7 +15,7 @@ import { ensureNotBusy } from "../../../helpers/BusyGuard"; import { SequencerStartupModule } from "../../../sequencer/SequencerStartupModule"; import { BlockProductionInstrumentation } from "../../../metrics/BlockProductionInstrumentation"; import { SequencerCoreModule } from "../../../sequencer/SequencerCoreModule"; -import { Batch } from "../../../storage/model/Batch"; +import { SettleableBatch } from "../../../storage/model/Batch"; import { BlockTriggerBase } from "./BlockTrigger"; @@ -122,9 +122,16 @@ export class TimedBlockTrigger private async tryProduceSettlement(): Promise { const batch = await this.produceBatch(); - let batches: Batch[] | undefined = undefined; + let batches: SettleableBatch[] | undefined = undefined; if (this.isFirstSettlement) { - batches = await this.batchProducerModule?.getSettleableBatches(); + const rawBatches = await this.batchProducerModule?.getSettleableBatches(); + if (rawBatches !== undefined) { + batches = await mapSequential( + rawBatches, + async (rawBatch) => + await this.batchProducerModule!.recoverSettleableBatch(rawBatch) + ); + } this.isFirstSettlement = false; } else if (batch !== undefined) { batches = [batch]; diff --git a/packages/sequencer/src/settlement/tasks/BatchMergingFlow.ts b/packages/sequencer/src/settlement/tasks/BatchMergingFlow.ts index 4e8d7b1f5..7b9d43533 100644 --- a/packages/sequencer/src/settlement/tasks/BatchMergingFlow.ts +++ b/packages/sequencer/src/settlement/tasks/BatchMergingFlow.ts @@ -3,7 +3,7 @@ import { JsonProof } from "o1js"; import { mapSequential } from "@proto-kit/common"; import { BlockProof } from "@proto-kit/protocol"; -import { Batch } from "../../storage/model/Batch"; +import { SettleableBatch } from "../../storage/model/Batch"; import { FlowCreator } from "../../worker/flow/Flow"; import { BlockReductionTask } from "../../protocol/production/tasks/BlockReductionTask"; import { BlockProofSerializer } from "../../protocol/production/tasks/serializers/BlockProofSerializer"; @@ -17,7 +17,9 @@ export class BatchMergingFlow { private readonly blockProofSerializer: BlockProofSerializer ) {} - public async mergeBatches(batches: Batch[]): Promise { + public async mergeBatches( + batches: SettleableBatch[] + ): Promise { const serializer = this.blockProofSerializer.getBlockProofSerializer(); const flow = new ReductionTaskFlow( diff --git a/packages/sequencer/src/storage/model/Batch.ts b/packages/sequencer/src/storage/model/Batch.ts index c777ded39..aff22dee2 100644 --- a/packages/sequencer/src/storage/model/Batch.ts +++ b/packages/sequencer/src/storage/model/Batch.ts @@ -6,8 +6,9 @@ export interface Batch { blockHashes: string[]; height: number; createdAt: number; +} + +export interface SettleableBatch extends Batch { fromNetworkState: NetworkState; toNetworkState: NetworkState; } - -export interface SettleableBatch extends Batch {} From e65bff9a733f0667e585f24ee24020bc7e82e682 Mon Sep 17 00:00:00 2001 From: Raphael Panic Date: Wed, 6 May 2026 16:37:21 +0200 Subject: [PATCH 4/7] Made ManualBlockTrigger interface accept single batches --- .../protocol/production/trigger/ManualBlockTrigger.ts | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/packages/sequencer/src/protocol/production/trigger/ManualBlockTrigger.ts b/packages/sequencer/src/protocol/production/trigger/ManualBlockTrigger.ts index 72bace2b7..4c82e71dc 100644 --- a/packages/sequencer/src/protocol/production/trigger/ManualBlockTrigger.ts +++ b/packages/sequencer/src/protocol/production/trigger/ManualBlockTrigger.ts @@ -13,6 +13,7 @@ import { } from "../../../settlement/BridgingModule"; import { BlockTrigger, BlockTriggerBase } from "./BlockTrigger"; +import { match, P } from "ts-pattern"; @sequencerModule() export class ManualBlockTrigger @@ -56,10 +57,16 @@ export class ManualBlockTrigger } public async settle( - batches: SettleableBatch[], + batches: SettleableBatch | SettleableBatch[], config: SettlementTokenConfig ) { - return await super.settle(batches, config); + let batchArray: SettleableBatch[]; + if (Array.isArray(batches)) { + batchArray = batches; + } else { + batchArray = [batches]; + } + return await super.settle(batchArray, config); } public async produceBlock(): Promise { From 306f8fa5a0dae194e4d57c2a036317cc4ace79a7 Mon Sep 17 00:00:00 2001 From: Raphael Panic Date: Wed, 6 May 2026 16:45:43 +0200 Subject: [PATCH 5/7] Added PropertyStorage --- .../20260506144134_add_property/migration.sql | 7 +++++ packages/persistance/prisma/schema.prisma | 15 ++++++---- .../src/PrismaDatabaseConnection.ts | 4 +++ packages/persistance/src/index.ts | 1 + .../services/prisma/PrismaPropertyStorage.ts | 29 +++++++++++++++++++ packages/sequencer/src/index.ts | 2 ++ .../src/storage/StorageDependencyFactory.ts | 3 ++ .../src/storage/inmemory/InMemoryDatabase.ts | 4 +++ .../inmemory/InMemoryPropertyStorage.ts | 13 +++++++++ 9 files changed, 73 insertions(+), 5 deletions(-) create mode 100644 packages/persistance/prisma/migrations/20260506144134_add_property/migration.sql create mode 100644 packages/persistance/src/services/prisma/PrismaPropertyStorage.ts create mode 100644 packages/sequencer/src/storage/inmemory/InMemoryPropertyStorage.ts diff --git a/packages/persistance/prisma/migrations/20260506144134_add_property/migration.sql b/packages/persistance/prisma/migrations/20260506144134_add_property/migration.sql new file mode 100644 index 000000000..d6f1b1522 --- /dev/null +++ b/packages/persistance/prisma/migrations/20260506144134_add_property/migration.sql @@ -0,0 +1,7 @@ +-- CreateTable +CREATE TABLE "Property" ( + "key" TEXT NOT NULL, + "value" TEXT NOT NULL, + + CONSTRAINT "Property_pkey" PRIMARY KEY ("key") +); diff --git a/packages/persistance/prisma/schema.prisma b/packages/persistance/prisma/schema.prisma index 8ad2d0a1b..5a1de69c0 100644 --- a/packages/persistance/prisma/schema.prisma +++ b/packages/persistance/prisma/schema.prisma @@ -107,8 +107,8 @@ model Block { toMessagesHash String fromStateRoot String - beforeBlockStateTransitions Json @db.Json - createdAt DateTime @default(now()) + beforeBlockStateTransitions Json @db.Json + createdAt DateTime @default(now()) parentHash String? @unique parent Block? @relation("Parent", fields: [parentHash], references: [hash]) @@ -124,7 +124,7 @@ model Block { model Batch { height Int @id - proof Json @db.Json + proof Json @db.Json createdAt DateTime @default(now()) blocks Block[] @@ -148,9 +148,9 @@ model BlockResult { model Settlement { // transaction String - transactionHash String @id + transactionHash String @id promisedMessagesHash String - createdAt DateTime @default(now()) + createdAt DateTime @default(now()) batches Batch[] } @@ -173,3 +173,8 @@ model IncomingMessageBatch { messages IncomingMessageBatchTransaction[] } + +model Property { + key String @id + value String +} diff --git a/packages/persistance/src/PrismaDatabaseConnection.ts b/packages/persistance/src/PrismaDatabaseConnection.ts index c59585419..0bd8c0564 100644 --- a/packages/persistance/src/PrismaDatabaseConnection.ts +++ b/packages/persistance/src/PrismaDatabaseConnection.ts @@ -14,6 +14,7 @@ import { PrismaSettlementStorage } from "./services/prisma/PrismaSettlementStora import { PrismaMessageStorage } from "./services/prisma/PrismaMessageStorage"; import { PrismaTransactionStorage } from "./services/prisma/PrismaTransactionStorage"; import { PrismaLinkedLeafStore } from "./services/prisma/PrismaLinkedLeafStore"; +import { PrismaPropertyStorage } from "./services/prisma/PrismaPropertyStorage"; export interface PrismaDatabaseConfig { // Either object-based config or connection string @@ -95,6 +96,9 @@ export class PrismaDatabaseConnection transactionStorage: { useClass: PrismaTransactionStorage, }, + propertyStorage: { + useClass: PrismaPropertyStorage, + }, asyncLinkedLeafStore: { useGenerated: (module) => { diff --git a/packages/persistance/src/index.ts b/packages/persistance/src/index.ts index 788de3d57..e67cc278b 100644 --- a/packages/persistance/src/index.ts +++ b/packages/persistance/src/index.ts @@ -7,6 +7,7 @@ export * from "./services/prisma/PrismaBatchStore"; export * from "./services/prisma/PrismaSettlementStorage"; export * from "./services/prisma/PrismaMessageStorage"; export * from "./services/prisma/PrismaTransactionStorage"; +export * from "./services/prisma/PrismaPropertyStorage"; export * from "./services/prisma/mappers/BatchMapper"; export * from "./services/prisma/mappers/BlockMapper"; export * from "./services/prisma/mappers/FieldMapper"; diff --git a/packages/persistance/src/services/prisma/PrismaPropertyStorage.ts b/packages/persistance/src/services/prisma/PrismaPropertyStorage.ts new file mode 100644 index 000000000..95bc8fc89 --- /dev/null +++ b/packages/persistance/src/services/prisma/PrismaPropertyStorage.ts @@ -0,0 +1,29 @@ +import { PropertyStorage } from "@proto-kit/sequencer"; +import { inject } from "tsyringe"; + +import { PrismaConnection } from "../../PrismaDatabaseConnection"; + +export class PrismaPropertyStorage implements PropertyStorage { + public constructor( + @inject("Database") private readonly connection: PrismaConnection + ) {} + + public async get(key: string): Promise { + const record = await this.connection.prismaClient.property.findFirst({ + where: { + key, + }, + }); + + return record?.value; + } + + public async set(key: string, value: string): Promise { + await this.connection.prismaClient.property.create({ + data: { + key, + value, + }, + }); + } +} diff --git a/packages/sequencer/src/index.ts b/packages/sequencer/src/index.ts index 77fee6d6a..aef80b004 100644 --- a/packages/sequencer/src/index.ts +++ b/packages/sequencer/src/index.ts @@ -84,6 +84,7 @@ export * from "./storage/repositories/BlockStorage"; export * from "./storage/repositories/SettlementStorage"; export * from "./storage/repositories/MessageStorage"; export * from "./storage/repositories/TransactionStorage"; +export * from "./storage/repositories/PropertyStorage"; export * from "./storage/inmemory/InMemoryDatabase"; export * from "./storage/inmemory/InMemoryAsyncMerkleTreeStore"; export * from "./storage/inmemory/InMemoryBlockStorage"; @@ -91,6 +92,7 @@ export * from "./storage/inmemory/InMemoryBatchStorage"; export * from "./storage/inmemory/InMemorySettlementStorage"; export * from "./storage/inmemory/InMemoryMessageStorage"; export * from "./storage/inmemory/InMemoryTransactionStorage"; +export * from "./storage/inmemory/InMemoryPropertyStorage"; export * from "./storage/StorageDependencyFactory"; export * from "./storage/Database"; export * from "./storage/Prunable"; diff --git a/packages/sequencer/src/storage/StorageDependencyFactory.ts b/packages/sequencer/src/storage/StorageDependencyFactory.ts index 51b95a35f..d24a4658f 100644 --- a/packages/sequencer/src/storage/StorageDependencyFactory.ts +++ b/packages/sequencer/src/storage/StorageDependencyFactory.ts @@ -9,6 +9,7 @@ import { BlockQueue, BlockStorage } from "./repositories/BlockStorage"; import { MessageStorage } from "./repositories/MessageStorage"; import { SettlementStorage } from "./repositories/SettlementStorage"; import { TransactionStorage } from "./repositories/TransactionStorage"; +import { PropertyStorage } from "./repositories/PropertyStorage"; export interface StorageDependencyMinimumDependencies< Module, @@ -29,6 +30,8 @@ export interface StorageDependencyMinimumDependencies< asyncTreeStore: DependencyDeclaration; unprovenTreeStore: DependencyDeclaration; + + propertyStorage: DependencyDeclaration; } export interface DatabaseDependencyFactory { diff --git a/packages/sequencer/src/storage/inmemory/InMemoryDatabase.ts b/packages/sequencer/src/storage/inmemory/InMemoryDatabase.ts index c29f32f8b..7ed33c678 100644 --- a/packages/sequencer/src/storage/inmemory/InMemoryDatabase.ts +++ b/packages/sequencer/src/storage/inmemory/InMemoryDatabase.ts @@ -16,6 +16,7 @@ import { InMemoryMessageStorage } from "./InMemoryMessageStorage"; import { InMemorySettlementStorage } from "./InMemorySettlementStorage"; import { InMemoryTransactionStorage } from "./InMemoryTransactionStorage"; import { InMemoryAsyncMerkleTreeStore } from "./InMemoryAsyncMerkleTreeStore"; +import { InMemoryPropertyStorage } from "./InMemoryPropertyStorage"; @sequencerModule() @closeable() @@ -62,6 +63,9 @@ export class InMemoryDatabase extends SequencerModule implements Database { asyncTreeStore: { useClass: InMemoryAsyncMerkleTreeStore, }, + propertyStorage: { + useClass: InMemoryPropertyStorage, + }, }; } diff --git a/packages/sequencer/src/storage/inmemory/InMemoryPropertyStorage.ts b/packages/sequencer/src/storage/inmemory/InMemoryPropertyStorage.ts new file mode 100644 index 000000000..ec2a21f70 --- /dev/null +++ b/packages/sequencer/src/storage/inmemory/InMemoryPropertyStorage.ts @@ -0,0 +1,13 @@ +import { PropertyStorage } from "../repositories/PropertyStorage"; + +export class InMemoryPropertyStorage implements PropertyStorage { + private readonly store: Record = {}; + + public async get(key: string): Promise { + return this.store[key]; + } + + public async set(key: string, value: string): Promise { + this.store[key] = value; + } +} From b2020fa6251631bf02f56f5fd1be7e8ead20c047 Mon Sep 17 00:00:00 2001 From: Raphael Panic Date: Thu, 7 May 2026 19:28:21 +0200 Subject: [PATCH 6/7] Fixed dependency cycle --- .../persistance/src/services/prisma/PrismaPropertyStorage.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/persistance/src/services/prisma/PrismaPropertyStorage.ts b/packages/persistance/src/services/prisma/PrismaPropertyStorage.ts index 95bc8fc89..be8a34ff0 100644 --- a/packages/persistance/src/services/prisma/PrismaPropertyStorage.ts +++ b/packages/persistance/src/services/prisma/PrismaPropertyStorage.ts @@ -1,7 +1,7 @@ import { PropertyStorage } from "@proto-kit/sequencer"; import { inject } from "tsyringe"; -import { PrismaConnection } from "../../PrismaDatabaseConnection"; +import type { PrismaConnection } from "../../PrismaDatabaseConnection"; export class PrismaPropertyStorage implements PropertyStorage { public constructor( From 0e49c3efb71f9ff45a8b6a2b8bc18a6ce677c0e8 Mon Sep 17 00:00:00 2001 From: Raphael Panic Date: Thu, 7 May 2026 19:29:39 +0200 Subject: [PATCH 7/7] Removed leftover imports --- .../protocol/production/trigger/ManualBlockTrigger.ts | 1 - packages/sequencer/src/settlement/SettlementModule.ts | 9 ++------- 2 files changed, 2 insertions(+), 8 deletions(-) diff --git a/packages/sequencer/src/protocol/production/trigger/ManualBlockTrigger.ts b/packages/sequencer/src/protocol/production/trigger/ManualBlockTrigger.ts index 4c82e71dc..9042814d0 100644 --- a/packages/sequencer/src/protocol/production/trigger/ManualBlockTrigger.ts +++ b/packages/sequencer/src/protocol/production/trigger/ManualBlockTrigger.ts @@ -13,7 +13,6 @@ import { } from "../../../settlement/BridgingModule"; import { BlockTrigger, BlockTriggerBase } from "./BlockTrigger"; -import { match, P } from "ts-pattern"; @sequencerModule() export class ManualBlockTrigger diff --git a/packages/sequencer/src/settlement/SettlementModule.ts b/packages/sequencer/src/settlement/SettlementModule.ts index 5d949a2ba..a7d7cc949 100644 --- a/packages/sequencer/src/settlement/SettlementModule.ts +++ b/packages/sequencer/src/settlement/SettlementModule.ts @@ -147,12 +147,7 @@ export class SettlementModule return this.contract; } - public async settleBatch( - batches: SettleableBatch[], - options: { - nonce?: number; - } = {} - ): Promise { + public async settleBatch(batches: SettleableBatch[]): Promise { let batch: SettleableBatch; if (batches.length === 0) { @@ -178,7 +173,7 @@ export class SettlementModule ); const settlement = await tryNTimes( - async () => await interaction.settle(batch, options), + async () => await interaction.settle(batch), 3, 1000 );