diff --git a/packages/deployment/docker/lightnet/docker-compose.yml b/packages/deployment/docker/lightnet/docker-compose.yml index 093f137a5..c00d7e883 100644 --- a/packages/deployment/docker/lightnet/docker-compose.yml +++ b/packages/deployment/docker/lightnet/docker-compose.yml @@ -12,5 +12,5 @@ services: - 8080:8080 - 8181:8181 # archive endpoints - - 5432:5432 + - 5433:5432 - 8282:8282 \ No newline at end of file diff --git a/packages/indexer/src/tasks/IndexBlockTaskParameters.ts b/packages/indexer/src/tasks/IndexBlockTaskParameters.ts index 88518ca4d..9baa05a3f 100644 --- a/packages/indexer/src/tasks/IndexBlockTaskParameters.ts +++ b/packages/indexer/src/tasks/IndexBlockTaskParameters.ts @@ -2,7 +2,11 @@ import { BlockWithResult } from "@proto-kit/sequencer"; import { BlockMapper, BlockResultMapper, + StateTransitionBatchArrayMapper, + StateTransitionMapper, TransactionExecutionResultMapper, + STBatchOutput, + STArrayOutput, } from "@proto-kit/persistance"; import { injectable } from "tsyringe"; @@ -13,37 +17,90 @@ export class IndexBlockTaskParametersSerializer { public constructor( public blockMapper: BlockMapper, public blockResultMapper: BlockResultMapper, - public transactionResultMapper: TransactionExecutionResultMapper + public transactionResultMapper: TransactionExecutionResultMapper, + public stateTransitionBatchMapper: StateTransitionBatchArrayMapper, + public stateTransitionMapper: StateTransitionMapper ) {} public toJSON(parameters: IndexBlockTaskParameters): string { return JSON.stringify({ - block: this.blockMapper.mapOut(parameters.block), - transactions: parameters.block.transactions.map((tx) => - this.transactionResultMapper.mapOut(tx) - ), - result: this.blockResultMapper.mapOut(parameters.result), + block: { + ...this.blockMapper.mapOut(parameters.block), + beforeBlockStateTransitions: + parameters.block.beforeBlockStateTransitions.map((st) => + this.stateTransitionMapper.mapOut(st) + ), + }, + transactions: parameters.block.transactions.map((tx) => { + const txMap = this.transactionResultMapper.mapOut(tx); + const stBatches = this.stateTransitionBatchMapper.mapOut( + tx.stateTransitions + ); + return { + ...txMap, + stateTransitionBatch: stBatches.map(([batch, sts]) => ({ + applied: batch.applied, + stateTransitions: sts, + })), + }; + }), + result: { + ...this.blockResultMapper.mapOut(parameters.result), + afterBlockStateTransitions: + parameters.result.afterBlockStateTransitions.map((st) => + this.stateTransitionMapper.mapOut(st) + ), + }, }); } public fromJSON(json: string): IndexBlockTaskParameters { // eslint-disable-next-line @typescript-eslint/consistent-type-assertions const parsed = JSON.parse(json) as { - block: ReturnType; - transactions: ReturnType[]; - result: ReturnType; + block: ReturnType & { + beforeBlockStateTransitions: ReturnType< + StateTransitionMapper["mapOut"] + >[]; + }; + transactions: (ReturnType & { + stateTransitionBatch: (STBatchOutput & { + stateTransitions: STArrayOutput; + })[]; + })[]; + result: ReturnType & { + afterBlockStateTransitions: ReturnType< + StateTransitionMapper["mapOut"] + >[]; + }; }; - const transactions = parsed.transactions.map((tx) => - this.transactionResultMapper.mapIn(tx) - ); + const transactions = parsed.transactions.map((tx) => { + const txMapped = this.transactionResultMapper.mapIn(tx); + const stBatch = tx.stateTransitionBatch.map< + [STBatchOutput, STArrayOutput] + >((batch) => [{ applied: batch.applied }, batch.stateTransitions]); + return { + ...txMapped, + stateTransitions: this.stateTransitionBatchMapper.mapIn(stBatch), + }; + }); return { block: { ...this.blockMapper.mapIn(parsed.block), + beforeBlockStateTransitions: + parsed.block.beforeBlockStateTransitions.map((st) => + this.stateTransitionMapper.mapIn(st) + ), transactions, }, - result: this.blockResultMapper.mapIn(parsed.result), + result: { + ...this.blockResultMapper.mapIn(parsed.result), + afterBlockStateTransitions: + parsed.result.afterBlockStateTransitions.map((st) => + this.stateTransitionMapper.mapIn(st) + ), + }, }; } } diff --git a/packages/persistance/prisma/migrations/20250226134830_st_batches/migration.sql b/packages/persistance/prisma/migrations/20250226134830_st_batches/migration.sql new file mode 100644 index 000000000..cb8d04885 --- /dev/null +++ b/packages/persistance/prisma/migrations/20250226134830_st_batches/migration.sql @@ -0,0 +1,52 @@ +/* + Warnings: + + - You are about to drop the column `beforeBlockStateTransitions` on the `Block` table. All the data in the column will be lost. + - You are about to drop the column `afterBlockStateTransitions` on the `BlockResult` table. All the data in the column will be lost. + - You are about to drop the column `stateTransitions` on the `TransactionExecutionResult` table. All the data in the column will be lost. + +*/ +-- AlterTable +ALTER TABLE "Block" DROP COLUMN "beforeBlockStateTransitions"; + +-- AlterTable +ALTER TABLE "BlockResult" DROP COLUMN "afterBlockStateTransitions"; + +-- AlterTable +ALTER TABLE "TransactionExecutionResult" DROP COLUMN "stateTransitions"; + +-- CreateTable +CREATE TABLE "StateTransition" ( + "id" SERIAL NOT NULL, + "batchId" INTEGER NOT NULL, + "path" TEXT NOT NULL, + "from" TEXT[], + "fromIsSome" BOOLEAN NOT NULL, + "to" TEXT[], + "toIsSome" BOOLEAN NOT NULL, + + CONSTRAINT "StateTransition_pkey" PRIMARY KEY ("id") +); + +-- CreateTable +CREATE TABLE "StateTransitionBatch" ( + "id" SERIAL NOT NULL, + "txExecutionResultId" TEXT, + "blockId" TEXT, + "blockResultId" TEXT, + "applied" BOOLEAN NOT NULL, + + CONSTRAINT "StateTransitionBatch_pkey" PRIMARY KEY ("id") +); + +-- AddForeignKey +ALTER TABLE "StateTransition" ADD CONSTRAINT "StateTransition_batchId_fkey" FOREIGN KEY ("batchId") REFERENCES "StateTransitionBatch"("id") ON DELETE RESTRICT ON UPDATE CASCADE; + +-- AddForeignKey +ALTER TABLE "StateTransitionBatch" ADD CONSTRAINT "StateTransitionBatch_txExecutionResultId_fkey" FOREIGN KEY ("txExecutionResultId") REFERENCES "TransactionExecutionResult"("txHash") ON DELETE SET NULL ON UPDATE CASCADE; + +-- AddForeignKey +ALTER TABLE "StateTransitionBatch" ADD CONSTRAINT "StateTransitionBatch_blockId_fkey" FOREIGN KEY ("blockId") REFERENCES "Block"("hash") ON DELETE SET NULL ON UPDATE CASCADE; + +-- AddForeignKey +ALTER TABLE "StateTransitionBatch" ADD CONSTRAINT "StateTransitionBatch_blockResultId_fkey" FOREIGN KEY ("blockResultId") REFERENCES "BlockResult"("blockHash") ON DELETE SET NULL ON UPDATE CASCADE; diff --git a/packages/persistance/prisma/schema.prisma b/packages/persistance/prisma/schema.prisma index 58c214477..614733bc0 100644 --- a/packages/persistance/prisma/schema.prisma +++ b/packages/persistance/prisma/schema.prisma @@ -39,18 +39,42 @@ model Transaction { IncomingMessageBatchTransaction IncomingMessageBatchTransaction[] } +model StateTransition { + id Int @id @default(autoincrement()) + batchId Int + path String + from String[] + fromIsSome Boolean + to String[] + toIsSome Boolean + + batch StateTransitionBatch @relation(fields: [batchId], references: [id]) +} + +model StateTransitionBatch { + id Int @id @default(autoincrement()) + txExecutionResultId String? + blockId String? + blockResultId String? + applied Boolean + + transactionExecutionResult TransactionExecutionResult? @relation(fields: [txExecutionResultId], references: [txHash]) + block Block? @relation(fields: [blockId], references: [hash]) + blockResult BlockResult? @relation(fields: [blockResultId], references: [blockHash]) + stateTransitions StateTransition[] +} + model TransactionExecutionResult { - // TODO Make StateTransitionBatch and StateTransition Table - stateTransitions Json @db.Json - status Boolean - statusMessage String? - events Json @db.Json + status Boolean + statusMessage String? + events Json @db.Json tx Transaction @relation(fields: [txHash], references: [hash]) txHash String @id - block Block @relation(fields: [blockHash], references: [hash]) - blockHash String + block Block @relation(fields: [blockHash], references: [hash]) + blockHash String + stateTransitionBatch StateTransitionBatch[] } model Block { @@ -67,8 +91,6 @@ model Block { toMessagesHash String fromStateRoot String - beforeBlockStateTransitions Json @db.Json - parentHash String? @unique parent Block? @relation("Parent", fields: [parentHash], references: [hash]) successor Block? @relation("Parent") @@ -76,8 +98,9 @@ model Block { transactions TransactionExecutionResult[] result BlockResult? - batch Batch? @relation(fields: [batchHeight], references: [height]) - batchHeight Int? + batch Batch? @relation(fields: [batchHeight], references: [height]) + batchHeight Int? + stateTransitionBatch StateTransitionBatch[] } model Batch { @@ -94,14 +117,14 @@ model Batch { model BlockResult { blockHash String @id @unique - stateRoot String - blockHashRoot String - witnessedRoots String[] - afterNetworkState Json @db.Json - afterBlockStateTransitions Json @db.Json - blockHashWitness Json @db.Json + stateRoot String + blockHashRoot String + witnessedRoots String[] + afterNetworkState Json @db.Json + blockHashWitness Json @db.Json - block Block? @relation(fields: [blockHash], references: [hash]) + block Block? @relation(fields: [blockHash], references: [hash]) + stateTransitionBatch StateTransitionBatch[] } model Settlement { diff --git a/packages/persistance/src/PrismaDatabaseConnection.ts b/packages/persistance/src/PrismaDatabaseConnection.ts index 54f5cfb30..f1f9474a5 100644 --- a/packages/persistance/src/PrismaDatabaseConnection.ts +++ b/packages/persistance/src/PrismaDatabaseConnection.ts @@ -90,6 +90,8 @@ export class PrismaDatabaseConnection "Settlement", "IncomingMessageBatch", "IncomingMessageBatchTransaction", + "StateTransition", + "StateTransitionBatch", ]; await this.prismaClient.$transaction( diff --git a/packages/persistance/src/services/prisma/PrismaBlockStorage.ts b/packages/persistance/src/services/prisma/PrismaBlockStorage.ts index 51ebaed62..949626182 100644 --- a/packages/persistance/src/services/prisma/PrismaBlockStorage.ts +++ b/packages/persistance/src/services/prisma/PrismaBlockStorage.ts @@ -12,6 +12,8 @@ import { log } from "@proto-kit/common"; import { Prisma, TransactionExecutionResult as DBTransactionExecutionResult, + StateTransition as DBStateTransition, + StateTransitionBatch as DBStateTransitionBatch, } from "@prisma/client"; import { inject, injectable } from "tsyringe"; @@ -23,6 +25,12 @@ import { } from "./mappers/TransactionMapper"; import { BlockResultMapper } from "./mappers/BlockResultMapper"; import { BlockMapper } from "./mappers/BlockMapper"; +import { + StateTransitionBatchArrayMapper, + StateTransitionMapper, + STBatchOutput, + STArrayOutput, +} from "./mappers/StateTransitionMapper"; @injectable() export class PrismaBlockStorage @@ -33,7 +41,9 @@ export class PrismaBlockStorage private readonly transactionResultMapper: TransactionExecutionResultMapper, private readonly transactionMapper: TransactionMapper, private readonly blockResultMapper: BlockResultMapper, - private readonly blockMapper: BlockMapper + private readonly blockMapper: BlockMapper, + private readonly stateTransitionBatchMapper: StateTransitionBatchArrayMapper, + private readonly stateTransitionMapper: StateTransitionMapper ) {} private async getBlockByQuery( @@ -45,25 +55,67 @@ export class PrismaBlockStorage transactions: { include: { tx: true, + stateTransitionBatch: { + include: { + stateTransitions: true, + }, + }, + }, + }, + stateTransitionBatch: { + include: { + stateTransitions: true, + }, + }, + result: { + include: { + stateTransitionBatch: { + include: { + stateTransitions: true, + }, + }, }, }, - result: true, }, }); if (dbResult === null) { return undefined; } const transactions = dbResult.transactions.map( - (txresult) => this.transactionResultMapper.mapIn([txresult, txresult.tx]) + (txresult) => { + const txExecResult = this.transactionResultMapper.mapIn([ + txresult, + txresult.tx, + ]); + const stBatch = txresult.stateTransitionBatch.map< + [STBatchOutput, STArrayOutput] + >((batch) => [{ applied: batch.applied }, batch.stateTransitions]); + return { + ...txExecResult, + stateTransitions: this.stateTransitionBatchMapper.mapIn(stBatch), + }; + } ); return { block: { ...this.blockMapper.mapIn(dbResult), + beforeBlockStateTransitions: + // Each block should just have one batch of STs associated with it + dbResult.stateTransitionBatch[0].stateTransitions.map((st) => + this.stateTransitionMapper.mapIn(st) + ), transactions, }, result: dbResult.result - ? this.blockResultMapper.mapIn(dbResult.result) + ? { + ...this.blockResultMapper.mapIn(dbResult.result), + afterBlockStateTransitions: + // Each block should just have one batch of STs assoicated with it + dbResult.stateTransitionBatch[0].stateTransitions.map((st) => + this.stateTransitionMapper.mapIn(st) + ), + } : undefined, }; } @@ -106,13 +158,24 @@ export class PrismaBlockStorage await prismaClient.block.create({ data: { ...encodedBlock, - beforeBlockStateTransitions: - encodedBlock.beforeBlockStateTransitions as Prisma.InputJsonArray, beforeNetworkState: encodedBlock.beforeNetworkState as Prisma.InputJsonObject, duringNetworkState: encodedBlock.duringNetworkState as Prisma.InputJsonObject, - + stateTransitionBatch: { + create: [ + { + applied: true, + stateTransitions: { + createMany: { + data: block.beforeBlockStateTransitions.map((st) => + this.stateTransitionMapper.mapOut(st) + ), + }, + }, + }, + ], + }, transactions: { createMany: { data: transactions.map((tx) => { @@ -120,30 +183,62 @@ export class PrismaBlockStorage status: tx.status, statusMessage: tx.statusMessage, txHash: tx.txHash, - - stateTransitions: tx.stateTransitions as Prisma.InputJsonArray, events: tx.events as Prisma.InputJsonArray, }; }), skipDuplicates: true, }, }, - batchHeight: undefined, }, }); + + const stateTransitionBatches = block.transactions.flatMap((tx) => { + const batches = this.stateTransitionBatchMapper.mapOut( + tx.stateTransitions + ); + const resultMapper = this.transactionResultMapper.mapOut(tx)[0]; + return batches.map((batch, index) => ({ + ...batch[0], + txExecutionResultId: resultMapper.txHash, + stateTransitions: batch[1], + })); + }); + + await prismaClient.$transaction( + stateTransitionBatches.map((batch) => + prismaClient.stateTransitionBatch.create({ + data: { + ...batch, + stateTransitions: { + create: batch.stateTransitions, + }, + }, + }) + ) + ); } public async pushResult(result: BlockResult): Promise { const encoded = this.blockResultMapper.mapOut(result); + const batches = this.stateTransitionBatchMapper.mapOut([ + { stateTransitions: result.afterBlockStateTransitions, applied: true }, + ]); await this.connection.prismaClient.blockResult.create({ data: { afterNetworkState: encoded.afterNetworkState as Prisma.InputJsonValue, blockHashWitness: encoded.blockHashWitness as Prisma.InputJsonValue, - afterBlockStateTransitions: - encoded.afterBlockStateTransitions as Prisma.InputJsonValue, - + stateTransitionBatch: { + create: batches.map(([stBatch, sts]) => { + return { + ...stBatch, + stateTransitions: { + create: sts, + }, + }; + }), + }, stateRoot: encoded.stateRoot, blockHash: encoded.blockHash, blockHashRoot: encoded.blockHashRoot, @@ -206,9 +301,27 @@ export class PrismaBlockStorage transactions: { include: { tx: true, + stateTransitionBatch: { + include: { + stateTransitions: true, + }, + }, + }, + }, + stateTransitionBatch: { + include: { + stateTransitions: true, + }, + }, + result: { + include: { + stateTransitionBatch: { + include: { + stateTransitions: true, + }, + }, }, }, - result: true, }, orderBy: { height: Prisma.SortOrder.asc, @@ -218,7 +331,23 @@ export class PrismaBlockStorage return blocks.map((block, index) => { const transactions = block.transactions.map( (txresult) => { - return this.transactionResultMapper.mapIn([txresult, txresult.tx]); + const txExecResult = this.transactionResultMapper.mapIn([ + txresult, + txresult.tx, + ]); + const stBatch = txresult.stateTransitionBatch.map< + [ + Omit< + DBStateTransitionBatch, + "txExecutionResultId" | "id" | "blockId" | "blockResultId" + >, + Omit[], + ] + >((batch) => [{ applied: batch.applied }, batch.stateTransitions]); + return { + ...txExecResult, + stateTransitions: this.stateTransitionBatchMapper.mapIn(stBatch), + }; } ); const decodedBlock = this.blockMapper.mapIn(block); @@ -233,8 +362,20 @@ export class PrismaBlockStorage } return { - block: decodedBlock, - result: this.blockResultMapper.mapIn(result), + block: { + ...decodedBlock, + beforeBlockStateTransitions: + block.stateTransitionBatch[0].stateTransitions.map((st) => + this.stateTransitionMapper.mapIn(st) + ), + }, + result: { + ...this.blockResultMapper.mapIn(result), + afterBlockStateTransitions: + result.stateTransitionBatch[0].stateTransitions.map((st) => + this.stateTransitionMapper.mapIn(st) + ), + }, }; }); } diff --git a/packages/persistance/src/services/prisma/mappers/BlockMapper.ts b/packages/persistance/src/services/prisma/mappers/BlockMapper.ts index bd5ee9b06..f360207b4 100644 --- a/packages/persistance/src/services/prisma/mappers/BlockMapper.ts +++ b/packages/persistance/src/services/prisma/mappers/BlockMapper.ts @@ -6,15 +6,18 @@ import { Field } from "o1js"; import { ObjectMapper } from "../../../ObjectMapper"; -import { StateTransitionArrayMapper } from "./StateTransitionMapper"; +import { StateTransitionBatchArrayMapper } from "./StateTransitionMapper"; @singleton() -export class BlockMapper implements ObjectMapper { +export class BlockMapper + implements + ObjectMapper, PrismaBlock> +{ public constructor( - private readonly stArrayMapper: StateTransitionArrayMapper + private readonly stArrayMapper: StateTransitionBatchArrayMapper ) {} - public mapIn(input: PrismaBlock): Block { + public mapIn(input: PrismaBlock): Omit { return { transactions: [], @@ -41,14 +44,12 @@ export class BlockMapper implements ObjectMapper { transactionsHash: Field(input.transactionsHash), previousBlockHash: input.parentHash !== null ? Field(input.parentHash) : undefined, - - beforeBlockStateTransitions: this.stArrayMapper.mapIn( - input.beforeBlockStateTransitions - ), }; } - public mapOut(input: Block): PrismaBlock { + public mapOut( + input: Omit + ): PrismaBlock { return { height: Number(input.height.toBigInt()), beforeNetworkState: NetworkState.toJSON(input.networkState.before), @@ -64,10 +65,6 @@ export class BlockMapper implements ObjectMapper { transactionsHash: input.transactionsHash.toString(), parentHash: input.previousBlockHash?.toString() ?? null, batchHeight: null, - - beforeBlockStateTransitions: this.stArrayMapper.mapOut( - input.beforeBlockStateTransitions - ), }; } } diff --git a/packages/persistance/src/services/prisma/mappers/BlockResultMapper.ts b/packages/persistance/src/services/prisma/mappers/BlockResultMapper.ts index fd65c6d02..605cd1763 100644 --- a/packages/persistance/src/services/prisma/mappers/BlockResultMapper.ts +++ b/packages/persistance/src/services/prisma/mappers/BlockResultMapper.ts @@ -5,17 +5,23 @@ import { BlockHashMerkleTreeWitness, NetworkState } from "@proto-kit/protocol"; import { ObjectMapper } from "../../../ObjectMapper"; -import { StateTransitionArrayMapper } from "./StateTransitionMapper"; +import { StateTransitionBatchArrayMapper } from "./StateTransitionMapper"; @singleton() export class BlockResultMapper - implements ObjectMapper + implements + ObjectMapper< + Omit, + DBBlockResult + > { public constructor( - private readonly stArrayMapper: StateTransitionArrayMapper + private readonly stArrayMapper: StateTransitionBatchArrayMapper ) {} - public mapIn(input: DBBlockResult): BlockResult { + public mapIn( + input: DBBlockResult + ): Omit { return { afterNetworkState: new NetworkState( // eslint-disable-next-line @typescript-eslint/no-unsafe-argument @@ -25,11 +31,10 @@ export class BlockResultMapper stateRoot: BigInt(input.stateRoot), blockHashRoot: BigInt(input.blockHashRoot), blockHashWitness: new BlockHashMerkleTreeWitness( - // eslint-disable-next-line @typescript-eslint/no-unsafe-argument - BlockHashMerkleTreeWitness.fromJSON(input.blockHashWitness as any) - ), - afterBlockStateTransitions: this.stArrayMapper.mapIn( - input.afterBlockStateTransitions + BlockHashMerkleTreeWitness.fromJSON( + // eslint-disable-next-line @typescript-eslint/no-unsafe-argument + input.blockHashWitness as any + ) ), blockHash: BigInt(input.blockHash), @@ -37,7 +42,9 @@ export class BlockResultMapper }; } - public mapOut(input: BlockResult): DBBlockResult { + public mapOut( + input: Omit + ): DBBlockResult { return { stateRoot: input.stateRoot.toString(), blockHash: input.blockHash.toString(), @@ -46,9 +53,6 @@ export class BlockResultMapper blockHashWitness: BlockHashMerkleTreeWitness.toJSON( input.blockHashWitness ), - afterBlockStateTransitions: this.stArrayMapper.mapOut( - input.afterBlockStateTransitions - ), afterNetworkState: NetworkState.toJSON(input.afterNetworkState), witnessedRoots: [input.witnessedRoots[0].toString()], diff --git a/packages/persistance/src/services/prisma/mappers/StateTransitionMapper.ts b/packages/persistance/src/services/prisma/mappers/StateTransitionMapper.ts index ed35da7b3..5af3d9363 100644 --- a/packages/persistance/src/services/prisma/mappers/StateTransitionMapper.ts +++ b/packages/persistance/src/services/prisma/mappers/StateTransitionMapper.ts @@ -3,78 +3,96 @@ import { StateTransitionBatch, UntypedStateTransition, } from "@proto-kit/sequencer"; -import { Prisma } from "@prisma/client"; +import { + StateTransitionBatch as DBStateTransitionBatch, + StateTransition as DBStateTransition, +} from "@prisma/client"; import { ObjectMapper } from "../../../ObjectMapper"; -@singleton() -export class StateTransitionMapper - implements ObjectMapper -{ - public mapIn(input: Prisma.JsonObject): UntypedStateTransition { - // eslint-disable-next-line @typescript-eslint/no-unsafe-argument - return UntypedStateTransition.fromJSON(input as any); - } - - public mapOut(input: UntypedStateTransition): Prisma.JsonObject { - return input.toJSON(); - } -} +export type STBatchOutput = Omit< + DBStateTransitionBatch, + "txExecutionResultId" | "id" | "blockId" | "blockResultId" +>; +export type STArrayOutput = Omit[]; @singleton() -export class StateTransitionArrayMapper +export class StateTransitionMapper implements - ObjectMapper + ObjectMapper< + UntypedStateTransition, + Omit + > { - public constructor(private readonly stMapper: StateTransitionMapper) {} - - public mapIn(input: Prisma.JsonValue | undefined): UntypedStateTransition[] { - if (input === undefined) return []; - - if (Array.isArray(input)) { - return (input as Prisma.JsonArray).map((stJson) => - this.stMapper.mapIn(stJson as Prisma.JsonObject) - ); - } - return []; + public mapOut( + input: UntypedStateTransition + ): Omit { + const json = input.toJSON(); + return { + path: json.path, + from: json.from.value, + fromIsSome: json.from.isSome, + to: json.to.value, + toIsSome: json.to.isSome, + }; } - public mapOut(input: UntypedStateTransition[]): Prisma.JsonValue { - return input.map((st) => this.stMapper.mapOut(st)) as Prisma.JsonArray; + public mapIn( + input: Omit + ): UntypedStateTransition { + return UntypedStateTransition.fromJSON({ + path: input.path, + from: { + isSome: input.fromIsSome, + value: input.from, + isForcedSome: false, + }, + to: { + isSome: input.toIsSome, + value: input.to, + isForcedSome: false, + }, + }); } } @singleton() export class StateTransitionBatchArrayMapper - implements ObjectMapper + implements + ObjectMapper< + StateTransitionBatch[], + [ + Omit< + DBStateTransitionBatch, + "txExecutionResultId" | "id" | "blockId" | "blockResultId" + >, + Omit[], + ][] + > { public constructor( - private readonly stArrayMapper: StateTransitionArrayMapper + private readonly stateTransitionMapper: StateTransitionMapper ) {} - public mapOut(input: StateTransitionBatch[]): Prisma.JsonValue { - return input.map((st) => ({ - stateTransitions: this.stArrayMapper.mapOut( - st.stateTransitions - ) as Prisma.JsonArray, - applied: st.applied, - })); + public mapOut( + input: StateTransitionBatch[] + ): [STBatchOutput, STArrayOutput][] { + return input.map((stBatch) => [ + { + applied: stBatch.applied, + }, + stBatch.stateTransitions.map((st) => + this.stateTransitionMapper.mapOut(st) + ), + ]); } - public mapIn(input: Prisma.JsonValue): StateTransitionBatch[] { - if (input === undefined) return []; - - if (Array.isArray(input)) { - return (input as Prisma.JsonArray).map((stJson) => { - const batchJsonObject = stJson as Prisma.JsonObject; - return { - stateTransitions: this.stArrayMapper.mapIn( - batchJsonObject.stateTransitions - ), - applied: batchJsonObject.applied as boolean, - }; - }); - } - return []; + public mapIn( + input: [STBatchOutput, STArrayOutput][] + ): StateTransitionBatch[] { + return input.map((x) => ({ + applied: x[0].applied, + stateTransitions: x[1].map((st) => this.stateTransitionMapper.mapIn(st)), + })); } } diff --git a/packages/persistance/src/services/prisma/mappers/TransactionMapper.ts b/packages/persistance/src/services/prisma/mappers/TransactionMapper.ts index 1e242dace..84014988d 100644 --- a/packages/persistance/src/services/prisma/mappers/TransactionMapper.ts +++ b/packages/persistance/src/services/prisma/mappers/TransactionMapper.ts @@ -11,8 +11,8 @@ import { Bool } from "o1js"; import { ObjectMapper } from "../../../ObjectMapper"; -import { StateTransitionBatchArrayMapper } from "./StateTransitionMapper"; import { EventArrayMapper } from "./EventMapper"; +import { StateTransitionBatchArrayMapper } from "./StateTransitionMapper"; @singleton() @injectable() @@ -49,39 +49,35 @@ export class TransactionMapper export class TransactionExecutionResultMapper implements ObjectMapper< - TransactionExecutionResult, + Omit, [Omit, DBTransaction] > { public constructor( private readonly transactionMapper: TransactionMapper, - private readonly stBatchMapper: StateTransitionBatchArrayMapper, - private readonly eventArrayMapper: EventArrayMapper + private readonly eventArrayMapper: EventArrayMapper, + private readonly stBatchArrayMapper: StateTransitionBatchArrayMapper ) {} public mapIn( input: [Omit, DBTransaction] - ): TransactionExecutionResult { + ): Omit { const executionResult = input[0]; return { tx: this.transactionMapper.mapIn(input[1]), status: Bool(executionResult.status), statusMessage: executionResult.statusMessage ?? undefined, - stateTransitions: this.stBatchMapper.mapIn( - executionResult.stateTransitions - ), events: this.eventArrayMapper.mapIn(executionResult.events), }; } mapOut( - input: TransactionExecutionResult + input: Omit ): [Omit, DBTransaction] { const tx = this.transactionMapper.mapOut(input.tx); const executionResult = { status: input.status.toBoolean(), statusMessage: input.statusMessage ?? null, - stateTransitions: this.stBatchMapper.mapOut(input.stateTransitions), events: this.eventArrayMapper.mapOut(input.events), txHash: tx.hash, }; diff --git a/packages/persistance/test/stMapper.test.ts b/packages/persistance/test/stMapper.test.ts new file mode 100644 index 000000000..d3a2306b8 --- /dev/null +++ b/packages/persistance/test/stMapper.test.ts @@ -0,0 +1,46 @@ +import "reflect-metadata"; + +import { UntypedStateTransition } from "@proto-kit/sequencer"; + +import { StateTransitionMapper } from "../src"; + +describe("StMapper", () => { + it.each([ + { + path: "1234", + from: { isSome: true, value: ["12345"], isForcedSome: false }, + to: { isSome: true, value: ["6789"], isForcedSome: false }, + }, + { + path: "5678", + from: { isSome: false, value: [], isForcedSome: false }, + to: { isSome: false, value: [], isForcedSome: false }, + }, + ])("MapOut to MapIn", (input) => { + const untypedTransition = UntypedStateTransition.fromJSON(input); + const stMapper = new StateTransitionMapper(); + const result = stMapper.mapIn(stMapper.mapOut(untypedTransition)).toJSON(); + expect(result).toEqual(input); + }); + + it.each([ + { + path: "1234", + from: ["12345"], + fromIsSome: false, + to: ["6789"], + toIsSome: true, + }, + { + path: "5678", + from: [], + fromIsSome: false, + to: [], + toIsSome: false, + }, + ])("MapIn to MapOut", (input) => { + const stMapper = new StateTransitionMapper(); + const result = stMapper.mapOut(stMapper.mapIn(input)); + expect(result).toEqual(input); + }); +}); diff --git a/packages/processor/src/indexer/BlockFetching.ts b/packages/processor/src/indexer/BlockFetching.ts index 722f691d3..e35ec5335 100644 --- a/packages/processor/src/indexer/BlockFetching.ts +++ b/packages/processor/src/indexer/BlockFetching.ts @@ -4,11 +4,17 @@ import { BlockResult as PrismaBlockResult, Transaction as PrismaTransaction, TransactionExecutionResult as PrismaTransactionExecutionResult, + StateTransition as PrismaStateTransition, + StateTransitionBatch as PrismaStateTransitionBatch, } from "@prisma/client"; import { BlockMapper, BlockResultMapper, TransactionExecutionResultMapper, + StateTransitionMapper, + StateTransitionBatchArrayMapper, + STBatchOutput, + STArrayOutput, } from "@proto-kit/persistance"; import { log } from "@proto-kit/common"; import { injectable } from "tsyringe"; @@ -23,10 +29,20 @@ export interface BlockFetchingConfig { export interface BlockResponse { data: { findFirstBlock: PrismaBlock & { - result: PrismaBlockResult; + stateTransitionBatch: (PrismaStateTransitionBatch & { + stateTransitions: PrismaStateTransition[]; + })[]; + result: PrismaBlockResult & { + stateTransitionBatch: (PrismaStateTransitionBatch & { + stateTransitions: PrismaStateTransition[]; + })[]; + }; } & { transactions: (PrismaTransactionExecutionResult & { tx: PrismaTransaction; + stateTransitionBatch: (PrismaStateTransitionBatch & { + stateTransitions: PrismaStateTransition[]; + })[]; })[]; }; }; @@ -37,7 +53,9 @@ export class BlockFetching extends ProcessorModule { public constructor( public blockMapper: BlockMapper, public blockResultMapper: BlockResultMapper, - public transactionResultMapper: TransactionExecutionResultMapper + public transactionResultMapper: TransactionExecutionResultMapper, + private readonly stateTransitionBatchMapper: StateTransitionBatchArrayMapper, + private readonly stateTransitionMapper: StateTransitionMapper ) { super(); } @@ -63,6 +81,14 @@ export class BlockFetching extends ProcessorModule { fromMessagesHash toMessagesHash transactionsHash + stateTransitionBatch { + applied, + stateTransition { + path, + from, + to, + } + } parent { hash } @@ -73,6 +99,13 @@ export class BlockFetching extends ProcessorModule { blockHashWitness, blockStateTransitions, blockHash, + stateTransitionBatch { + stateTransition { + path, + from, + to, + } + } } transactions { stateTransitions @@ -80,6 +113,13 @@ export class BlockFetching extends ProcessorModule { status statusMessage events + stateTransitionBatch { + stateTransition { + path, + from, + to, + } + } tx { hash methodId @@ -115,13 +155,33 @@ export class BlockFetching extends ProcessorModule { return undefined; } - const block = this.blockMapper.mapIn(parsedResponse?.data.findFirstBlock); - const result = this.blockResultMapper.mapIn( - parsedResponse?.data.findFirstBlock.result - ); + const block = { + ...this.blockMapper.mapIn(parsedResponse?.data.findFirstBlock), + beforeBlockStateTransitions: + parsedResponse.data.findFirstBlock.stateTransitionBatch[0].stateTransitions.map( + (st) => this.stateTransitionMapper.mapIn(st) + ), + }; + const result = { + ...this.blockResultMapper.mapIn( + parsedResponse?.data.findFirstBlock.result + ), + afterBlockStateTransitions: + parsedResponse.data.findFirstBlock.result.stateTransitionBatch[0].stateTransitions.map( + (st) => this.stateTransitionMapper.mapIn(st) + ), + }; + const transactions = parsedResponse?.data.findFirstBlock.transactions.map( (tx) => { - return this.transactionResultMapper.mapIn([tx, tx.tx]); + const txMapped = this.transactionResultMapper.mapIn([tx, tx.tx]); + const stBatch = tx.stateTransitionBatch.map< + [STBatchOutput, STArrayOutput] + >((batch) => [{ applied: batch.applied }, batch.stateTransitions]); + return { + ...txMapped, + stateTransitions: this.stateTransitionBatchMapper.mapIn(stBatch), + }; } );