diff --git a/packages/persistance/src/services/prisma/mappers/EventMapper.ts b/packages/persistance/src/services/prisma/mappers/EventMapper.ts index 10d1488fc..b56416a87 100644 --- a/packages/persistance/src/services/prisma/mappers/EventMapper.ts +++ b/packages/persistance/src/services/prisma/mappers/EventMapper.ts @@ -4,45 +4,53 @@ import { Field } from "o1js"; import { ObjectMapper } from "../../../ObjectMapper"; +type EventData = { + eventName: string; + data: Field[]; + source: "afterTxHook" | "beforeTxHook" | "runtime"; +}; + @singleton() -export class EventMapper - implements - ObjectMapper<{ eventName: string; data: Field[] }, Prisma.JsonObject> -{ - public mapIn(input: Prisma.JsonObject): { eventName: string; data: Field[] } { - if (input === undefined) return { eventName: "", data: [] }; +export class EventMapper implements ObjectMapper { + public mapIn(input: Prisma.JsonObject): EventData { return { eventName: input.eventName as string, data: (input.data as Prisma.JsonArray).map((field) => Field.fromJSON(field as string) ), + source: this.sourceConvert(input.source as string), }; } - public mapOut(input: { - eventName: string; - data: Field[]; - }): Prisma.JsonObject { + public mapOut(input: EventData): Prisma.JsonObject { return { eventName: input.eventName, data: input.data.map((field) => field.toString()), + source: input.source, } as Prisma.JsonObject; } + + private sourceConvert(input: string) { + if ( + input === "beforeTxHook" || + input === "afterTxHook" || + input === "runtime" + ) { + return input; + } + throw new Error( + "Event Source must be one of 'beforeTxHook', 'afterTxHook' or 'runtime'" + ); + } } @singleton() export class EventArrayMapper - implements - ObjectMapper< - { eventName: string; data: Field[] }[], - Prisma.JsonValue | undefined - > + implements ObjectMapper { public constructor(private readonly eventMapper: EventMapper) {} - public mapIn( - input: Prisma.JsonValue | undefined - ): { eventName: string; data: Field[] }[] { + public mapIn(input: Prisma.JsonValue | undefined): EventData[] { if (input === undefined) return []; if (Array.isArray(input)) { @@ -53,9 +61,7 @@ export class EventArrayMapper return []; } - public mapOut( - input: { eventName: string; data: Field[] }[] - ): Prisma.JsonValue { + public mapOut(input: EventData[]): Prisma.JsonValue { return input.map((event) => this.eventMapper.mapOut(event) ) as Prisma.JsonArray; diff --git a/packages/sequencer/src/protocol/production/sequencing/TransactionExecutionService.ts b/packages/sequencer/src/protocol/production/sequencing/TransactionExecutionService.ts index 0e42fdd78..8073d7507 100644 --- a/packages/sequencer/src/protocol/production/sequencing/TransactionExecutionService.ts +++ b/packages/sequencer/src/protocol/production/sequencing/TransactionExecutionService.ts @@ -111,8 +111,13 @@ async function decodeTransaction( } function extractEvents( - runtimeResult: RuntimeContextReducedExecutionResult -): { eventName: string; data: Field[] }[] { + runtimeResult: RuntimeContextReducedExecutionResult, + source: "afterTxHook" | "beforeTxHook" | "runtime" +): { + eventName: string; + data: Field[]; + source: "afterTxHook" | "beforeTxHook" | "runtime"; +}[] { return runtimeResult.events.reduce( (acc, event) => { if (event.condition.toBoolean()) { @@ -120,13 +125,18 @@ function extractEvents( eventName: event.eventName, // eslint-disable-next-line @typescript-eslint/no-unsafe-argument data: event.eventType.toFields(event.event), + source: source, }; acc.push(obj); } return acc; }, // eslint-disable-next-line @typescript-eslint/consistent-type-assertions - [] as { eventName: string; data: Field[] }[] + [] as { + eventName: string; + data: Field[]; + source: "afterTxHook" | "beforeTxHook" | "runtime"; + }[] ); } @@ -314,6 +324,7 @@ export class TransactionExecutionService { async (hook, hookArgs) => await hook.beforeTransaction(hookArgs), "beforeTx" ); + const beforeHookEvents = extractEvents(beforeTxHookResult, "beforeTxHook"); await recordingStateService.applyStateTransitions( beforeTxHookResult.stateTransitions @@ -363,6 +374,7 @@ export class TransactionExecutionService { async (hook, hookArgs) => await hook.afterTransaction(hookArgs), "afterTx" ); + const afterHookEvents = extractEvents(afterTxHookResult, "afterTxHook"); await recordingStateService.applyStateTransitions( afterTxHookResult.stateTransitions ); @@ -376,7 +388,7 @@ export class TransactionExecutionService { appChain.setProofsEnabled(previousProofsEnabled); // Extract sequencing results - const events = extractEvents(runtimeResult); + const runtimeResultEvents = extractEvents(runtimeResult, "runtime"); const stateTransitions = this.buildSTBatches( [ beforeTxHookResult.stateTransitions, @@ -394,7 +406,7 @@ export class TransactionExecutionService { statusMessage: runtimeResult.statusMessage, stateTransitions, - events, + events: beforeHookEvents.concat(runtimeResultEvents, afterHookEvents), }, ]; } diff --git a/packages/sequencer/src/storage/model/Block.ts b/packages/sequencer/src/storage/model/Block.ts index 8f1da00a4..823b3c8c6 100644 --- a/packages/sequencer/src/storage/model/Block.ts +++ b/packages/sequencer/src/storage/model/Block.ts @@ -20,7 +20,11 @@ export interface TransactionExecutionResult { stateTransitions: StateTransitionBatch[]; status: Bool; statusMessage?: string; - events: { eventName: string; data: Field[] }[]; + events: { + eventName: string; + data: Field[]; + source: "afterTxHook" | "beforeTxHook" | "runtime"; + }[]; } // TODO Why is Block using Fields, but BlockResult bigints? Align that towards the best option diff --git a/packages/sequencer/test/integration/BlockProduction.test.ts b/packages/sequencer/test/integration/BlockProduction.test.ts index 644427239..b24a6a732 100644 --- a/packages/sequencer/test/integration/BlockProduction.test.ts +++ b/packages/sequencer/test/integration/BlockProduction.test.ts @@ -684,11 +684,13 @@ describe("block production", () => { const firstEventReduced = { eventName: firstExpectedEvent.eventName, data: firstExpectedEvent.eventType.toFields(firstExpectedEvent.event), + source: "runtime", }; const secondEventReduced = { eventName: secondExpectedEvent.eventName, data: secondExpectedEvent.eventType.toFields(secondExpectedEvent.event), + source: "runtime", }; const block = await test.produceBlock();