diff --git a/packages/api/src/graphql/modules/MerkleWitnessResolver.ts b/packages/api/src/graphql/modules/MerkleWitnessResolver.ts index 8c59b8cc8..c48a734ab 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 = this.treeStoreCreator.getMask(maskName); + + const syncStore = new CachedMerkleTreeStore(treeStore); await syncStore.preloadKey(BigInt(path)); const tree = new RollupMerkleTree(syncStore); diff --git a/packages/persistance/src/PrismaDatabaseConnection.ts b/packages/persistance/src/PrismaDatabaseConnection.ts index 07b56f6db..59d52f742 100644 --- a/packages/persistance/src/PrismaDatabaseConnection.ts +++ b/packages/persistance/src/PrismaDatabaseConnection.ts @@ -7,12 +7,12 @@ 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"; 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 @@ -54,12 +54,9 @@ export class PrismaDatabaseConnection public dependencies(): OmitKeys< StorageDependencyMinimumDependencies, - "asyncMerkleStore" | "blockTreeStore" | "unprovenMerkleStore" + "blockTreeStore" | "treeStoreCreator" > { return { - asyncStateService: { - useFactory: () => new PrismaStateService(this, this.tracer, "batch"), - }, batchStorage: { useClass: PrismaBatchStore, }, @@ -78,8 +75,8 @@ export class PrismaDatabaseConnection transactionStorage: { useClass: PrismaTransactionStorage, }, - unprovenStateService: { - useFactory: () => new PrismaStateService(this, this.tracer, "block"), + stateServiceCreator: { + useClass: PrismaStateServiceCreator, }, }; } diff --git a/packages/persistance/src/RedisConnection.ts b/packages/persistance/src/RedisConnection.ts index 2fb48dd8a..b10df2978 100644 --- a/packages/persistance/src/RedisConnection.ts +++ b/packages/persistance/src/RedisConnection.ts @@ -8,6 +8,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; @@ -44,20 +45,16 @@ export class RedisConnectionModule public dependencies(): Pick< StorageDependencyMinimumDependencies, - "asyncMerkleStore" | "blockTreeStore" | "unprovenMerkleStore" + "blockTreeStore" | "treeStoreCreator" > { return { - asyncMerkleStore: { - useFactory: () => new RedisMerkleTreeStore(this, this.tracer), - }, - unprovenMerkleStore: { - useFactory: () => - new RedisMerkleTreeStore(this, this.tracer, "unproven"), - }, blockTreeStore: { useFactory: () => new RedisMerkleTreeStore(this, this.tracer, "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..447b72f0b --- /dev/null +++ b/packages/persistance/src/creators/PrismaStateServiceCreator.ts @@ -0,0 +1,38 @@ +import { + AsyncStateService, + StateServiceCreator, + Tracer, +} 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, + @inject("Tracer") private readonly tracer: Tracer + ) {} + + public async createMask( + name: string, + parent: string + ): Promise { + return new PrismaStateService(this.connection, this.tracer, name, parent); + } + + public getMask(name: string): AsyncStateService { + return new PrismaStateService(this.connection, this.tracer, name); + } + + public async mergeIntoParent(name: string): Promise { + const service = new PrismaStateService(this.connection, this.tracer, name); + await service.mergeIntoParent(); + } + + public async drop(name: string): Promise { + 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 new file mode 100644 index 000000000..7e4d30d6b --- /dev/null +++ b/packages/persistance/src/creators/RedisTreeStoreCreator.ts @@ -0,0 +1,28 @@ +import { + AsyncMerkleTreeStore, + InMemoryMerkleTreeStoreMask, + MaskGraph, + Tracer, + 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 +{ + 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 2a0d00f70..4c23a3846 100644 --- a/packages/persistance/src/services/prisma/PrismaStateService.ts +++ b/packages/persistance/src/services/prisma/PrismaStateService.ts @@ -3,6 +3,7 @@ import { StateEntry, Tracer, trace, + MaskName, } from "@proto-kit/sequencer"; import { Field } from "o1js"; import { Prisma } from "@prisma/client"; @@ -25,56 +26,88 @@ 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 tracer * @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, public readonly tracer: Tracer, 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, + }; } @trace("db.state.commit") 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) @@ -100,11 +133,12 @@ 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: { 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) @@ -112,7 +146,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())) ?? [], })); } @@ -131,16 +164,23 @@ export class PrismaStateService implements AsyncStateService { } public async createMask(name: string): Promise { - const maskId = await this.getMaskId(); - return new PrismaStateService(this.connection, this.tracer, 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, + this.tracer, + 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 @@ -158,7 +198,7 @@ export class PrismaStateService implements AsyncStateService { parent: maskId, }, data: { - parent: this.parent, + parent: parentId, }, }), client.mask.delete({ @@ -173,7 +213,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 ad61a38a6..45b4d3d49 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, trace, @@ -12,10 +13,12 @@ import type { RedisConnection } from "../../RedisConnection"; export class RedisMerkleTreeStore implements AsyncMerkleTreeStore { private cache: MerkleTreeNode[] = []; + public readonly name = "base"; + public constructor( private readonly connection: RedisConnection, public readonly tracer: Tracer, - private readonly mask: string = "base" + private readonly mask: string ) {} private getKey(node: MerkleTreeNodeQuery): string { @@ -85,4 +88,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); + } } diff --git a/packages/persistance/test/connection.test.ts b/packages/persistance/test/connection.test.ts index 5252587cb..b901a45f7 100644 --- a/packages/persistance/test/connection.test.ts +++ b/packages/persistance/test/connection.test.ts @@ -24,7 +24,7 @@ describe.skip("prisma", () => { password: "password", }; await db.start(); - const store = new RedisMerkleTreeStore(db, tracer); + const store = new RedisMerkleTreeStore(db, tracer, "base"); const cached = new CachedMerkleTreeStore(store); const tree = new RollupMerkleTree(cached); @@ -40,7 +40,7 @@ describe.skip("prisma", () => { console.log(`Root ${tree.getRoot().toBigInt()}`); - const store2 = new RedisMerkleTreeStore(db, tracer); + const store2 = new RedisMerkleTreeStore(db, tracer, "base"); const cached2 = new CachedMerkleTreeStore(store2); const tree2 = new RollupMerkleTree(cached2); diff --git a/packages/sdk/src/query/StateServiceQueryModule.ts b/packages/sdk/src/query/StateServiceQueryModule.ts index ec78f44aa..d1a1e7770 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 stateServiceCreator.getMask(MaskName.base()); + } + + public async treeStore(): Promise { + const treeStoreCreator = + this.sequencer.dependencyContainer.resolve( + "TreeStoreCreator" + ); + return 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/index.ts b/packages/sequencer/src/index.ts index 476da4de4..53e27e77a 100644 --- a/packages/sequencer/src/index.ts +++ b/packages/sequencer/src/index.ts @@ -76,6 +76,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"; @@ -90,6 +94,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 c57553c3d..9b8b9bc23 100644 --- a/packages/sequencer/src/mempool/private/PrivateMempool.ts +++ b/packages/sequencer/src/mempool/private/PrivateMempool.ts @@ -27,10 +27,11 @@ import { SequencerModulesRecord, } from "../../sequencer/executor/Sequencer"; import { CachedStateService } from "../../state/state/CachedStateService"; -import { AsyncStateService } from "../../state/async/AsyncStateService"; import { distinctByPredicate } from "../../helpers/utils"; import { Tracer } from "../../logging/Tracer"; import { trace } from "../../logging/trace"; +import { StateServiceCreator } from "../../state/masking/StateServiceCreator"; +import { MaskName } from "../../state/masking/MaskName"; type MempoolTransactionPaths = { transaction: PendingTransaction; @@ -58,8 +59,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, @inject("Tracer") public readonly tracer: Tracer ) { super(); @@ -115,7 +116,8 @@ export class PrivateMempool public async getTxs(limit?: number): Promise { const txs = await this.transactionStorage.getPendingUserTransactions(); - const baseCachedStateService = new CachedStateService(this.stateService); + const stateService = this.stateServiceCreator.getMask(MaskName.base()); + const baseCachedStateService = new CachedStateService(stateService); const networkState = (await this.getStagedNetworkState()) ?? NetworkState.empty(); diff --git a/packages/sequencer/src/protocol/production/BatchProducerModule.ts b/packages/sequencer/src/protocol/production/BatchProducerModule.ts index fcfffa5b5..14c3b2fbd 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,10 @@ 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 { MaskName } from "../../state/masking/MaskName"; import { BlockProofSerializer } from "./tasks/serializers/BlockProofSerializer"; import { BatchTracingService } from "./tracing/BatchTracingService"; @@ -47,8 +48,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 +102,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 +133,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,7 +199,11 @@ export class BatchProducerModule extends SequencerModule { throw errors.blockWithoutTxs(); } - const merkleTreeStore = new CachedMerkleTreeStore(this.merkleStore); + const mask = await this.treeStoreCreator.createMask( + "batch", + MaskName.base() + ); + const merkleTreeStore = new CachedMerkleTreeStore(mask); const trace = await this.batchTraceService.traceBatch( blocks.map((block) => block), @@ -194,6 +211,8 @@ export class BatchProducerModule extends SequencerModule { batchId ); + await this.treeStoreCreator.drop("batch"); + const proof = await this.batchFlow.executeBatch(trace, batchId); 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 a85fbcf8e..e5a1732d3 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,9 @@ import { } from "../../../storage/model/Block"; 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 { Tracer } from "../../../logging/Tracer"; import { trace } from "../../../logging/trace"; @@ -43,10 +45,12 @@ export class BlockProducerModule extends SequencerModule { @inject("Mempool") private readonly mempool: Mempool, @injectOptional("IncomingMessagesService") private readonly messageService: IncomingMessagesService | undefined, - @inject("UnprovenStateService") - private readonly unprovenStateService: AsyncStateService, - @inject("UnprovenMerkleStore") - private readonly unprovenMerkleStore: AsyncMerkleTreeStore, + @inject("StateServiceCreator") + private readonly stateServiceCreator: StateServiceCreator, + @inject("TreeStoreCreator") + private readonly treeStoreCreator: TreeStoreCreator, + // @inject("UnprovenMerkleStore") + // private readonly unprovenMerkleStore: AsyncMerkleTreeStore, @inject("BlockQueue") private readonly blockQueue: BlockQueue, @inject("BlockTreeStore") @@ -111,6 +115,16 @@ export class BlockProducerModule extends SequencerModule { @trace("block.result", ([block]) => ({ height: block.height.toString() })) public async generateMetadata(block: Block): Promise { + const height = block.height.toBigInt(); + const maskName = MaskName.block(height); + const asyncStateService = this.stateServiceCreator.getMask(maskName); + + const asyncTreeStore = await this.treeStoreCreator.createMask( + maskName, + MaskName.block(height - 1n), + MaskName.base() + ); + const traceMetadata = { height: block.height.toString(), }; @@ -118,9 +132,9 @@ export class BlockProducerModule extends SequencerModule { const { result, blockHashTreeStore, treeStore, stateService } = await this.resultService.generateMetadataForNextBlock( block, - this.unprovenMerkleStore, + asyncTreeStore, this.blockTreeStore, - this.unprovenStateService + asyncStateService ); await this.tracer.trace( @@ -132,6 +146,8 @@ export class BlockProducerModule extends SequencerModule { await stateService.mergeIntoParent(); await this.blockQueue.pushResult(result); + + await this.stateServiceCreator.mergeIntoParent(maskName); }), traceMetadata ); @@ -217,6 +233,16 @@ 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; + + return await this.stateServiceCreator.createMask( + MaskName.block(height), + MaskName.base() + ); + } + @trace("block") private async produceBlock(): Promise { this.productionInProgress = true; @@ -228,8 +254,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/protocol/production/sequencing/BlockResultService.ts b/packages/sequencer/src/protocol/production/sequencing/BlockResultService.ts index 7e5803e7e..9c5954203 100644 --- a/packages/sequencer/src/protocol/production/sequencing/BlockResultService.ts +++ b/packages/sequencer/src/protocol/production/sequencing/BlockResultService.ts @@ -30,7 +30,7 @@ import { Tracer } from "../../../logging/Tracer"; import { executeWithExecutionContext } from "./TransactionExecutionService"; -function collectStateDiff( +export function collectStateDiff( stateTransitions: UntypedStateTransition[] ): StateRecord { return stateTransitions.reduce>( @@ -44,9 +44,9 @@ function collectStateDiff( ); } -function createCombinedStateDiff( - transactions: TransactionExecutionResult[], - blockHookSTs: UntypedStateTransition[] +export function createCombinedStateDiff( + blockHookSTs: UntypedStateTransition[], + transactions: TransactionExecutionResult[] ) { // Flatten diff list into a single diff by applying them over each other return transactions @@ -55,7 +55,7 @@ function createCombinedStateDiff( .filter(({ applied }) => applied) .flatMap(({ stateTransitions }) => stateTransitions); - transitions.push(...blockHookSTs); + transitions.splice(0, 0, ...blockHookSTs); return collectStateDiff(transitions); }) @@ -65,6 +65,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 { @@ -152,28 +174,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; - } - @trace("block.result.generate", ([block]) => ({ height: block.height.toString(), })) @@ -189,13 +189,13 @@ export class BlockResultService { stateService: CachedStateService; }> { const combinedDiff = createCombinedStateDiff( - block.transactions, - block.beforeBlockStateTransitions + block.beforeBlockStateTransitions, + block.transactions ); const inMemoryStore = new CachedMerkleTreeStore(merkleTreeStore); - const tree = await this.applyStateDiff(inMemoryStore, combinedDiff); + const tree = await applyStateDiff(inMemoryStore, combinedDiff); const witnessedStateRoot = tree.getRoot(); @@ -218,7 +218,7 @@ export class BlockResultService { ); // Apply afterBlock STs to the tree - const tree2 = await this.applyStateDiff( + const tree2 = await applyStateDiff( inMemoryStore, collectStateDiff( stateTransitions.map((stateTransition) => diff --git a/packages/sequencer/src/settlement/BridgingModule.ts b/packages/sequencer/src/settlement/BridgingModule.ts index c4e407246..9fe8bf14c 100644 --- a/packages/sequencer/src/settlement/BridgingModule.ts +++ b/packages/sequencer/src/settlement/BridgingModule.ts @@ -41,9 +41,10 @@ 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"; @@ -75,8 +76,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 +358,8 @@ export class BridgingModule extends SequencerModule { const bridgeContract = this.createBridgeContract(bridgeAddress, tokenId); - const cachedStore = new CachedMerkleTreeStore(this.merkleTreeStore); + const merkleTreeStore = this.treeStoreCreator.getMask(MaskName.base()); + const cachedStore = new CachedMerkleTreeStore(merkleTreeStore); const tree = new RollupMerkleTree(cachedStore); const [withdrawalModule, withdrawalStateName] = 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..9f87a56fd --- /dev/null +++ b/packages/sequencer/src/state/masking/MaskGraph.ts @@ -0,0 +1,127 @@ +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; +}; + +type BaseMaskType = Interface & { + createMask(name: string): Promise; + name: string; +}; + +type MaskType = BaseMaskType & { + mergeIntoParent(): Promise; + updateParent(parent: Interface): void; +}; + +export class MaskGraph< + Interface, + Base extends BaseMaskType, + Mask extends MaskType, +> { + 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 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..ee994c98b --- /dev/null +++ b/packages/sequencer/src/state/masking/MaskName.ts @@ -0,0 +1,10 @@ +import { Field } from "o1js"; + +export const MaskName = { + block(height: bigint | Field) { + const heightBigint = + typeof height === "bigint" ? height : height.toBigInt(); + return `block-${heightBigint}`; + }, + base: () => "base", +}; diff --git a/packages/sequencer/src/state/masking/StateServiceCreator.ts b/packages/sequencer/src/state/masking/StateServiceCreator.ts new file mode 100644 index 000000000..da62b8785 --- /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): AsyncStateService; + 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..e9f186c79 --- /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): AsyncMerkleTreeStore; + mergeIntoParent(name: string): Promise; + drop(name: string): Promise; +} 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 5af85e235..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 ed660d8fa..135bb08ca 100644 --- a/packages/sequencer/src/storage/StorageDependencyFactory.ts +++ b/packages/sequencer/src/storage/StorageDependencyFactory.ts @@ -4,8 +4,9 @@ import { DependencyRecord, } from "@proto-kit/common"; -import { AsyncStateService } from "../state/async/AsyncStateService"; import { AsyncMerkleTreeStore } from "../state/async/AsyncMerkleTreeStore"; +import { StateServiceCreator } from "../state/masking/StateServiceCreator"; +import { TreeStoreCreator } from "../state/masking/TreeStoreCreator"; import { BatchStorage } from "./repositories/BatchStorage"; import { BlockQueue, BlockStorage } from "./repositories/BlockStorage"; @@ -14,17 +15,15 @@ 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; + 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 a8eb330ab..a27b06d9c 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, @@ -10,23 +9,19 @@ import { Database } from "../Database"; import { closeable } from "../../sequencer/builder/Closeable"; 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, - }, - asyncStateService: { - useFactory: () => new CachedStateService(undefined), - }, batchStorage: { useClass: InMemoryBatchStorage, }, @@ -36,14 +31,14 @@ export class InMemoryDatabase extends SequencerModule implements Database { blockStorage: { useToken: "BlockQueue", }, - unprovenStateService: { - useFactory: () => new CachedStateService(undefined), + 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..9b21d7c61 --- /dev/null +++ b/packages/sequencer/src/storage/inmemory/masking/InMemoryStateServiceCreator.ts @@ -0,0 +1,18 @@ +import { StateServiceCreator } from "../../../state/masking/StateServiceCreator"; +import { MaskGraph } from "../../../state/masking/MaskGraph"; +import { AsyncStateService } from "../../../state/async/AsyncStateService"; + +import { InMemoryStateServiceMask } from "./InMemoryStateServiceMask"; + +export class InMemoryStateServiceCreator + extends MaskGraph< + AsyncStateService, + InMemoryStateServiceMask, + InMemoryStateServiceMask + > + 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..5a13b4585 --- /dev/null +++ b/packages/sequencer/src/storage/inmemory/masking/InMemoryTreeStoreCreator.ts @@ -0,0 +1,21 @@ +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, + InMemoryBaseMerkleTreeStore, + InMemoryMerkleTreeStoreMask + > + implements TreeStoreCreator +{ + public constructor() { + super(new InMemoryBaseMerkleTreeStore()); + } +} diff --git a/packages/sequencer/test/integration/BlockProduction.test.ts b/packages/sequencer/test/integration/BlockProduction.test.ts index 049b5d223..d24bd2493 100644 --- a/packages/sequencer/test/integration/BlockProduction.test.ts +++ b/packages/sequencer/test/integration/BlockProduction.test.ts @@ -234,7 +234,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(); @@ -250,7 +250,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); @@ -275,14 +275,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(); @@ -304,12 +304,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 () => { @@ -443,7 +441,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( @@ -487,7 +485,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(); @@ -496,7 +494,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)); @@ -603,7 +601,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( @@ -619,7 +617,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/StorageIntegration.test.ts b/packages/sequencer/test/integration/StorageIntegration.test.ts index 88213a936..d4c07b513 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, @@ -20,6 +19,8 @@ import { StorageDependencyFactory, BlockStorage, VanillaTaskWorkerModules, + StateServiceCreator, + MaskName, } from "../../src"; import { DefaultTestingSequencerModules, @@ -63,8 +64,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; @@ -127,8 +127,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 () => { @@ -176,14 +175,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 f51c64393..cc44dfe01 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, MaskName, PrivateMempool } from "../../../src"; import { createTransaction } from "../utils"; +import { StateServiceCreator } from "../../../src/state/masking/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(MaskName.base()); return await service.get(path); }