From 432d86fa43801d1390de4e8eb5e99d430faacb76 Mon Sep 17 00:00:00 2001 From: Raphael Panic Date: Wed, 19 Feb 2025 11:13:59 +0100 Subject: [PATCH 01/14] Tip tracking for BlockStorage --- .../src/mempool/private/PrivateMempool.ts | 9 ++- .../sequencing/BlockProducerModule.ts | 27 +++++-- .../src/state/StateServiceCreator.ts | 77 +++++++++++++++++++ .../src/state/state/CachedStateService.ts | 2 +- .../src/storage/StorageDependencyFactory.ts | 5 +- .../src/storage/inmemory/InMemoryDatabase.ts | 9 +-- .../test/integration/BlockProduction.test.ts | 22 +++--- .../integration/services/BlockTestService.ts | 16 ++-- 8 files changed, 126 insertions(+), 41 deletions(-) create mode 100644 packages/sequencer/src/state/StateServiceCreator.ts diff --git a/packages/sequencer/src/mempool/private/PrivateMempool.ts b/packages/sequencer/src/mempool/private/PrivateMempool.ts index f1c56f5af..83db843f2 100644 --- a/packages/sequencer/src/mempool/private/PrivateMempool.ts +++ b/packages/sequencer/src/mempool/private/PrivateMempool.ts @@ -27,8 +27,8 @@ import { SequencerModulesRecord, } from "../../sequencer/executor/Sequencer"; import { CachedStateService } from "../../state/state/CachedStateService"; -import { AsyncStateService } from "../../state/async/AsyncStateService"; import { distinctByPredicate } from "../../helpers/utils"; +import { StateServiceCreator } from "../../state/StateServiceCreator"; type MempoolTransactionPaths = { transaction: PendingTransaction; @@ -54,8 +54,8 @@ export class PrivateMempool private readonly protocol: Protocol, @inject("Sequencer") private readonly sequencer: Sequencer, - @inject("UnprovenStateService") - private readonly stateService: AsyncStateService + @inject("StateServiceCreator") + private readonly stateServiceCreator: StateServiceCreator ) { super(); this.accountStateHook = @@ -104,7 +104,8 @@ export class PrivateMempool public async getTxs(limit?: number): Promise { const txs = await this.transactionStorage.getPendingUserTransactions(); - const baseCachedStateService = new CachedStateService(this.stateService); + const stateService = await this.stateServiceCreator.getMask("latest"); + const baseCachedStateService = new CachedStateService(stateService); const networkState = (await this.getStagedNetworkState()) ?? NetworkState.empty(); diff --git a/packages/sequencer/src/protocol/production/sequencing/BlockProducerModule.ts b/packages/sequencer/src/protocol/production/sequencing/BlockProducerModule.ts index 78e358955..452e2adf2 100644 --- a/packages/sequencer/src/protocol/production/sequencing/BlockProducerModule.ts +++ b/packages/sequencer/src/protocol/production/sequencing/BlockProducerModule.ts @@ -16,7 +16,6 @@ import { import { BlockQueue } from "../../../storage/repositories/BlockStorage"; import { PendingTransaction } from "../../../mempool/PendingTransaction"; import { AsyncMerkleTreeStore } from "../../../state/async/AsyncMerkleTreeStore"; -import { AsyncStateService } from "../../../state/async/AsyncStateService"; import { Block, BlockResult, @@ -24,6 +23,7 @@ import { } from "../../../storage/model/Block"; import { Database } from "../../../storage/Database"; import { IncomingMessagesService } from "../../../settlement/messages/IncomingMessagesService"; +import { StateServiceCreator } from "../../../state/StateServiceCreator"; import { BlockProductionService } from "./BlockProductionService"; import { BlockResultService } from "./BlockResultService"; @@ -40,8 +40,8 @@ export class BlockProducerModule extends SequencerModule { public constructor( @inject("Mempool") private readonly mempool: Mempool, private readonly messageService: IncomingMessagesService, - @inject("UnprovenStateService") - private readonly unprovenStateService: AsyncStateService, + @inject("StateServiceCreator") + private readonly stateServiceCreator: StateServiceCreator, @inject("UnprovenMerkleStore") private readonly unprovenMerkleStore: AsyncMerkleTreeStore, @inject("BlockQueue") @@ -106,12 +106,16 @@ export class BlockProducerModule extends SequencerModule { } public async generateMetadata(block: Block): Promise { + const stateServiceMask = `block-${block.height.toBigInt()}`; + const asyncStateService = + await this.stateServiceCreator.getMask(stateServiceMask); + const { result, blockHashTreeStore, treeStore, stateService } = await this.resultService.generateMetadataForNextBlock( block, this.unprovenMerkleStore, this.blockTreeStore, - this.unprovenStateService + asyncStateService ); await this.database.executeInTransaction(async () => { @@ -120,6 +124,8 @@ export class BlockProducerModule extends SequencerModule { await stateService.mergeIntoParent(); await this.blockQueue.pushResult(result); + + await this.stateServiceCreator.mergeIntoParent(stateServiceMask); }); return result; @@ -197,6 +203,15 @@ export class BlockProducerModule extends SequencerModule { }; } + private async createMask(previousBlock: Block) { + const isFirstBlock = previousBlock.hash.equals(0n).toBoolean(); + const height = isFirstBlock ? 0n : previousBlock.height.toBigInt() + 1n; + + // const parent = isFirstBlock ? "base" : `block-${height - 1n}`; + + return await this.stateServiceCreator.createMask(`block-${height}`, "base"); + } + private async produceBlock(): Promise { this.productionInProgress = true; @@ -207,8 +222,10 @@ export class BlockProducerModule extends SequencerModule { return undefined; } + const stateService = await this.createMask(metadata.block); + const blockResult = await this.productionService.createBlock( - this.unprovenStateService, + stateService, txs, metadata, this.allowEmptyBlock() diff --git a/packages/sequencer/src/state/StateServiceCreator.ts b/packages/sequencer/src/state/StateServiceCreator.ts new file mode 100644 index 000000000..1eb13e008 --- /dev/null +++ b/packages/sequencer/src/state/StateServiceCreator.ts @@ -0,0 +1,77 @@ +import { AsyncStateService } from "./async/AsyncStateService"; +import { CachedStateService } from "./state/CachedStateService"; + +export interface StateServiceCreator { + createMask(name: string, parent: string): Promise; + getMask(name: string): Promise; + mergeIntoParent(name: string): Promise; +} + +export class InMemoryMasker< + T extends { + createMask(name: string): Promise; + mergeIntoParent(): Promise; + name?: string; + }, +> { + public constructor(base: T) { + this.serviceStack = [base]; + } + + private serviceStack: T[]; + + private findService(name: string) { + if (name === "latest") { + return this.serviceStack.at(-1)!; + } + return this.serviceStack.find((service) => service.name === name); + } + + public async getMask(name: string) { + const candidate = this.findService(name); + if (candidate === undefined) { + throw new Error(`State service ${name} not found`); + } + return candidate; + } + + public async createMask(name: string, parentName: string): Promise { + const candidate = this.findService(name); + if (candidate !== undefined) { + return candidate; + } + const parent = this.findService(parentName); + if (parent === undefined) { + throw new Error(`State service ${parentName} not found`); + } + const mask = await parent.createMask(name); + this.serviceStack.push(mask); + return mask; + } + + public async mergeIntoParent(name: string): Promise { + const service = await this.getMask(name); + + await service.mergeIntoParent(); + + const index = this.serviceStack.indexOf(service); + if (index === -1) { + throw new Error( + "Service not found in stack although found earlier, this shouldn't happen" + ); + } + this.serviceStack.splice(index, 1); + } +} + +export class InMemoryStateServiceCreator + extends InMemoryMasker + implements StateServiceCreator +{ + public constructor() { + super(new CachedStateService(undefined, "base")); + } +} + +// export class InMemoryTreeStoreCreator +// export InMemoryMasker diff --git a/packages/sequencer/src/state/state/CachedStateService.ts b/packages/sequencer/src/state/state/CachedStateService.ts index 5af85e235..79f80b4b6 100644 --- a/packages/sequencer/src/state/state/CachedStateService.ts +++ b/packages/sequencer/src/state/state/CachedStateService.ts @@ -22,7 +22,7 @@ export class CachedStateService super(); } - public async createMask(name: string): Promise { + public async createMask(name: string): Promise { return new CachedStateService(this, name); } diff --git a/packages/sequencer/src/storage/StorageDependencyFactory.ts b/packages/sequencer/src/storage/StorageDependencyFactory.ts index ed660d8fa..153e504bd 100644 --- a/packages/sequencer/src/storage/StorageDependencyFactory.ts +++ b/packages/sequencer/src/storage/StorageDependencyFactory.ts @@ -4,8 +4,8 @@ import { DependencyRecord, } from "@proto-kit/common"; -import { AsyncStateService } from "../state/async/AsyncStateService"; import { AsyncMerkleTreeStore } from "../state/async/AsyncMerkleTreeStore"; +import { StateServiceCreator } from "../state/StateServiceCreator"; import { BatchStorage } from "./repositories/BatchStorage"; import { BlockQueue, BlockStorage } from "./repositories/BlockStorage"; @@ -14,17 +14,16 @@ import { SettlementStorage } from "./repositories/SettlementStorage"; import { TransactionStorage } from "./repositories/TransactionStorage"; export interface StorageDependencyMinimumDependencies extends DependencyRecord { - asyncStateService: DependencyDeclaration; asyncMerkleStore: DependencyDeclaration; batchStorage: DependencyDeclaration; blockQueue: DependencyDeclaration; blockStorage: DependencyDeclaration; - unprovenStateService: DependencyDeclaration; unprovenMerkleStore: DependencyDeclaration; blockTreeStore: DependencyDeclaration; messageStorage: DependencyDeclaration; settlementStorage: DependencyDeclaration; transactionStorage: DependencyDeclaration; + stateServiceCreator: DependencyDeclaration; } export interface StorageDependencyFactory extends DependencyFactory { diff --git a/packages/sequencer/src/storage/inmemory/InMemoryDatabase.ts b/packages/sequencer/src/storage/inmemory/InMemoryDatabase.ts index a8eb330ab..47ecb5f4b 100644 --- a/packages/sequencer/src/storage/inmemory/InMemoryDatabase.ts +++ b/packages/sequencer/src/storage/inmemory/InMemoryDatabase.ts @@ -1,6 +1,5 @@ import { noop } from "@proto-kit/common"; -import { CachedStateService } from "../../state/state/CachedStateService"; import { sequencerModule, SequencerModule, @@ -8,6 +7,7 @@ import { import { StorageDependencyMinimumDependencies } from "../StorageDependencyFactory"; import { Database } from "../Database"; import { closeable } from "../../sequencer/builder/Closeable"; +import { InMemoryStateServiceCreator } from "../../state/StateServiceCreator"; import { InMemoryBlockStorage } from "./InMemoryBlockStorage"; import { InMemoryAsyncMerkleTreeStore } from "./InMemoryAsyncMerkleTreeStore"; @@ -24,9 +24,6 @@ export class InMemoryDatabase extends SequencerModule implements Database { asyncMerkleStore: { useClass: InMemoryAsyncMerkleTreeStore, }, - asyncStateService: { - useFactory: () => new CachedStateService(undefined), - }, batchStorage: { useClass: InMemoryBatchStorage, }, @@ -36,8 +33,8 @@ export class InMemoryDatabase extends SequencerModule implements Database { blockStorage: { useToken: "BlockQueue", }, - unprovenStateService: { - useFactory: () => new CachedStateService(undefined), + stateServiceCreator: { + useClass: InMemoryStateServiceCreator, }, unprovenMerkleStore: { useClass: InMemoryAsyncMerkleTreeStore, diff --git a/packages/sequencer/test/integration/BlockProduction.test.ts b/packages/sequencer/test/integration/BlockProduction.test.ts index b24a6a732..8a18a4632 100644 --- a/packages/sequencer/test/integration/BlockProduction.test.ts +++ b/packages/sequencer/test/integration/BlockProduction.test.ts @@ -232,7 +232,7 @@ describe("block production", () => { ); // TODO // const newState = await test.getState(balancesPath, "batch"); - const newUnprovenState = await test.getState(balancesPath, "block"); + const newUnprovenState = await test.getState(balancesPath); // expect(newState).toBeDefined(); expect(newUnprovenState).toBeDefined(); @@ -248,7 +248,7 @@ describe("block production", () => { accountModule.accountState.keyType, publicKey ); - const newAccountState = await test.getState(accountStatePath, "block"); + const newAccountState = await test.getState(accountStatePath); expect(newAccountState).toBeDefined(); expect(AccountState.fromFields(newAccountState!).nonce.toBigInt()).toBe(1n); @@ -273,14 +273,14 @@ describe("block production", () => { expect(batch!.blockHashes).toHaveLength(1); expect(batch!.proof.proof).toBe(MOCK_PROOF); - const state2 = await test.getState(balancesPath, "block"); + const state2 = await test.getState(balancesPath); expect(state2).toBeDefined(); expect(UInt64.fromFields(state2!)).toStrictEqual(UInt64.from(200)); }, 60_000); it("should reject tx and not apply the state", async () => { - expect.assertions(5); + expect.assertions(4); const privateKey = PrivateKey.random(); @@ -302,12 +302,10 @@ describe("block production", () => { balanceModule.balances.keyType, PublicKey.empty() ); - const unprovenState = await test.getState(balancesPath, "block"); - const newState = await test.getState(balancesPath, "batch"); + const unprovenState = await test.getState(balancesPath); // Assert that state is not set expect(unprovenState).toBeUndefined(); - expect(newState).toBeUndefined(); }, 30_000); it("should produce txs in non-consecutive blocks", async () => { @@ -441,7 +439,7 @@ describe("block production", () => { balanceModule.balances.keyType, publicKey ); - const newState = await test.getState(balancesPath, "block"); + const newState = await test.getState(balancesPath); expect(newState).toBeDefined(); expect(UInt64.fromFields(newState!)).toStrictEqual( @@ -485,7 +483,7 @@ describe("block production", () => { balanceModule.balances.keyType, pk1.toPublicKey() ); - const newState1 = await test.getState(balancesPath1, "block"); + const newState1 = await test.getState(balancesPath1); expect(newState1).toBeUndefined(); @@ -494,7 +492,7 @@ describe("block production", () => { balanceModule.balances.keyType, pk2.toPublicKey() ); - const newState2 = await test.getState(balancesPath2, "block"); + const newState2 = await test.getState(balancesPath2); expect(newState2).toBeDefined(); expect(UInt64.fromFields(newState2!)).toStrictEqual(UInt64.from(100)); @@ -601,7 +599,7 @@ describe("block production", () => { "totalSupply", PROTOKIT_PREFIXES.STATE_RUNTIME ); - const newState = await test.getState(supplyPath, "block"); + const newState = await test.getState(supplyPath); expect(newState).toBeDefined(); expect(UInt64.fromFields(newState!)).toStrictEqual( @@ -617,7 +615,7 @@ describe("block production", () => { pk2 ); - const newBalance = await test.getState(balancesPath, "block"); + const newBalance = await test.getState(balancesPath); expect(newBalance).toBeDefined(); expect(UInt64.fromFields(newBalance!)).toStrictEqual(UInt64.from(200)); diff --git a/packages/sequencer/test/integration/services/BlockTestService.ts b/packages/sequencer/test/integration/services/BlockTestService.ts index f51c64393..b78c39364 100644 --- a/packages/sequencer/test/integration/services/BlockTestService.ts +++ b/packages/sequencer/test/integration/services/BlockTestService.ts @@ -3,12 +3,9 @@ import { Field, PrivateKey } from "o1js"; import { Runtime, RuntimeModulesRecord } from "@proto-kit/module"; import { ArgumentTypes } from "@proto-kit/common"; -import { - AsyncStateService, - ManualBlockTrigger, - PrivateMempool, -} from "../../../src"; +import { ManualBlockTrigger, PrivateMempool } from "../../../src"; import { createTransaction } from "../utils"; +import { StateServiceCreator } from "../../../src/state/StateServiceCreator"; @injectable() @scoped(Lifecycle.ContainerScoped) @@ -17,8 +14,8 @@ export class BlockTestService { @inject("BlockTrigger") private trigger: ManualBlockTrigger, @inject("Mempool") private mempool: PrivateMempool, @inject("Runtime") private runtime: Runtime, - @inject("AsyncStateService") private batchStateService: AsyncStateService, - @inject("UnprovenStateService") private blockStateService: AsyncStateService + @inject("StateServiceCreator") + private stateServiceCreator: StateServiceCreator ) {} private nonces: Record = {}; @@ -47,9 +44,8 @@ export class BlockTestService { this.nonces[privateKey.toPublicKey().toBase58()] = nonce + 1; } - public async getState(path: Field, type: "block" | "batch" = "block") { - const service = - type === "batch" ? this.batchStateService : this.blockStateService; + public async getState(path: Field) { + const service = await this.stateServiceCreator.getMask("latest"); return await service.get(path); } From c3e5dd5f092b5761f6aa5a1b53758890f7b2856d Mon Sep 17 00:00:00 2001 From: Raphael Panic Date: Thu, 20 Feb 2025 19:17:49 +0100 Subject: [PATCH 02/14] Implemented MaskGraph and Creator interfaces --- .../src/state/async/AsyncStateService.ts | 2 - .../sequencer/src/state/masking/MaskGraph.ts | 134 ++++++++++++++++++ .../sequencer/src/state/masking/MaskName.ts | 3 + .../src/state/masking/StateServiceCreator.ts | 10 ++ .../src/state/masking/TreeStoreCreator.ts | 12 ++ 5 files changed, 159 insertions(+), 2 deletions(-) create mode 100644 packages/sequencer/src/state/masking/MaskGraph.ts create mode 100644 packages/sequencer/src/state/masking/MaskName.ts create mode 100644 packages/sequencer/src/state/masking/StateServiceCreator.ts create mode 100644 packages/sequencer/src/state/masking/TreeStoreCreator.ts diff --git a/packages/sequencer/src/state/async/AsyncStateService.ts b/packages/sequencer/src/state/async/AsyncStateService.ts index b5e7a1be0..1d365eb4c 100644 --- a/packages/sequencer/src/state/async/AsyncStateService.ts +++ b/packages/sequencer/src/state/async/AsyncStateService.ts @@ -21,8 +21,6 @@ export interface AsyncStateService { get(key: Field): Promise; - createMask(name: string): Promise; - mergeIntoParent(): Promise; drop(): Promise; diff --git a/packages/sequencer/src/state/masking/MaskGraph.ts b/packages/sequencer/src/state/masking/MaskGraph.ts new file mode 100644 index 000000000..fca884476 --- /dev/null +++ b/packages/sequencer/src/state/masking/MaskGraph.ts @@ -0,0 +1,134 @@ +function assertMaskFound( + service: R | undefined, + name: string +): asserts service is R { + if (service === undefined) { + throw new Error(`Mask with name ${name} not found`); + } +} + +type Node = { + mask: T; + children: Child[]; + parent: Parent; +}; + +// export type Mask = { +// createMask(name: string): Promise; +// name: string; +// mergeIntoParent(): Promise; +// updateParent(parent: Mask): void; +// }; +// +// export type MaskBase = { +// createMask(name: string): Promise; +// name: string; +// }; + +export class MaskGraph< + Base extends { + createMask(name: string): Promise; + name: string; + }, + Mask extends Base & { + mergeIntoParent(): Promise; + updateParent(parent: Mask | Base): void; + }, +> { + public constructor(base: Base) { + this.root = { + mask: base, + children: [], + parent: undefined, + }; + } + + root: Node; + + masks: Record> = {}; + + private findMaskNode(name: string) { + return this.masks[name]; + } + + private findNode( + name: string + ): Node | undefined { + if (name === "base") { + return this.root; + } + return this.findMaskNode(name); + } + + private findService(name: string): Mask | Base | undefined { + return this.findNode(name)?.mask; + } + + public async getMask(name: string) { + const candidate = this.findService(name); + + assertMaskFound(candidate, name); + + return candidate; + } + + public async createMask( + name: string, + parentName: string, + fallback?: string + ): Promise { + const candidate = this.findService(name); + if (candidate !== undefined) { + return candidate; + } + + let parent = this.findService(parentName); + + if (parent === undefined && fallback !== undefined) { + parent = this.findService(fallback); + } + + assertMaskFound(parent, `${parentName} | ${fallback}`); + + const mask = await parent.createMask(name); + + this.masks[name] = { + mask, + parent, + children: [], + }; + this.findNode(parentName)?.children.push(mask); + + return mask; + } + + private removeFromGraph(name: string) { + const node = this.findMaskNode(name); + + assertMaskFound(node, name); + + const { children, parent } = node; + + children.forEach((child) => { + child.updateParent(parent!); + this.findNode(child.name)!.parent = parent; + }); + const parentNode = this.findNode(parent!.name)!; + parentNode.children = parentNode.children.filter((c) => c !== node.mask); + parentNode.children.push(...children); + + delete this.masks[name]; + + return node.mask; + } + + public async mergeIntoParent(name: string): Promise { + const mask = this.removeFromGraph(name); + + await mask.mergeIntoParent(); + } + + public async drop(name: string) { + this.removeFromGraph(name); + } +} diff --git a/packages/sequencer/src/state/masking/MaskName.ts b/packages/sequencer/src/state/masking/MaskName.ts new file mode 100644 index 000000000..e320c9caa --- /dev/null +++ b/packages/sequencer/src/state/masking/MaskName.ts @@ -0,0 +1,3 @@ +export const MaskName = { + // TODO +}; diff --git a/packages/sequencer/src/state/masking/StateServiceCreator.ts b/packages/sequencer/src/state/masking/StateServiceCreator.ts new file mode 100644 index 000000000..cd562c480 --- /dev/null +++ b/packages/sequencer/src/state/masking/StateServiceCreator.ts @@ -0,0 +1,10 @@ +import { AsyncStateService } from "../async/AsyncStateService"; + +export interface StateServiceCreator { + createMask(name: string, parent: string): Promise; + getMask(name: string): Promise; + mergeIntoParent(name: string): Promise; + drop(name: string): Promise; +} + +// TODO Add Prefix diff --git a/packages/sequencer/src/state/masking/TreeStoreCreator.ts b/packages/sequencer/src/state/masking/TreeStoreCreator.ts new file mode 100644 index 000000000..fd419df9a --- /dev/null +++ b/packages/sequencer/src/state/masking/TreeStoreCreator.ts @@ -0,0 +1,12 @@ +import { AsyncMerkleTreeStore } from "../async/AsyncMerkleTreeStore"; + +export interface TreeStoreCreator { + createMask( + name: string, + parent: string, + fallback?: string + ): Promise; + getMask(name: string): Promise; + mergeIntoParent(name: string): Promise; + drop(name: string): Promise; +} From 49f5b08a914fe8b04ddc0f0a22c7bca436a4bb03 Mon Sep 17 00:00:00 2001 From: Raphael Panic Date: Thu, 20 Feb 2025 19:18:08 +0100 Subject: [PATCH 03/14] Implemented InMemory native masking --- .../src/state/StateServiceCreator.ts | 77 ------------------- .../src/state/merkle/CachedMerkleTreeStore.ts | 6 +- .../src/state/state/CachedStateService.ts | 9 +-- .../src/storage/StorageDependencyFactory.ts | 6 +- .../src/storage/inmemory/InMemoryDatabase.ts | 14 ++-- .../masking/InMemoryMerkleTreeStoreMask.ts | 24 ++++++ .../masking/InMemoryStateServiceCreator.ts | 13 ++++ .../masking/InMemoryStateServiceMask.ts | 15 ++++ .../masking/InMemoryTreeStoreCreator.ts | 23 ++++++ 9 files changed, 92 insertions(+), 95 deletions(-) delete mode 100644 packages/sequencer/src/state/StateServiceCreator.ts create mode 100644 packages/sequencer/src/storage/inmemory/masking/InMemoryMerkleTreeStoreMask.ts create mode 100644 packages/sequencer/src/storage/inmemory/masking/InMemoryStateServiceCreator.ts create mode 100644 packages/sequencer/src/storage/inmemory/masking/InMemoryStateServiceMask.ts create mode 100644 packages/sequencer/src/storage/inmemory/masking/InMemoryTreeStoreCreator.ts diff --git a/packages/sequencer/src/state/StateServiceCreator.ts b/packages/sequencer/src/state/StateServiceCreator.ts deleted file mode 100644 index 1eb13e008..000000000 --- a/packages/sequencer/src/state/StateServiceCreator.ts +++ /dev/null @@ -1,77 +0,0 @@ -import { AsyncStateService } from "./async/AsyncStateService"; -import { CachedStateService } from "./state/CachedStateService"; - -export interface StateServiceCreator { - createMask(name: string, parent: string): Promise; - getMask(name: string): Promise; - mergeIntoParent(name: string): Promise; -} - -export class InMemoryMasker< - T extends { - createMask(name: string): Promise; - mergeIntoParent(): Promise; - name?: string; - }, -> { - public constructor(base: T) { - this.serviceStack = [base]; - } - - private serviceStack: T[]; - - private findService(name: string) { - if (name === "latest") { - return this.serviceStack.at(-1)!; - } - return this.serviceStack.find((service) => service.name === name); - } - - public async getMask(name: string) { - const candidate = this.findService(name); - if (candidate === undefined) { - throw new Error(`State service ${name} not found`); - } - return candidate; - } - - public async createMask(name: string, parentName: string): Promise { - const candidate = this.findService(name); - if (candidate !== undefined) { - return candidate; - } - const parent = this.findService(parentName); - if (parent === undefined) { - throw new Error(`State service ${parentName} not found`); - } - const mask = await parent.createMask(name); - this.serviceStack.push(mask); - return mask; - } - - public async mergeIntoParent(name: string): Promise { - const service = await this.getMask(name); - - await service.mergeIntoParent(); - - const index = this.serviceStack.indexOf(service); - if (index === -1) { - throw new Error( - "Service not found in stack although found earlier, this shouldn't happen" - ); - } - this.serviceStack.splice(index, 1); - } -} - -export class InMemoryStateServiceCreator - extends InMemoryMasker - implements StateServiceCreator -{ - public constructor() { - super(new CachedStateService(undefined, "base")); - } -} - -// export class InMemoryTreeStoreCreator -// export InMemoryMasker diff --git a/packages/sequencer/src/state/merkle/CachedMerkleTreeStore.ts b/packages/sequencer/src/state/merkle/CachedMerkleTreeStore.ts index 677668abd..eb4018e7f 100644 --- a/packages/sequencer/src/state/merkle/CachedMerkleTreeStore.ts +++ b/packages/sequencer/src/state/merkle/CachedMerkleTreeStore.ts @@ -29,10 +29,14 @@ export class CachedMerkleTreeStore noop(); } - public constructor(private readonly parent: AsyncMerkleTreeStore) { + public constructor(public parent: AsyncMerkleTreeStore) { super(); } + public updateParent(parent: AsyncMerkleTreeStore) { + this.parent = parent; + } + public getNode(key: bigint, level: number): bigint | undefined { return super.getNode(key, level); } diff --git a/packages/sequencer/src/state/state/CachedStateService.ts b/packages/sequencer/src/state/state/CachedStateService.ts index 79f80b4b6..645df5e7d 100644 --- a/packages/sequencer/src/state/state/CachedStateService.ts +++ b/packages/sequencer/src/state/state/CachedStateService.ts @@ -15,15 +15,12 @@ export class CachedStateService { private writes: StateEntry[] = []; - public constructor( - private readonly parent: AsyncStateService | undefined, - public readonly name?: string - ) { + public constructor(private parent: AsyncStateService | undefined) { super(); } - public async createMask(name: string): Promise { - return new CachedStateService(this, name); + public updateParent(parent: AsyncStateService) { + this.parent = parent; } public async drop(): Promise { diff --git a/packages/sequencer/src/storage/StorageDependencyFactory.ts b/packages/sequencer/src/storage/StorageDependencyFactory.ts index 153e504bd..135bb08ca 100644 --- a/packages/sequencer/src/storage/StorageDependencyFactory.ts +++ b/packages/sequencer/src/storage/StorageDependencyFactory.ts @@ -5,7 +5,8 @@ import { } from "@proto-kit/common"; import { AsyncMerkleTreeStore } from "../state/async/AsyncMerkleTreeStore"; -import { StateServiceCreator } from "../state/StateServiceCreator"; +import { StateServiceCreator } from "../state/masking/StateServiceCreator"; +import { TreeStoreCreator } from "../state/masking/TreeStoreCreator"; import { BatchStorage } from "./repositories/BatchStorage"; import { BlockQueue, BlockStorage } from "./repositories/BlockStorage"; @@ -14,16 +15,15 @@ import { SettlementStorage } from "./repositories/SettlementStorage"; import { TransactionStorage } from "./repositories/TransactionStorage"; export interface StorageDependencyMinimumDependencies extends DependencyRecord { - asyncMerkleStore: DependencyDeclaration; batchStorage: DependencyDeclaration; blockQueue: DependencyDeclaration; blockStorage: DependencyDeclaration; - unprovenMerkleStore: DependencyDeclaration; blockTreeStore: DependencyDeclaration; messageStorage: DependencyDeclaration; settlementStorage: DependencyDeclaration; transactionStorage: DependencyDeclaration; stateServiceCreator: DependencyDeclaration; + treeStoreCreator: DependencyDeclaration; } export interface StorageDependencyFactory extends DependencyFactory { diff --git a/packages/sequencer/src/storage/inmemory/InMemoryDatabase.ts b/packages/sequencer/src/storage/inmemory/InMemoryDatabase.ts index 47ecb5f4b..a27b06d9c 100644 --- a/packages/sequencer/src/storage/inmemory/InMemoryDatabase.ts +++ b/packages/sequencer/src/storage/inmemory/InMemoryDatabase.ts @@ -7,23 +7,21 @@ import { import { StorageDependencyMinimumDependencies } from "../StorageDependencyFactory"; import { Database } from "../Database"; import { closeable } from "../../sequencer/builder/Closeable"; -import { InMemoryStateServiceCreator } from "../../state/StateServiceCreator"; import { InMemoryBlockStorage } from "./InMemoryBlockStorage"; -import { InMemoryAsyncMerkleTreeStore } from "./InMemoryAsyncMerkleTreeStore"; import { InMemoryBatchStorage } from "./InMemoryBatchStorage"; import { InMemoryMessageStorage } from "./InMemoryMessageStorage"; import { InMemorySettlementStorage } from "./InMemorySettlementStorage"; import { InMemoryTransactionStorage } from "./InMemoryTransactionStorage"; +import { InMemoryStateServiceCreator } from "./masking/InMemoryStateServiceCreator"; +import { InMemoryTreeStoreCreator } from "./masking/InMemoryTreeStoreCreator"; +import { InMemoryBaseMerkleTreeStore } from "./masking/InMemoryMerkleTreeStoreMask"; @sequencerModule() @closeable() export class InMemoryDatabase extends SequencerModule implements Database { public dependencies(): StorageDependencyMinimumDependencies { return { - asyncMerkleStore: { - useClass: InMemoryAsyncMerkleTreeStore, - }, batchStorage: { useClass: InMemoryBatchStorage, }, @@ -36,11 +34,11 @@ export class InMemoryDatabase extends SequencerModule implements Database { stateServiceCreator: { useClass: InMemoryStateServiceCreator, }, - unprovenMerkleStore: { - useClass: InMemoryAsyncMerkleTreeStore, + treeStoreCreator: { + useClass: InMemoryTreeStoreCreator, }, blockTreeStore: { - useClass: InMemoryAsyncMerkleTreeStore, + useClass: InMemoryBaseMerkleTreeStore, }, messageStorage: { useClass: InMemoryMessageStorage, diff --git a/packages/sequencer/src/storage/inmemory/masking/InMemoryMerkleTreeStoreMask.ts b/packages/sequencer/src/storage/inmemory/masking/InMemoryMerkleTreeStoreMask.ts new file mode 100644 index 000000000..68f59177b --- /dev/null +++ b/packages/sequencer/src/storage/inmemory/masking/InMemoryMerkleTreeStoreMask.ts @@ -0,0 +1,24 @@ +import { AsyncMerkleTreeStore } from "../../../state/async/AsyncMerkleTreeStore"; +import { CachedMerkleTreeStore } from "../../../state/merkle/CachedMerkleTreeStore"; +import { InMemoryAsyncMerkleTreeStore } from "../InMemoryAsyncMerkleTreeStore"; + +export class InMemoryMerkleTreeStoreMask extends CachedMerkleTreeStore { + public constructor( + parent: AsyncMerkleTreeStore, + public readonly name: string + ) { + super(parent); + } + + public async createMask(name: string): Promise { + return new InMemoryMerkleTreeStoreMask(this, name); + } +} + +export class InMemoryBaseMerkleTreeStore extends InMemoryAsyncMerkleTreeStore { + public name = "base"; + + public async createMask(name: string): Promise { + return new InMemoryMerkleTreeStoreMask(this, name); + } +} diff --git a/packages/sequencer/src/storage/inmemory/masking/InMemoryStateServiceCreator.ts b/packages/sequencer/src/storage/inmemory/masking/InMemoryStateServiceCreator.ts new file mode 100644 index 000000000..6f8ea5f1f --- /dev/null +++ b/packages/sequencer/src/storage/inmemory/masking/InMemoryStateServiceCreator.ts @@ -0,0 +1,13 @@ +import { StateServiceCreator } from "../../../state/masking/StateServiceCreator"; +import { MaskGraph } from "../../../state/masking/MaskGraph"; + +import { InMemoryStateServiceMask } from "./InMemoryStateServiceMask"; + +export class InMemoryStateServiceCreator + extends MaskGraph + implements StateServiceCreator +{ + public constructor() { + super(new InMemoryStateServiceMask(undefined, "base")); + } +} diff --git a/packages/sequencer/src/storage/inmemory/masking/InMemoryStateServiceMask.ts b/packages/sequencer/src/storage/inmemory/masking/InMemoryStateServiceMask.ts new file mode 100644 index 000000000..529cb24c1 --- /dev/null +++ b/packages/sequencer/src/storage/inmemory/masking/InMemoryStateServiceMask.ts @@ -0,0 +1,15 @@ +import { AsyncStateService } from "../../../state/async/AsyncStateService"; +import { CachedStateService } from "../../../state/state/CachedStateService"; + +export class InMemoryStateServiceMask extends CachedStateService { + public constructor( + parent: AsyncStateService | undefined, + public readonly name: string + ) { + super(parent); + } + + public async createMask(name: string): Promise { + return new InMemoryStateServiceMask(this, name); + } +} diff --git a/packages/sequencer/src/storage/inmemory/masking/InMemoryTreeStoreCreator.ts b/packages/sequencer/src/storage/inmemory/masking/InMemoryTreeStoreCreator.ts new file mode 100644 index 000000000..7d0d136ed --- /dev/null +++ b/packages/sequencer/src/storage/inmemory/masking/InMemoryTreeStoreCreator.ts @@ -0,0 +1,23 @@ +import { MaskGraph } from "../../../state/masking/MaskGraph"; +import { AsyncMerkleTreeStore } from "../../../state/async/AsyncMerkleTreeStore"; +import { TreeStoreCreator } from "../../../state/masking/TreeStoreCreator"; + +import { + InMemoryBaseMerkleTreeStore, + InMemoryMerkleTreeStoreMask, +} from "./InMemoryMerkleTreeStoreMask"; + +export class InMemoryTreeStoreCreator + extends MaskGraph< + AsyncMerkleTreeStore & { + createMask: (name: string) => Promise; + name: string; + }, + InMemoryMerkleTreeStoreMask + > + implements TreeStoreCreator +{ + public constructor() { + super(new InMemoryBaseMerkleTreeStore()); + } +} From 1802f78df488d195f9a1f45d7eb2f1227f7317d8 Mon Sep 17 00:00:00 2001 From: Raphael Panic Date: Thu, 20 Feb 2025 19:18:44 +0100 Subject: [PATCH 04/14] Integrated masking into block prod pipeline --- packages/sequencer/src/index.ts | 8 ++++++ .../src/mempool/private/PrivateMempool.ts | 4 +-- .../production/BatchProducerModule.ts | 27 ++++++++++++++----- .../sequencing/BlockProducerModule.ts | 25 +++++++++++------ .../integration/services/BlockTestService.ts | 4 +-- 5 files changed, 50 insertions(+), 18 deletions(-) diff --git a/packages/sequencer/src/index.ts b/packages/sequencer/src/index.ts index 462da091d..67cbcc71a 100644 --- a/packages/sequencer/src/index.ts +++ b/packages/sequencer/src/index.ts @@ -75,6 +75,10 @@ export * from "./storage/inmemory/InMemoryBatchStorage"; export * from "./storage/inmemory/InMemorySettlementStorage"; export * from "./storage/inmemory/InMemoryMessageStorage"; export * from "./storage/inmemory/InMemoryTransactionStorage"; +export * from "./storage/inmemory/masking/InMemoryStateServiceCreator"; +export * from "./storage/inmemory/masking/InMemoryTreeStoreCreator"; +export * from "./storage/inmemory/masking/InMemoryStateServiceMask"; +export * from "./storage/inmemory/masking/InMemoryMerkleTreeStoreMask"; export * from "./storage/StorageDependencyFactory"; export * from "./storage/Database"; export * from "./storage/DatabasePruneModule"; @@ -89,6 +93,10 @@ export * from "./state/merkle/CachedMerkleTreeStore"; export * from "./state/merkle/SyncCachedMerkleTreeStore"; export * from "./state/state/DummyStateService"; export * from "./state/state/CachedStateService"; +export * from "./state/masking/MaskGraph"; +export * from "./state/masking/MaskName"; +export * from "./state/masking/StateServiceCreator"; +export * from "./state/masking/TreeStoreCreator"; export * from "./settlement/SettlementModule"; export * from "./settlement/messages/WithdrawalQueue"; export * from "./settlement/messages/IncomingMessageAdapter"; diff --git a/packages/sequencer/src/mempool/private/PrivateMempool.ts b/packages/sequencer/src/mempool/private/PrivateMempool.ts index 83db843f2..52bedb2b1 100644 --- a/packages/sequencer/src/mempool/private/PrivateMempool.ts +++ b/packages/sequencer/src/mempool/private/PrivateMempool.ts @@ -28,7 +28,7 @@ import { } from "../../sequencer/executor/Sequencer"; import { CachedStateService } from "../../state/state/CachedStateService"; import { distinctByPredicate } from "../../helpers/utils"; -import { StateServiceCreator } from "../../state/StateServiceCreator"; +import { StateServiceCreator } from "../../state/masking/StateServiceCreator"; type MempoolTransactionPaths = { transaction: PendingTransaction; @@ -104,7 +104,7 @@ export class PrivateMempool public async getTxs(limit?: number): Promise { const txs = await this.transactionStorage.getPendingUserTransactions(); - const stateService = await this.stateServiceCreator.getMask("latest"); + const stateService = await this.stateServiceCreator.getMask("base"); const baseCachedStateService = new CachedStateService(stateService); const networkState = diff --git a/packages/sequencer/src/protocol/production/BatchProducerModule.ts b/packages/sequencer/src/protocol/production/BatchProducerModule.ts index 6bdb77202..d7b343b84 100644 --- a/packages/sequencer/src/protocol/production/BatchProducerModule.ts +++ b/packages/sequencer/src/protocol/production/BatchProducerModule.ts @@ -5,7 +5,7 @@ import { NetworkState, } from "@proto-kit/protocol"; import { Field, Proof } from "o1js"; -import { log, noop } from "@proto-kit/common"; +import { log, mapSequential, noop } from "@proto-kit/common"; import { sequencerModule, @@ -14,9 +14,9 @@ import { import { BatchStorage } from "../../storage/repositories/BatchStorage"; import { SettleableBatch } from "../../storage/model/Batch"; import { CachedMerkleTreeStore } from "../../state/merkle/CachedMerkleTreeStore"; -import { AsyncMerkleTreeStore } from "../../state/async/AsyncMerkleTreeStore"; import { BlockWithResult } from "../../storage/model/Block"; import type { Database } from "../../storage/Database"; +import { TreeStoreCreator } from "../../state/masking/TreeStoreCreator"; import { BlockProofSerializer } from "./tasks/serializers/BlockProofSerializer"; import { BatchTracingService } from "./tracing/BatchTracingService"; @@ -47,8 +47,8 @@ export class BatchProducerModule extends SequencerModule { private productionInProgress = false; public constructor( - @inject("AsyncMerkleStore") - private readonly merkleStore: AsyncMerkleTreeStore, + @inject("TreeStoreCreator") + private readonly treeStoreCreator: TreeStoreCreator, @inject("BatchStorage") private readonly batchStorage: BatchStorage, @inject("Database") private readonly database: Database, @@ -101,6 +101,16 @@ export class BatchProducerModule extends SequencerModule { return undefined; } + private async mergeBatchIntoStorage(blocks: BlockWithResult[]) { + // Reverse, so that we merge from latest block to oldest. This way, + // nodes that have been written in multiple masks are only written to + // the base once, therefore implicitly deduped + await mapSequential(blocks.slice().reverse(), async (block) => { + const mask = `block-${block.block.height.toBigInt()}`; + await this.treeStoreCreator.mergeIntoParent(mask); + }); + } + private async tryProduceBatch( blocks: BlockWithResult[] ): Promise { @@ -122,7 +132,9 @@ export class BatchProducerModule extends SequencerModule { // Apply state changes to current MerkleTreeStore await this.database.executeInTransaction(async () => { await this.batchStorage.pushBatch(batchWithStateDiff.batch); - await batchWithStateDiff.changes.mergeIntoParent(); + + await this.mergeBatchIntoStorage(blocks); + // await batchWithStateDiff.changes.mergeIntoParent(); }); // TODO Add transition from unproven to proven state for stateservice @@ -186,13 +198,16 @@ export class BatchProducerModule extends SequencerModule { throw errors.blockWithoutTxs(); } - const merkleTreeStore = new CachedMerkleTreeStore(this.merkleStore); + const mask = await this.treeStoreCreator.createMask("batch", "base"); + const merkleTreeStore = new CachedMerkleTreeStore(mask); const trace = await this.batchTraceService.traceBatch( blocks.map((block) => block), merkleTreeStore ); + await this.treeStoreCreator.drop("batch"); + const proof = await this.batchFlow.executeBatch(trace, blockId); const fromNetworkState = blocks[0].block.networkState.before; diff --git a/packages/sequencer/src/protocol/production/sequencing/BlockProducerModule.ts b/packages/sequencer/src/protocol/production/sequencing/BlockProducerModule.ts index 452e2adf2..096c04c6f 100644 --- a/packages/sequencer/src/protocol/production/sequencing/BlockProducerModule.ts +++ b/packages/sequencer/src/protocol/production/sequencing/BlockProducerModule.ts @@ -23,7 +23,8 @@ import { } from "../../../storage/model/Block"; import { Database } from "../../../storage/Database"; import { IncomingMessagesService } from "../../../settlement/messages/IncomingMessagesService"; -import { StateServiceCreator } from "../../../state/StateServiceCreator"; +import { TreeStoreCreator } from "../../../state/masking/TreeStoreCreator"; +import { StateServiceCreator } from "../../../state/masking/StateServiceCreator"; import { BlockProductionService } from "./BlockProductionService"; import { BlockResultService } from "./BlockResultService"; @@ -42,8 +43,10 @@ export class BlockProducerModule extends SequencerModule { private readonly messageService: IncomingMessagesService, @inject("StateServiceCreator") private readonly stateServiceCreator: StateServiceCreator, - @inject("UnprovenMerkleStore") - private readonly unprovenMerkleStore: AsyncMerkleTreeStore, + @inject("TreeStoreCreator") + private readonly treeStoreCreator: TreeStoreCreator, + // @inject("UnprovenMerkleStore") + // private readonly unprovenMerkleStore: AsyncMerkleTreeStore, @inject("BlockQueue") private readonly blockQueue: BlockQueue, @inject("BlockTreeStore") @@ -106,14 +109,20 @@ export class BlockProducerModule extends SequencerModule { } public async generateMetadata(block: Block): Promise { - const stateServiceMask = `block-${block.height.toBigInt()}`; - const asyncStateService = - await this.stateServiceCreator.getMask(stateServiceMask); + const height = block.height.toBigInt(); + const maskName = `block-${height}`; + const asyncStateService = await this.stateServiceCreator.getMask(maskName); + + const asyncTreeStore = await this.treeStoreCreator.createMask( + maskName, + `block-${height - 1n}`, + "base" + ); const { result, blockHashTreeStore, treeStore, stateService } = await this.resultService.generateMetadataForNextBlock( block, - this.unprovenMerkleStore, + asyncTreeStore, this.blockTreeStore, asyncStateService ); @@ -125,7 +134,7 @@ export class BlockProducerModule extends SequencerModule { await this.blockQueue.pushResult(result); - await this.stateServiceCreator.mergeIntoParent(stateServiceMask); + await this.stateServiceCreator.mergeIntoParent(maskName); }); return result; diff --git a/packages/sequencer/test/integration/services/BlockTestService.ts b/packages/sequencer/test/integration/services/BlockTestService.ts index b78c39364..c7c0f4acb 100644 --- a/packages/sequencer/test/integration/services/BlockTestService.ts +++ b/packages/sequencer/test/integration/services/BlockTestService.ts @@ -5,7 +5,7 @@ import { ArgumentTypes } from "@proto-kit/common"; import { ManualBlockTrigger, PrivateMempool } from "../../../src"; import { createTransaction } from "../utils"; -import { StateServiceCreator } from "../../../src/state/StateServiceCreator"; +import { StateServiceCreator } from "../../../src/state/masking/StateServiceCreator"; @injectable() @scoped(Lifecycle.ContainerScoped) @@ -45,7 +45,7 @@ export class BlockTestService { } public async getState(path: Field) { - const service = await this.stateServiceCreator.getMask("latest"); + const service = await this.stateServiceCreator.getMask("base"); return await service.get(path); } From 6b9245e7b6af3d9bf5fe8fcbaabe373ac4b2c5a7 Mon Sep 17 00:00:00 2001 From: Raphael Panic Date: Mon, 24 Feb 2025 15:39:35 +0100 Subject: [PATCH 05/14] Added MaskNames --- packages/sequencer/src/state/masking/MaskName.ts | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/packages/sequencer/src/state/masking/MaskName.ts b/packages/sequencer/src/state/masking/MaskName.ts index e320c9caa..ee994c98b 100644 --- a/packages/sequencer/src/state/masking/MaskName.ts +++ b/packages/sequencer/src/state/masking/MaskName.ts @@ -1,3 +1,10 @@ +import { Field } from "o1js"; + export const MaskName = { - // TODO + block(height: bigint | Field) { + const heightBigint = + typeof height === "bigint" ? height : height.toBigInt(); + return `block-${heightBigint}`; + }, + base: () => "base", }; From 6982dfe20634017e399b23145b4fbe0e669bd455 Mon Sep 17 00:00:00 2001 From: Raphael Panic Date: Mon, 24 Feb 2025 15:40:05 +0100 Subject: [PATCH 06/14] Replaced remaining occurrences of old service references --- .../graphql/modules/MerkleWitnessResolver.ts | 17 ++++++-- .../sdk/src/query/StateServiceQueryModule.ts | 42 +++++++++++++++---- .../src/mempool/private/PrivateMempool.ts | 5 ++- .../sequencing/BlockProducerModule.ts | 14 ++++--- .../src/settlement/BridgingModule.ts | 11 +++-- .../integration/StorageIntegration.test.ts | 15 +++---- .../integration/services/BlockTestService.ts | 4 +- 7 files changed, 76 insertions(+), 32 deletions(-) diff --git a/packages/api/src/graphql/modules/MerkleWitnessResolver.ts b/packages/api/src/graphql/modules/MerkleWitnessResolver.ts index 8c59b8cc8..b499fb28e 100644 --- a/packages/api/src/graphql/modules/MerkleWitnessResolver.ts +++ b/packages/api/src/graphql/modules/MerkleWitnessResolver.ts @@ -3,8 +3,10 @@ import { Length } from "class-validator"; import { inject } from "tsyringe"; import { RollupMerkleTree, RollupMerkleTreeWitness } from "@proto-kit/common"; import { - AsyncMerkleTreeStore, + BlockStorage, CachedMerkleTreeStore, + MaskName, + TreeStoreCreator, } from "@proto-kit/sequencer"; import { GraphqlModule, graphqlModule } from "../GraphqlModule"; @@ -34,7 +36,9 @@ export class MerkleWitnessDTO { @graphqlModule() export class MerkleWitnessResolver extends GraphqlModule { public constructor( - @inject("AsyncMerkleStore") private readonly treeStore: AsyncMerkleTreeStore + @inject("TreeStoreCreator") + private readonly treeStoreCreator: TreeStoreCreator, + @inject("BlockStorage") private readonly blockStorage: BlockStorage ) { super(); } @@ -44,7 +48,14 @@ export class MerkleWitnessResolver extends GraphqlModule { "Allows retrieval of merkle witnesses corresponding to a specific path in the appchain's state tree. These proves are generally retrieved from the current 'proven' state", }) public async witness(@Arg("path") path: string) { - const syncStore = new CachedMerkleTreeStore(this.treeStore); + const latestBlock = await this.blockStorage.getLatestBlock(); + const maskName = + latestBlock !== undefined + ? MaskName.block(latestBlock.block.height) + : MaskName.base(); + const treeStore = await this.treeStoreCreator.getMask(maskName); + + const syncStore = new CachedMerkleTreeStore(treeStore); await syncStore.preloadKey(BigInt(path)); const tree = new RollupMerkleTree(syncStore); diff --git a/packages/sdk/src/query/StateServiceQueryModule.ts b/packages/sdk/src/query/StateServiceQueryModule.ts index ec78f44aa..7f07c8957 100644 --- a/packages/sdk/src/query/StateServiceQueryModule.ts +++ b/packages/sdk/src/query/StateServiceQueryModule.ts @@ -5,6 +5,10 @@ import { Sequencer, SequencerModulesRecord, AsyncMerkleTreeStore, + BlockStorage, + TreeStoreCreator, + StateServiceCreator, + MaskName, } from "@proto-kit/sequencer"; import { Field } from "o1js"; import { inject, injectable } from "tsyringe"; @@ -23,24 +27,46 @@ export class StateServiceQueryModule super(); } - public get asyncStateService(): AsyncStateService { - return this.sequencer.dependencyContainer.resolve( - "UnprovenStateService" + private async getCurrentTreeMask() { + const block = await this.blockStorage().getLatestBlock(); + if (block !== undefined) { + return MaskName.block(block.block.height); + } + return MaskName.base(); + } + + public blockStorage() { + return this.sequencer.dependencyContainer.resolve( + "BlockStorage" ); } - public get treeStore(): AsyncMerkleTreeStore { - return this.sequencer.dependencyContainer.resolve("AsyncMerkleStore"); + public async asyncStateService(): Promise { + const stateServiceCreator = + this.sequencer.dependencyContainer.resolve( + "StateServiceCreator" + ); + return await stateServiceCreator.getMask(MaskName.base()); + } + + public async treeStore(): Promise { + const treeStoreCreator = + this.sequencer.dependencyContainer.resolve( + "TreeStoreCreator" + ); + return await treeStoreCreator.getMask(await this.getCurrentTreeMask()); } - public get(key: Field) { - return this.asyncStateService.get(key); + public async get(key: Field) { + const stateService = await this.asyncStateService(); + return await stateService.get(key); } public async merkleWitness( path: Field ): Promise { - const syncStore = new CachedMerkleTreeStore(this.treeStore); + const treeStore = await this.treeStore(); + const syncStore = new CachedMerkleTreeStore(treeStore); await syncStore.preloadKey(path.toBigInt()); const tree = new RollupMerkleTree(syncStore); diff --git a/packages/sequencer/src/mempool/private/PrivateMempool.ts b/packages/sequencer/src/mempool/private/PrivateMempool.ts index 52bedb2b1..102e31e7e 100644 --- a/packages/sequencer/src/mempool/private/PrivateMempool.ts +++ b/packages/sequencer/src/mempool/private/PrivateMempool.ts @@ -29,6 +29,7 @@ import { import { CachedStateService } from "../../state/state/CachedStateService"; import { distinctByPredicate } from "../../helpers/utils"; import { StateServiceCreator } from "../../state/masking/StateServiceCreator"; +import { MaskName } from "../../state/masking/MaskName"; type MempoolTransactionPaths = { transaction: PendingTransaction; @@ -104,7 +105,9 @@ export class PrivateMempool public async getTxs(limit?: number): Promise { const txs = await this.transactionStorage.getPendingUserTransactions(); - const stateService = await this.stateServiceCreator.getMask("base"); + const stateService = await this.stateServiceCreator.getMask( + MaskName.base() + ); const baseCachedStateService = new CachedStateService(stateService); const networkState = diff --git a/packages/sequencer/src/protocol/production/sequencing/BlockProducerModule.ts b/packages/sequencer/src/protocol/production/sequencing/BlockProducerModule.ts index 096c04c6f..b780d8318 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 { IncomingMessagesService } from "../../../settlement/messages/IncomingMessagesService"; import { TreeStoreCreator } from "../../../state/masking/TreeStoreCreator"; import { StateServiceCreator } from "../../../state/masking/StateServiceCreator"; +import { MaskName } from "../../../state/masking/MaskName"; import { BlockProductionService } from "./BlockProductionService"; import { BlockResultService } from "./BlockResultService"; @@ -110,13 +111,13 @@ export class BlockProducerModule extends SequencerModule { public async generateMetadata(block: Block): Promise { const height = block.height.toBigInt(); - const maskName = `block-${height}`; + const maskName = MaskName.block(height); const asyncStateService = await this.stateServiceCreator.getMask(maskName); const asyncTreeStore = await this.treeStoreCreator.createMask( maskName, - `block-${height - 1n}`, - "base" + MaskName.block(height - 1n), + MaskName.base() ); const { result, blockHashTreeStore, treeStore, stateService } = @@ -216,9 +217,10 @@ export class BlockProducerModule extends SequencerModule { const isFirstBlock = previousBlock.hash.equals(0n).toBoolean(); const height = isFirstBlock ? 0n : previousBlock.height.toBigInt() + 1n; - // const parent = isFirstBlock ? "base" : `block-${height - 1n}`; - - return await this.stateServiceCreator.createMask(`block-${height}`, "base"); + return await this.stateServiceCreator.createMask( + MaskName.block(height), + MaskName.base() + ); } private async produceBlock(): Promise { diff --git a/packages/sequencer/src/settlement/BridgingModule.ts b/packages/sequencer/src/settlement/BridgingModule.ts index c4e407246..135e7b2b1 100644 --- a/packages/sequencer/src/settlement/BridgingModule.ts +++ b/packages/sequencer/src/settlement/BridgingModule.ts @@ -49,6 +49,8 @@ import type { OutgoingMessageAdapter } from "./messages/WithdrawalQueue"; import type { SettlementModule } from "./SettlementModule"; import { SettlementUtils } from "./utils/SettlementUtils"; import { MinaTransactionSender } from "./transactions/MinaTransactionSender"; +import { TreeStoreCreator } from "../state/masking/TreeStoreCreator"; +import { MaskName } from "../state/masking/MaskName"; /** * Sequencer module that facilitates all transaction creation and monitoring for @@ -75,8 +77,8 @@ export class BridgingModule extends SequencerModule { private readonly settlementModule: SettlementModule, @inject("OutgoingMessageQueue") private readonly outgoingMessageQueue: OutgoingMessageAdapter, - @inject("AsyncMerkleStore") - private readonly merkleTreeStore: AsyncMerkleTreeStore, + @inject("TreeStoreCreator") + private readonly treeStoreCreator: TreeStoreCreator, @inject("FeeStrategy") private readonly feeStrategy: FeeStrategy, @inject("AreProofsEnabled") areProofsEnabled: AreProofsEnabled, @@ -357,7 +359,10 @@ export class BridgingModule extends SequencerModule { const bridgeContract = this.createBridgeContract(bridgeAddress, tokenId); - const cachedStore = new CachedMerkleTreeStore(this.merkleTreeStore); + const merkleTreeStore = await this.treeStoreCreator.getMask( + MaskName.base() + ); + const cachedStore = new CachedMerkleTreeStore(merkleTreeStore); const tree = new RollupMerkleTree(cachedStore); const [withdrawalModule, withdrawalStateName] = diff --git a/packages/sequencer/test/integration/StorageIntegration.test.ts b/packages/sequencer/test/integration/StorageIntegration.test.ts index 9fa4543b6..3e1579b02 100644 --- a/packages/sequencer/test/integration/StorageIntegration.test.ts +++ b/packages/sequencer/test/integration/StorageIntegration.test.ts @@ -20,6 +20,8 @@ import { StorageDependencyFactory, BlockStorage, VanillaTaskWorkerModules, + StateServiceCreator, + MaskName, } from "../../src"; import { DefaultTestingSequencerModules, @@ -63,8 +65,7 @@ describe.each([["InMemory", InMemoryDatabase]])( >; let runtime: Runtime<{ Balance: typeof Balance }>; - let unprovenState: AsyncStateService; - let provenState: AsyncStateService; + let stateMasks: StateServiceCreator; // let unprovenTreeStore: AsyncMerkleTreeStore; // let provenTreeStore: AsyncMerkleTreeStore; @@ -125,8 +126,7 @@ describe.each([["InMemory", InMemoryDatabase]])( runtime = appChain.runtime; sequencer = appChain.sequencer; - unprovenState = sequencer.resolve("UnprovenStateService"); - provenState = sequencer.resolve("AsyncStateService"); + stateMasks = sequencer.resolve("StateServiceCreator"); }); it("test unproven block prod", async () => { @@ -174,14 +174,11 @@ describe.each([["InMemory", InMemoryDatabase]])( ) ); - const state = await unprovenState.getMany( - Object.keys(stateDiff).map(Field) - ); + const mask = await stateMasks.getMask(MaskName.base()); + const state = await mask.getMany(Object.keys(stateDiff).map(Field)); expect(checkStateDiffEquality(stateDiff, state)).toBe(true); expect(state.length).toBeGreaterThanOrEqual(1); - - await expect(provenState.get(state[0].key)).resolves.toBeUndefined(); }); it("test proven block prod", async () => { diff --git a/packages/sequencer/test/integration/services/BlockTestService.ts b/packages/sequencer/test/integration/services/BlockTestService.ts index c7c0f4acb..cc44dfe01 100644 --- a/packages/sequencer/test/integration/services/BlockTestService.ts +++ b/packages/sequencer/test/integration/services/BlockTestService.ts @@ -3,7 +3,7 @@ import { Field, PrivateKey } from "o1js"; import { Runtime, RuntimeModulesRecord } from "@proto-kit/module"; import { ArgumentTypes } from "@proto-kit/common"; -import { ManualBlockTrigger, PrivateMempool } from "../../../src"; +import { ManualBlockTrigger, MaskName, PrivateMempool } from "../../../src"; import { createTransaction } from "../utils"; import { StateServiceCreator } from "../../../src/state/masking/StateServiceCreator"; @@ -45,7 +45,7 @@ export class BlockTestService { } public async getState(path: Field) { - const service = await this.stateServiceCreator.getMask("base"); + const service = await this.stateServiceCreator.getMask(MaskName.base()); return await service.get(path); } From 16685c694af75521fbe555f27f50c408a2cdb80c Mon Sep 17 00:00:00 2001 From: Raphael Panic Date: Mon, 24 Feb 2025 17:33:43 +0100 Subject: [PATCH 07/14] Fixed ST order bug in result calculation --- .../production/sequencing/BlockResultService.ts | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/packages/sequencer/src/protocol/production/sequencing/BlockResultService.ts b/packages/sequencer/src/protocol/production/sequencing/BlockResultService.ts index dcebfef0a..3e4098d5f 100644 --- a/packages/sequencer/src/protocol/production/sequencing/BlockResultService.ts +++ b/packages/sequencer/src/protocol/production/sequencing/BlockResultService.ts @@ -43,8 +43,8 @@ function collectStateDiff( } function createCombinedStateDiff( - transactions: TransactionExecutionResult[], - blockHookSTs: UntypedStateTransition[] + blockHookSTs: UntypedStateTransition[], + transactions: TransactionExecutionResult[] ) { // Flatten diff list into a single diff by applying them over each other return transactions @@ -53,7 +53,7 @@ function createCombinedStateDiff( .filter(({ applied }) => applied) .flatMap(({ stateTransitions }) => stateTransitions); - transitions.push(...blockHookSTs); + transitions.splice(0, 0, ...blockHookSTs); return collectStateDiff(transitions); }) @@ -181,8 +181,8 @@ export class BlockResultService { stateService: CachedStateService; }> { const combinedDiff = createCombinedStateDiff( - block.transactions, - block.beforeBlockStateTransitions + block.beforeBlockStateTransitions, + block.transactions ); const inMemoryStore = new CachedMerkleTreeStore(merkleTreeStore); From e96b22ae528aa9fdd24c78b3fcef4b534050eea2 Mon Sep 17 00:00:00 2001 From: Raphael Panic Date: Mon, 24 Feb 2025 19:27:21 +0100 Subject: [PATCH 08/14] Added Prisma and Redis Creators --- .../src/PrismaDatabaseConnection.ts | 10 ++- packages/persistance/src/RedisConnection.ts | 12 ++-- .../src/creators/PrismaStateServiceCreator.ts | 33 +++++++++ .../src/creators/RedisTreeStoreCreator.ts | 15 ++++ .../src/services/prisma/PrismaStateService.ts | 72 ++++++++++++++----- .../services/redis/RedisMerkleTreeStore.ts | 9 ++- 6 files changed, 118 insertions(+), 33 deletions(-) create mode 100644 packages/persistance/src/creators/PrismaStateServiceCreator.ts create mode 100644 packages/persistance/src/creators/RedisTreeStoreCreator.ts diff --git a/packages/persistance/src/PrismaDatabaseConnection.ts b/packages/persistance/src/PrismaDatabaseConnection.ts index 15c31d8f4..8a824afcb 100644 --- a/packages/persistance/src/PrismaDatabaseConnection.ts +++ b/packages/persistance/src/PrismaDatabaseConnection.ts @@ -12,6 +12,7 @@ import { PrismaBlockStorage } from "./services/prisma/PrismaBlockStorage"; import { PrismaSettlementStorage } from "./services/prisma/PrismaSettlementStorage"; import { PrismaMessageStorage } from "./services/prisma/PrismaMessageStorage"; import { PrismaTransactionStorage } from "./services/prisma/PrismaTransactionStorage"; +import { PrismaStateServiceCreator } from "./creators/PrismaStateServiceCreator"; export interface PrismaDatabaseConfig { // Either object-based config or connection string @@ -49,12 +50,9 @@ export class PrismaDatabaseConnection public dependencies(): OmitKeys< StorageDependencyMinimumDependencies, - "asyncMerkleStore" | "blockTreeStore" | "unprovenMerkleStore" + "blockTreeStore" | "treeStoreCreator" > { return { - asyncStateService: { - useFactory: () => new PrismaStateService(this, "batch"), - }, batchStorage: { useClass: PrismaBatchStore, }, @@ -73,8 +71,8 @@ export class PrismaDatabaseConnection transactionStorage: { useClass: PrismaTransactionStorage, }, - unprovenStateService: { - useFactory: () => new PrismaStateService(this, "block"), + stateServiceCreator: { + useClass: PrismaStateServiceCreator, }, }; } diff --git a/packages/persistance/src/RedisConnection.ts b/packages/persistance/src/RedisConnection.ts index da10ea78e..7d6dbf6f2 100644 --- a/packages/persistance/src/RedisConnection.ts +++ b/packages/persistance/src/RedisConnection.ts @@ -7,6 +7,7 @@ import { DependencyFactory } from "@proto-kit/common"; import isArray from "lodash/isArray"; import { RedisMerkleTreeStore } from "./services/redis/RedisMerkleTreeStore"; +import { RedisTreeStoreCreator } from "./creators/RedisTreeStoreCreator"; export interface RedisConnectionConfig { host: string; @@ -39,18 +40,15 @@ export class RedisConnectionModule public dependencies(): Pick< StorageDependencyMinimumDependencies, - "asyncMerkleStore" | "blockTreeStore" | "unprovenMerkleStore" + "blockTreeStore" | "treeStoreCreator" > { return { - asyncMerkleStore: { - useFactory: () => new RedisMerkleTreeStore(this), - }, - unprovenMerkleStore: { - useFactory: () => new RedisMerkleTreeStore(this, "unproven"), - }, blockTreeStore: { useFactory: () => new RedisMerkleTreeStore(this, "blockHash"), }, + treeStoreCreator: { + useClass: RedisTreeStoreCreator, + }, }; } diff --git a/packages/persistance/src/creators/PrismaStateServiceCreator.ts b/packages/persistance/src/creators/PrismaStateServiceCreator.ts new file mode 100644 index 000000000..3b1de25b3 --- /dev/null +++ b/packages/persistance/src/creators/PrismaStateServiceCreator.ts @@ -0,0 +1,33 @@ +import { AsyncStateService, StateServiceCreator } from "@proto-kit/sequencer"; +import { inject, injectable } from "tsyringe"; + +import { PrismaStateService } from "../services/prisma/PrismaStateService"; +import type { PrismaConnection } from "../PrismaDatabaseConnection"; + +@injectable() +export class PrismaStateServiceCreator implements StateServiceCreator { + public constructor( + @inject("Database") private readonly connection: PrismaConnection + ) {} + + public async createMask( + name: string, + parent: string + ): Promise { + return new PrismaStateService(this.connection, name, parent); + } + + public getMask(name: string): AsyncStateService { + return new PrismaStateService(this.connection, name); + } + + public async mergeIntoParent(name: string): Promise { + const service = new PrismaStateService(this.connection, name); + await service.mergeIntoParent(); + } + + public async drop(name: string): Promise { + const service = new PrismaStateService(this.connection, name); + await service.drop(); + } +} diff --git a/packages/persistance/src/creators/RedisTreeStoreCreator.ts b/packages/persistance/src/creators/RedisTreeStoreCreator.ts new file mode 100644 index 000000000..c0e4a1d71 --- /dev/null +++ b/packages/persistance/src/creators/RedisTreeStoreCreator.ts @@ -0,0 +1,15 @@ +import { + AsyncMerkleTreeStore, + InMemoryMerkleTreeStoreMask, + MaskGraph, + TreeStoreCreator, +} from "@proto-kit/sequencer"; +import { RedisMerkleTreeStore } from "../services/redis/RedisMerkleTreeStore"; + +export class RedisTreeStoreCreator + extends MaskGraph< + AsyncMerkleTreeStore, + RedisMerkleTreeStore, + InMemoryMerkleTreeStoreMask + > + implements TreeStoreCreator {} diff --git a/packages/persistance/src/services/prisma/PrismaStateService.ts b/packages/persistance/src/services/prisma/PrismaStateService.ts index 3f5c3a215..edec00cc7 100644 --- a/packages/persistance/src/services/prisma/PrismaStateService.ts +++ b/packages/persistance/src/services/prisma/PrismaStateService.ts @@ -1,4 +1,4 @@ -import { AsyncStateService, StateEntry } from "@proto-kit/sequencer"; +import { AsyncStateService, MaskName, StateEntry } from "@proto-kit/sequencer"; import { Field } from "o1js"; import { Prisma } from "@prisma/client"; import { noop } from "@proto-kit/common"; @@ -20,53 +20,85 @@ const Decimal = Prisma.Decimal.clone({ export class PrismaStateService implements AsyncStateService { private cache: StateEntry[] = []; - private maskId?: number; + private maskId?: { id: number; parentId?: number }; /** * @param connection * @param mask A indicator to which masking level the values belong. * This name has to be unique - * @param parent + * @param parentName */ public constructor( private readonly connection: PrismaConnection, private readonly mask: string, - private readonly parent?: number + private readonly parentName?: string ) {} - private async getMaskId(): Promise { + private async getMaskId(): Promise<{ id: number; parentId?: number }> { if (this.maskId === undefined) { - this.maskId = await this.initializeMask(this.mask, this.parent); + this.maskId = await this.initializeMask(this.mask, this.parentName); } return this.maskId; } - private async initializeMask(mask: string, parent?: number): Promise { + private async initializeMask( + mask: string, + parentName?: string + ): Promise<{ id: number; parentId?: number }> { const { prismaClient } = this.connection; const found = await prismaClient.mask.findFirst({ where: { name: mask, - parent, + }, + include: { + parentMask: true, }, }); if (found === null) { + // Find parent id + let parentId: number | undefined = undefined; + if (parentName !== undefined) { + const parent = await prismaClient.mask.findFirst({ + where: { + name: parentName, + }, + }); + if (parent === null) { + throw new Error(`Parent mask with name ${parentName} not found`); + } + + parentId = parent.id; + } else if (mask !== MaskName.base()) { + throw new Error( + "Can't initialize mask that's not the base using a null-parent " + ); + } + + // Create mask const createdMask = await prismaClient.mask.create({ data: { - parent, + parent: parentId, name: mask, }, }); - return createdMask.id; + return { + id: createdMask.id, + parentId: parentId, + }; } - return found.id; + + return { + id: found.id, + parentId: found.parent ?? undefined, + }; } public async commit(): Promise { const { prismaClient } = this.connection; - const maskId = await this.getMaskId(); + const { id: maskId } = await this.getMaskId(); const data = this.cache .filter((entry) => entry.value !== undefined) @@ -92,7 +124,7 @@ export class PrismaStateService implements AsyncStateService { } public async getMany(keys: Field[]): Promise { - const maskId = await this.getMaskId(); + const { id: maskId } = await this.getMaskId(); const paths = keys.map((key) => new Decimal(key.toString())); const records: { @@ -123,16 +155,18 @@ export class PrismaStateService implements AsyncStateService { } public async createMask(name: string): Promise { - const maskId = await this.getMaskId(); - return new PrismaStateService(this.connection, name, maskId); + // We only call this to make sure this mask actually exists, therefore that the + // relation can be satisfied + await this.getMaskId(); + return new PrismaStateService(this.connection, name, this.mask); } public async mergeIntoParent(): Promise { - const maskId = await this.getMaskId(); + const { id: maskId, parentId } = await this.getMaskId(); const client = this.connection.prismaClient; - if (this.parent !== undefined) { + if (parentId !== undefined) { // Rough strategy here: // 1. Delete all entries that are bound to be overwritten from the parent mask // 2. Update this mask's entries to parent mask id @@ -150,7 +184,7 @@ export class PrismaStateService implements AsyncStateService { parent: maskId, }, data: { - parent: this.parent, + parent: parentId, }, }), client.mask.delete({ @@ -165,7 +199,7 @@ export class PrismaStateService implements AsyncStateService { } public async drop(): Promise { - const maskId = await this.getMaskId(); + const { id: maskId } = await this.getMaskId(); await this.connection.prismaClient.state.deleteMany({ where: { diff --git a/packages/persistance/src/services/redis/RedisMerkleTreeStore.ts b/packages/persistance/src/services/redis/RedisMerkleTreeStore.ts index 3de87a29e..af4f70e53 100644 --- a/packages/persistance/src/services/redis/RedisMerkleTreeStore.ts +++ b/packages/persistance/src/services/redis/RedisMerkleTreeStore.ts @@ -1,5 +1,6 @@ import { AsyncMerkleTreeStore, + InMemoryMerkleTreeStoreMask, MerkleTreeNode, MerkleTreeNodeQuery, } from "@proto-kit/sequencer"; @@ -10,9 +11,11 @@ import type { RedisConnection } from "../../RedisConnection"; export class RedisMerkleTreeStore implements AsyncMerkleTreeStore { private cache: MerkleTreeNode[] = []; + public readonly name = "base"; + public constructor( private readonly connection: RedisConnection, - private readonly mask: string = "base" + private readonly mask: string ) {} private getKey(node: MerkleTreeNodeQuery): string { @@ -80,4 +83,8 @@ export class RedisMerkleTreeStore implements AsyncMerkleTreeStore { // }); // console.log(`Reduced ${concat.length} to ${this.cache.length} items to write`) } + + public async createMask(name: string) { + return new InMemoryMerkleTreeStoreMask(this, name); + } } From d4c8793cc7d3d91b762a1073e82b1f3003b8453a Mon Sep 17 00:00:00 2001 From: Raphael Panic Date: Mon, 24 Feb 2025 19:27:59 +0100 Subject: [PATCH 09/14] Type cleanup --- .../sequencer/src/state/masking/MaskGraph.ts | 35 ++++++++----------- .../src/state/masking/StateServiceCreator.ts | 2 +- .../src/state/masking/TreeStoreCreator.ts | 2 +- .../masking/InMemoryStateServiceCreator.ts | 7 +++- .../masking/InMemoryTreeStoreCreator.ts | 6 ++-- 5 files changed, 24 insertions(+), 28 deletions(-) diff --git a/packages/sequencer/src/state/masking/MaskGraph.ts b/packages/sequencer/src/state/masking/MaskGraph.ts index fca884476..9f87a56fd 100644 --- a/packages/sequencer/src/state/masking/MaskGraph.ts +++ b/packages/sequencer/src/state/masking/MaskGraph.ts @@ -13,27 +13,20 @@ type Node = { parent: Parent; }; -// export type Mask = { -// createMask(name: string): Promise; -// name: string; -// mergeIntoParent(): Promise; -// updateParent(parent: Mask): void; -// }; -// -// export type MaskBase = { -// createMask(name: string): Promise; -// name: string; -// }; +type BaseMaskType = Interface & { + createMask(name: string): Promise; + name: string; +}; + +type MaskType = BaseMaskType & { + mergeIntoParent(): Promise; + updateParent(parent: Interface): void; +}; export class MaskGraph< - Base extends { - createMask(name: string): Promise; - name: string; - }, - Mask extends Base & { - mergeIntoParent(): Promise; - updateParent(parent: Mask | Base): void; - }, + Interface, + Base extends BaseMaskType, + Mask extends MaskType, > { public constructor(base: Base) { this.root = { @@ -64,7 +57,7 @@ export class MaskGraph< return this.findNode(name)?.mask; } - public async getMask(name: string) { + public getMask(name: string) { const candidate = this.findService(name); assertMaskFound(candidate, name); @@ -76,7 +69,7 @@ export class MaskGraph< name: string, parentName: string, fallback?: string - ): Promise { + ): Promise { const candidate = this.findService(name); if (candidate !== undefined) { return candidate; diff --git a/packages/sequencer/src/state/masking/StateServiceCreator.ts b/packages/sequencer/src/state/masking/StateServiceCreator.ts index cd562c480..da62b8785 100644 --- a/packages/sequencer/src/state/masking/StateServiceCreator.ts +++ b/packages/sequencer/src/state/masking/StateServiceCreator.ts @@ -2,7 +2,7 @@ import { AsyncStateService } from "../async/AsyncStateService"; export interface StateServiceCreator { createMask(name: string, parent: string): Promise; - getMask(name: string): Promise; + getMask(name: string): AsyncStateService; mergeIntoParent(name: string): Promise; drop(name: string): Promise; } diff --git a/packages/sequencer/src/state/masking/TreeStoreCreator.ts b/packages/sequencer/src/state/masking/TreeStoreCreator.ts index fd419df9a..e9f186c79 100644 --- a/packages/sequencer/src/state/masking/TreeStoreCreator.ts +++ b/packages/sequencer/src/state/masking/TreeStoreCreator.ts @@ -6,7 +6,7 @@ export interface TreeStoreCreator { parent: string, fallback?: string ): Promise; - getMask(name: string): Promise; + getMask(name: string): AsyncMerkleTreeStore; mergeIntoParent(name: string): Promise; drop(name: string): Promise; } diff --git a/packages/sequencer/src/storage/inmemory/masking/InMemoryStateServiceCreator.ts b/packages/sequencer/src/storage/inmemory/masking/InMemoryStateServiceCreator.ts index 6f8ea5f1f..5bef718ef 100644 --- a/packages/sequencer/src/storage/inmemory/masking/InMemoryStateServiceCreator.ts +++ b/packages/sequencer/src/storage/inmemory/masking/InMemoryStateServiceCreator.ts @@ -2,9 +2,14 @@ import { StateServiceCreator } from "../../../state/masking/StateServiceCreator" import { MaskGraph } from "../../../state/masking/MaskGraph"; import { InMemoryStateServiceMask } from "./InMemoryStateServiceMask"; +import { AsyncStateService } from "../../../state/async/AsyncStateService"; export class InMemoryStateServiceCreator - extends MaskGraph + extends MaskGraph< + AsyncStateService, + InMemoryStateServiceMask, + InMemoryStateServiceMask + > implements StateServiceCreator { public constructor() { diff --git a/packages/sequencer/src/storage/inmemory/masking/InMemoryTreeStoreCreator.ts b/packages/sequencer/src/storage/inmemory/masking/InMemoryTreeStoreCreator.ts index 7d0d136ed..5a13b4585 100644 --- a/packages/sequencer/src/storage/inmemory/masking/InMemoryTreeStoreCreator.ts +++ b/packages/sequencer/src/storage/inmemory/masking/InMemoryTreeStoreCreator.ts @@ -9,10 +9,8 @@ import { export class InMemoryTreeStoreCreator extends MaskGraph< - AsyncMerkleTreeStore & { - createMask: (name: string) => Promise; - name: string; - }, + AsyncMerkleTreeStore, + InMemoryBaseMerkleTreeStore, InMemoryMerkleTreeStoreMask > implements TreeStoreCreator From bd85c6853071e531a2e3c8b953a4be4f34de37ea Mon Sep 17 00:00:00 2001 From: Raphael Panic Date: Tue, 25 Feb 2025 11:11:19 +0100 Subject: [PATCH 10/14] Linting --- .../src/services/prisma/PrismaStateService.ts | 2 +- packages/persistance/test/connection.test.ts | 4 ++-- packages/sequencer/src/settlement/BridgingModule.ts | 9 +++------ .../inmemory/masking/InMemoryStateServiceCreator.ts | 2 +- .../test/integration/StorageIntegration.test.ts | 1 - 5 files changed, 7 insertions(+), 11 deletions(-) diff --git a/packages/persistance/src/services/prisma/PrismaStateService.ts b/packages/persistance/src/services/prisma/PrismaStateService.ts index edec00cc7..6535fdfd2 100644 --- a/packages/persistance/src/services/prisma/PrismaStateService.ts +++ b/packages/persistance/src/services/prisma/PrismaStateService.ts @@ -129,6 +129,7 @@ export class PrismaStateService implements AsyncStateService { const records: { path: Prisma.Decimal; + // TODO This could potentially be non-null, but should be tested values: Prisma.Decimal[] | null; }[] = await this.connection.prismaClient.$queryRaw( readState(maskId, paths) @@ -136,7 +137,6 @@ export class PrismaStateService implements AsyncStateService { return records.map((record) => ({ key: Field(record.path.toFixed()), - // TODO Figure out why that is nullable value: record.values?.map((x) => Field(x.toFixed())) ?? [], })); } diff --git a/packages/persistance/test/connection.test.ts b/packages/persistance/test/connection.test.ts index ef83f454b..561d83c0f 100644 --- a/packages/persistance/test/connection.test.ts +++ b/packages/persistance/test/connection.test.ts @@ -22,7 +22,7 @@ describe.skip("prisma", () => { password: "password", }; await db.start(); - const store = new RedisMerkleTreeStore(db); + const store = new RedisMerkleTreeStore(db, "base"); const cached = new CachedMerkleTreeStore(store); const tree = new RollupMerkleTree(cached); @@ -38,7 +38,7 @@ describe.skip("prisma", () => { console.log(`Root ${tree.getRoot().toBigInt()}`); - const store2 = new RedisMerkleTreeStore(db); + const store2 = new RedisMerkleTreeStore(db, "base"); const cached2 = new CachedMerkleTreeStore(store2); const tree2 = new RollupMerkleTree(cached2); diff --git a/packages/sequencer/src/settlement/BridgingModule.ts b/packages/sequencer/src/settlement/BridgingModule.ts index 135e7b2b1..9fe8bf14c 100644 --- a/packages/sequencer/src/settlement/BridgingModule.ts +++ b/packages/sequencer/src/settlement/BridgingModule.ts @@ -41,16 +41,15 @@ import { sequencerModule, } from "../sequencer/builder/SequencerModule"; import { CachedMerkleTreeStore } from "../state/merkle/CachedMerkleTreeStore"; -import { AsyncMerkleTreeStore } from "../state/async/AsyncMerkleTreeStore"; import { FeeStrategy } from "../protocol/baselayer/fees/FeeStrategy"; import type { MinaBaseLayer } from "../protocol/baselayer/MinaBaseLayer"; +import { TreeStoreCreator } from "../state/masking/TreeStoreCreator"; +import { MaskName } from "../state/masking/MaskName"; import type { OutgoingMessageAdapter } from "./messages/WithdrawalQueue"; import type { SettlementModule } from "./SettlementModule"; import { SettlementUtils } from "./utils/SettlementUtils"; import { MinaTransactionSender } from "./transactions/MinaTransactionSender"; -import { TreeStoreCreator } from "../state/masking/TreeStoreCreator"; -import { MaskName } from "../state/masking/MaskName"; /** * Sequencer module that facilitates all transaction creation and monitoring for @@ -359,9 +358,7 @@ export class BridgingModule extends SequencerModule { const bridgeContract = this.createBridgeContract(bridgeAddress, tokenId); - const merkleTreeStore = await this.treeStoreCreator.getMask( - MaskName.base() - ); + const merkleTreeStore = this.treeStoreCreator.getMask(MaskName.base()); const cachedStore = new CachedMerkleTreeStore(merkleTreeStore); const tree = new RollupMerkleTree(cachedStore); diff --git a/packages/sequencer/src/storage/inmemory/masking/InMemoryStateServiceCreator.ts b/packages/sequencer/src/storage/inmemory/masking/InMemoryStateServiceCreator.ts index 5bef718ef..9b21d7c61 100644 --- a/packages/sequencer/src/storage/inmemory/masking/InMemoryStateServiceCreator.ts +++ b/packages/sequencer/src/storage/inmemory/masking/InMemoryStateServiceCreator.ts @@ -1,8 +1,8 @@ import { StateServiceCreator } from "../../../state/masking/StateServiceCreator"; import { MaskGraph } from "../../../state/masking/MaskGraph"; +import { AsyncStateService } from "../../../state/async/AsyncStateService"; import { InMemoryStateServiceMask } from "./InMemoryStateServiceMask"; -import { AsyncStateService } from "../../../state/async/AsyncStateService"; export class InMemoryStateServiceCreator extends MaskGraph< diff --git a/packages/sequencer/test/integration/StorageIntegration.test.ts b/packages/sequencer/test/integration/StorageIntegration.test.ts index 3e1579b02..2316114e4 100644 --- a/packages/sequencer/test/integration/StorageIntegration.test.ts +++ b/packages/sequencer/test/integration/StorageIntegration.test.ts @@ -8,7 +8,6 @@ import { Bool, Field, PrivateKey, UInt64 } from "o1js"; import { TypedClass, expectDefined } from "@proto-kit/common"; import { - AsyncStateService, BatchStorage, HistoricalBatchStorage, HistoricalBlockStorage, From edc8d1e9f3d95d90e6901a553b51a42072a1ff90 Mon Sep 17 00:00:00 2001 From: Raphael Panic Date: Tue, 25 Feb 2025 16:37:17 +0100 Subject: [PATCH 11/14] Linting 2 --- .../src/creators/RedisTreeStoreCreator.ts | 1 + .../sequencing/BlockResultService.ts | 52 +++++++++---------- 2 files changed, 27 insertions(+), 26 deletions(-) diff --git a/packages/persistance/src/creators/RedisTreeStoreCreator.ts b/packages/persistance/src/creators/RedisTreeStoreCreator.ts index c0e4a1d71..a750ae283 100644 --- a/packages/persistance/src/creators/RedisTreeStoreCreator.ts +++ b/packages/persistance/src/creators/RedisTreeStoreCreator.ts @@ -4,6 +4,7 @@ import { MaskGraph, TreeStoreCreator, } from "@proto-kit/sequencer"; + import { RedisMerkleTreeStore } from "../services/redis/RedisMerkleTreeStore"; export class RedisTreeStoreCreator diff --git a/packages/sequencer/src/protocol/production/sequencing/BlockResultService.ts b/packages/sequencer/src/protocol/production/sequencing/BlockResultService.ts index 3e4098d5f..4a415e509 100644 --- a/packages/sequencer/src/protocol/production/sequencing/BlockResultService.ts +++ b/packages/sequencer/src/protocol/production/sequencing/BlockResultService.ts @@ -28,7 +28,7 @@ import type { StateRecord } from "../BatchProducerModule"; import { executeWithExecutionContext } from "./TransactionExecutionService"; -function collectStateDiff( +export function collectStateDiff( stateTransitions: UntypedStateTransition[] ): StateRecord { return stateTransitions.reduce>( @@ -42,7 +42,7 @@ function collectStateDiff( ); } -function createCombinedStateDiff( +export function createCombinedStateDiff( blockHookSTs: UntypedStateTransition[], transactions: TransactionExecutionResult[] ) { @@ -63,6 +63,28 @@ function createCombinedStateDiff( }, {}); } +export async function applyStateDiff( + store: CachedMerkleTreeStore, + stateDiff: StateRecord +): Promise { + await store.preloadKeys(Object.keys(stateDiff).map(BigInt)); + + // In case the diff is empty, we preload key 0 in order to + // retrieve the root, which we need later + if (Object.keys(stateDiff).length === 0) { + await store.preloadKey(0n); + } + + const tree = new RollupMerkleTree(store); + + Object.entries(stateDiff).forEach(([key, state]) => { + const treeValue = state !== undefined ? Poseidon.hash(state) : Field(0); + tree.setLeaf(BigInt(key), treeValue); + }); + + return tree; +} + @injectable() @scoped(Lifecycle.ContainerScoped) export class BlockResultService { @@ -147,28 +169,6 @@ export class BlockResultService { }; } - public async applyStateDiff( - store: CachedMerkleTreeStore, - stateDiff: StateRecord - ): Promise { - await store.preloadKeys(Object.keys(stateDiff).map(BigInt)); - - // In case the diff is empty, we preload key 0 in order to - // retrieve the root, which we need later - if (Object.keys(stateDiff).length === 0) { - await store.preloadKey(0n); - } - - const tree = new RollupMerkleTree(store); - - Object.entries(stateDiff).forEach(([key, state]) => { - const treeValue = state !== undefined ? Poseidon.hash(state) : Field(0); - tree.setLeaf(BigInt(key), treeValue); - }); - - return tree; - } - public async generateMetadataForNextBlock( block: Block, merkleTreeStore: AsyncMerkleTreeStore, @@ -187,7 +187,7 @@ export class BlockResultService { const inMemoryStore = new CachedMerkleTreeStore(merkleTreeStore); - const tree = await this.applyStateDiff(inMemoryStore, combinedDiff); + const tree = await applyStateDiff(inMemoryStore, combinedDiff); const witnessedStateRoot = tree.getRoot(); @@ -210,7 +210,7 @@ export class BlockResultService { ); // Apply afterBlock STs to the tree - const tree2 = await this.applyStateDiff( + const tree2 = await applyStateDiff( inMemoryStore, collectStateDiff( stateTransitions.map((stateTransition) => From d1530ea7cdd27a641fa33c35dd72b88471159e1c Mon Sep 17 00:00:00 2001 From: Raphael Panic Date: Tue, 25 Feb 2025 16:51:49 +0100 Subject: [PATCH 12/14] Linting 3 --- packages/persistance/src/PrismaDatabaseConnection.ts | 1 - packages/sdk/src/query/StateServiceQueryModule.ts | 4 ++-- 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/packages/persistance/src/PrismaDatabaseConnection.ts b/packages/persistance/src/PrismaDatabaseConnection.ts index 8a824afcb..9a04dbae0 100644 --- a/packages/persistance/src/PrismaDatabaseConnection.ts +++ b/packages/persistance/src/PrismaDatabaseConnection.ts @@ -6,7 +6,6 @@ import { } from "@proto-kit/sequencer"; import { DependencyFactory, OmitKeys } from "@proto-kit/common"; -import { PrismaStateService } from "./services/prisma/PrismaStateService"; import { PrismaBatchStore } from "./services/prisma/PrismaBatchStore"; import { PrismaBlockStorage } from "./services/prisma/PrismaBlockStorage"; import { PrismaSettlementStorage } from "./services/prisma/PrismaSettlementStorage"; diff --git a/packages/sdk/src/query/StateServiceQueryModule.ts b/packages/sdk/src/query/StateServiceQueryModule.ts index 7f07c8957..d1a1e7770 100644 --- a/packages/sdk/src/query/StateServiceQueryModule.ts +++ b/packages/sdk/src/query/StateServiceQueryModule.ts @@ -46,7 +46,7 @@ export class StateServiceQueryModule this.sequencer.dependencyContainer.resolve( "StateServiceCreator" ); - return await stateServiceCreator.getMask(MaskName.base()); + return stateServiceCreator.getMask(MaskName.base()); } public async treeStore(): Promise { @@ -54,7 +54,7 @@ export class StateServiceQueryModule this.sequencer.dependencyContainer.resolve( "TreeStoreCreator" ); - return await treeStoreCreator.getMask(await this.getCurrentTreeMask()); + return treeStoreCreator.getMask(await this.getCurrentTreeMask()); } public async get(key: Field) { From 5ccc3a92fbdf48bb3e6d6c66ae12c2031d2997d1 Mon Sep 17 00:00:00 2001 From: Raphael Panic Date: Wed, 26 Feb 2025 12:53:40 +0100 Subject: [PATCH 13/14] Fixed integration tests --- .../api/src/graphql/modules/MerkleWitnessResolver.ts | 2 +- .../persistance/src/creators/RedisTreeStoreCreator.ts | 10 +++++++++- .../sequencer/src/mempool/private/PrivateMempool.ts | 4 +--- .../src/protocol/production/BatchProducerModule.ts | 6 +++++- .../production/sequencing/BlockProducerModule.ts | 2 +- 5 files changed, 17 insertions(+), 7 deletions(-) diff --git a/packages/api/src/graphql/modules/MerkleWitnessResolver.ts b/packages/api/src/graphql/modules/MerkleWitnessResolver.ts index b499fb28e..c48a734ab 100644 --- a/packages/api/src/graphql/modules/MerkleWitnessResolver.ts +++ b/packages/api/src/graphql/modules/MerkleWitnessResolver.ts @@ -53,7 +53,7 @@ export class MerkleWitnessResolver extends GraphqlModule { latestBlock !== undefined ? MaskName.block(latestBlock.block.height) : MaskName.base(); - const treeStore = await this.treeStoreCreator.getMask(maskName); + const treeStore = this.treeStoreCreator.getMask(maskName); const syncStore = new CachedMerkleTreeStore(treeStore); await syncStore.preloadKey(BigInt(path)); diff --git a/packages/persistance/src/creators/RedisTreeStoreCreator.ts b/packages/persistance/src/creators/RedisTreeStoreCreator.ts index a750ae283..79d21497a 100644 --- a/packages/persistance/src/creators/RedisTreeStoreCreator.ts +++ b/packages/persistance/src/creators/RedisTreeStoreCreator.ts @@ -4,13 +4,21 @@ import { MaskGraph, TreeStoreCreator, } from "@proto-kit/sequencer"; +import { inject, injectable } from "tsyringe"; import { RedisMerkleTreeStore } from "../services/redis/RedisMerkleTreeStore"; +import type { RedisConnection } from "../RedisConnection"; +@injectable() export class RedisTreeStoreCreator extends MaskGraph< AsyncMerkleTreeStore, RedisMerkleTreeStore, InMemoryMerkleTreeStoreMask > - implements TreeStoreCreator {} + implements TreeStoreCreator +{ + public constructor(@inject("Database") connection: RedisConnection) { + super(new RedisMerkleTreeStore(connection, "base")); + } +} diff --git a/packages/sequencer/src/mempool/private/PrivateMempool.ts b/packages/sequencer/src/mempool/private/PrivateMempool.ts index 102e31e7e..1c1a7f241 100644 --- a/packages/sequencer/src/mempool/private/PrivateMempool.ts +++ b/packages/sequencer/src/mempool/private/PrivateMempool.ts @@ -105,9 +105,7 @@ export class PrivateMempool public async getTxs(limit?: number): Promise { const txs = await this.transactionStorage.getPendingUserTransactions(); - const stateService = await this.stateServiceCreator.getMask( - MaskName.base() - ); + const stateService = this.stateServiceCreator.getMask(MaskName.base()); const baseCachedStateService = new CachedStateService(stateService); const networkState = diff --git a/packages/sequencer/src/protocol/production/BatchProducerModule.ts b/packages/sequencer/src/protocol/production/BatchProducerModule.ts index d7b343b84..99fe69ae1 100644 --- a/packages/sequencer/src/protocol/production/BatchProducerModule.ts +++ b/packages/sequencer/src/protocol/production/BatchProducerModule.ts @@ -17,6 +17,7 @@ import { CachedMerkleTreeStore } from "../../state/merkle/CachedMerkleTreeStore" import { BlockWithResult } from "../../storage/model/Block"; import type { Database } from "../../storage/Database"; import { TreeStoreCreator } from "../../state/masking/TreeStoreCreator"; +import { MaskName } from "../../state/masking/MaskName"; import { BlockProofSerializer } from "./tasks/serializers/BlockProofSerializer"; import { BatchTracingService } from "./tracing/BatchTracingService"; @@ -198,7 +199,10 @@ export class BatchProducerModule extends SequencerModule { throw errors.blockWithoutTxs(); } - const mask = await this.treeStoreCreator.createMask("batch", "base"); + const mask = await this.treeStoreCreator.createMask( + "batch", + MaskName.base() + ); const merkleTreeStore = new CachedMerkleTreeStore(mask); const trace = await this.batchTraceService.traceBatch( diff --git a/packages/sequencer/src/protocol/production/sequencing/BlockProducerModule.ts b/packages/sequencer/src/protocol/production/sequencing/BlockProducerModule.ts index b780d8318..b264ec6ba 100644 --- a/packages/sequencer/src/protocol/production/sequencing/BlockProducerModule.ts +++ b/packages/sequencer/src/protocol/production/sequencing/BlockProducerModule.ts @@ -112,7 +112,7 @@ export class BlockProducerModule extends SequencerModule { public async generateMetadata(block: Block): Promise { const height = block.height.toBigInt(); const maskName = MaskName.block(height); - const asyncStateService = await this.stateServiceCreator.getMask(maskName); + const asyncStateService = this.stateServiceCreator.getMask(maskName); const asyncTreeStore = await this.treeStoreCreator.createMask( maskName, From f39bfa0c206e50b9b267ab524f5f528fc150931b Mon Sep 17 00:00:00 2001 From: Raphael Panic Date: Tue, 1 Apr 2025 15:47:52 +0200 Subject: [PATCH 14/14] Fixed merge errors --- .../src/creators/PrismaStateServiceCreator.ts | 17 +++++++++++------ .../src/creators/RedisTreeStoreCreator.ts | 8 ++++++-- .../src/services/prisma/PrismaStateService.ts | 7 ++++++- 3 files changed, 23 insertions(+), 9 deletions(-) diff --git a/packages/persistance/src/creators/PrismaStateServiceCreator.ts b/packages/persistance/src/creators/PrismaStateServiceCreator.ts index 3b1de25b3..447b72f0b 100644 --- a/packages/persistance/src/creators/PrismaStateServiceCreator.ts +++ b/packages/persistance/src/creators/PrismaStateServiceCreator.ts @@ -1,4 +1,8 @@ -import { AsyncStateService, StateServiceCreator } from "@proto-kit/sequencer"; +import { + AsyncStateService, + StateServiceCreator, + Tracer, +} from "@proto-kit/sequencer"; import { inject, injectable } from "tsyringe"; import { PrismaStateService } from "../services/prisma/PrismaStateService"; @@ -7,27 +11,28 @@ import type { PrismaConnection } from "../PrismaDatabaseConnection"; @injectable() export class PrismaStateServiceCreator implements StateServiceCreator { public constructor( - @inject("Database") private readonly connection: PrismaConnection + @inject("Database") private readonly connection: PrismaConnection, + @inject("Tracer") private readonly tracer: Tracer ) {} public async createMask( name: string, parent: string ): Promise { - return new PrismaStateService(this.connection, name, parent); + return new PrismaStateService(this.connection, this.tracer, name, parent); } public getMask(name: string): AsyncStateService { - return new PrismaStateService(this.connection, name); + return new PrismaStateService(this.connection, this.tracer, name); } public async mergeIntoParent(name: string): Promise { - const service = new PrismaStateService(this.connection, name); + const service = new PrismaStateService(this.connection, this.tracer, name); await service.mergeIntoParent(); } public async drop(name: string): Promise { - const service = new PrismaStateService(this.connection, name); + const service = new PrismaStateService(this.connection, this.tracer, name); await service.drop(); } } diff --git a/packages/persistance/src/creators/RedisTreeStoreCreator.ts b/packages/persistance/src/creators/RedisTreeStoreCreator.ts index 79d21497a..7e4d30d6b 100644 --- a/packages/persistance/src/creators/RedisTreeStoreCreator.ts +++ b/packages/persistance/src/creators/RedisTreeStoreCreator.ts @@ -2,6 +2,7 @@ import { AsyncMerkleTreeStore, InMemoryMerkleTreeStoreMask, MaskGraph, + Tracer, TreeStoreCreator, } from "@proto-kit/sequencer"; import { inject, injectable } from "tsyringe"; @@ -18,7 +19,10 @@ export class RedisTreeStoreCreator > implements TreeStoreCreator { - public constructor(@inject("Database") connection: RedisConnection) { - super(new RedisMerkleTreeStore(connection, "base")); + public constructor( + @inject("Database") connection: RedisConnection, + @inject("Tracer") tracer: Tracer + ) { + super(new RedisMerkleTreeStore(connection, tracer, "base")); } } diff --git a/packages/persistance/src/services/prisma/PrismaStateService.ts b/packages/persistance/src/services/prisma/PrismaStateService.ts index 0724b7abf..4c23a3846 100644 --- a/packages/persistance/src/services/prisma/PrismaStateService.ts +++ b/packages/persistance/src/services/prisma/PrismaStateService.ts @@ -167,7 +167,12 @@ export class PrismaStateService implements AsyncStateService { // We only call this to make sure this mask actually exists, therefore that the // relation can be satisfied await this.getMaskId(); - return new PrismaStateService(this.connection, this.tracer, name, this.mask); + return new PrismaStateService( + this.connection, + this.tracer, + name, + this.mask + ); } public async mergeIntoParent(): Promise {