From 0f3aae47b3ba0dbe7940fe5a061aac05f4caab86 Mon Sep 17 00:00:00 2001 From: 0xyaco Date: Fri, 16 Aug 2024 18:25:38 -0300 Subject: [PATCH] feat: implement EboActor.onRequestFinalized handler (#23) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit # 🤖 Linear Closes GRT-84 ## Description Handle the `RequestFinalized` event. It simply terminates the `EboActor` and executes a callback that can be used by the `EboProcessor` to remove the request from the `Map`. --- packages/automated-dispute/src/eboActor.ts | 29 +++++++- .../automated-dispute/src/types/events.ts | 13 +--- .../tests/eboActor/mocks/index.ts | 5 ++ .../tests/eboActor/onRequestCreated.spec.ts | 6 ++ .../tests/eboActor/onRequestFinalized.spec.ts | 69 +++++++++++++++++++ 5 files changed, 109 insertions(+), 13 deletions(-) create mode 100644 packages/automated-dispute/tests/eboActor/onRequestFinalized.spec.ts diff --git a/packages/automated-dispute/src/eboActor.ts b/packages/automated-dispute/src/eboActor.ts index 8298a97..8be489a 100644 --- a/packages/automated-dispute/src/eboActor.ts +++ b/packages/automated-dispute/src/eboActor.ts @@ -10,17 +10,32 @@ import { ProtocolProvider } from "./protocolProvider.js"; import { EboEvent } from "./types/events.js"; import { Dispute, Request, Response, ResponseBody } from "./types/prophet.js"; +type OnTerminateActorCallback = (request: Request) => Promise; + /** * Actor that handles a singular Prophet's request asking for the block number that corresponds * to an instant on an indexed chain. */ export class EboActor { + /** + * Creates an `EboActor` instance. + * + * @param actorRequest.id request ID this actor will handle + * @param actorRequest.epoch requested epoch + * @param actorRequest.epoch requested epoch's timestamp + * @param onTerminate callback to be run when this instance is being terminated + * @param protocolProvider a `ProtocolProvider` instance + * @param blockNumberService a `BlockNumberService` instance + * @param registry an `EboRegistry` instance + * @param logger an `ILogger` instance + */ constructor( private readonly actorRequest: { id: string; epoch: bigint; epochTimestamp: bigint; }, + private readonly onTerminate: OnTerminateActorCallback, private readonly protocolProvider: ProtocolProvider, private readonly blockNumberService: BlockNumberService, private readonly registry: EboRegistry, @@ -332,9 +347,17 @@ export class EboActor { } } - public async onFinalizeRequest(_event: EboEvent<"RequestFinalizable">): Promise { - // TODO: implement - return; + /** + * Handle the `ResponseFinalized` event. + * + * @param event `ResponseFinalized` event + */ + public async onRequestFinalized(event: EboEvent<"RequestFinalized">): Promise { + this.shouldHandleRequest(event.metadata.requestId); + + const request = this.getActorRequest(); + + await this.onTerminate(request); } public async onDisputeStatusChanged(_event: EboEvent<"DisputeStatusChanged">): Promise { diff --git a/packages/automated-dispute/src/types/events.ts b/packages/automated-dispute/src/types/events.ts index 85f4f6f..40451d8 100644 --- a/packages/automated-dispute/src/types/events.ts +++ b/packages/automated-dispute/src/types/events.ts @@ -10,7 +10,6 @@ export type EboEventName = | "ResponseDisputed" | "DisputeStatusChanged" | "DisputeEscalated" - | "RequestFinalizable" | "RequestFinalized"; export interface NewEpoch { @@ -49,10 +48,6 @@ export interface DisputeEscalated { blockNumber: bigint; } -export interface RequestFinalizable { - requestId: string; -} - export interface RequestFinalized { requestId: string; responseId: string; @@ -72,11 +67,9 @@ export type EboEventData = E extends "NewEpoch" ? DisputeStatusChanged : E extends "DisputeEscalated" ? DisputeEscalated - : E extends "RequestFinalizable" - ? RequestFinalizable - : E extends "RequestFinalized" - ? RequestFinalized - : never; + : E extends "RequestFinalized" + ? RequestFinalized + : never; export type EboEvent = { name: T; diff --git a/packages/automated-dispute/tests/eboActor/mocks/index.ts b/packages/automated-dispute/tests/eboActor/mocks/index.ts index 2f85b11..47d5039 100644 --- a/packages/automated-dispute/tests/eboActor/mocks/index.ts +++ b/packages/automated-dispute/tests/eboActor/mocks/index.ts @@ -1,6 +1,7 @@ import { BlockNumberService } from "@ebo-agent/blocknumber"; import { Caip2ChainId } from "@ebo-agent/blocknumber/dist/types"; import { ILogger } from "@ebo-agent/shared"; +import { vi } from "vitest"; import { EboActor } from "../../../src/eboActor"; import { EboMemoryRegistry } from "../../../src/eboMemoryRegistry"; @@ -18,6 +19,8 @@ import { DEFAULT_MOCKED_PROTOCOL_CONTRACTS } from "../fixtures"; function buildEboActor(request: Request, logger: ILogger) { const { id, chainId, epoch, epochTimestamp } = request; + const onTerminate = vi.fn(); + const protocolProviderRpcUrls = ["http://localhost:8538"]; const protocolProvider = new ProtocolProvider( protocolProviderRpcUrls, @@ -33,6 +36,7 @@ function buildEboActor(request: Request, logger: ILogger) { const actor = new EboActor( { id, epoch, epochTimestamp }, + onTerminate, protocolProvider, blockNumberService, registry, @@ -41,6 +45,7 @@ function buildEboActor(request: Request, logger: ILogger) { return { actor, + onTerminate, protocolProvider, blockNumberService, registry, diff --git a/packages/automated-dispute/tests/eboActor/onRequestCreated.spec.ts b/packages/automated-dispute/tests/eboActor/onRequestCreated.spec.ts index 1f40dc8..5303301 100644 --- a/packages/automated-dispute/tests/eboActor/onRequestCreated.spec.ts +++ b/packages/automated-dispute/tests/eboActor/onRequestCreated.spec.ts @@ -45,6 +45,8 @@ describe("EboActor", () => { }, }; + const onTerminate = vi.fn(); + let protocolProvider: ProtocolProvider; let blockNumberService: BlockNumberService; let registry: EboMemoryRegistry; @@ -88,6 +90,7 @@ describe("EboActor", () => { const actor = new EboActor( requestConfig, + onTerminate, protocolProvider, blockNumberService, registry, @@ -130,6 +133,7 @@ describe("EboActor", () => { const actor = new EboActor( requestConfig, + onTerminate, protocolProvider, blockNumberService, registry, @@ -179,6 +183,7 @@ describe("EboActor", () => { const actor = new EboActor( requestConfig, + onTerminate, protocolProvider, blockNumberService, registry, @@ -201,6 +206,7 @@ describe("EboActor", () => { const actor = new EboActor( requestConfig, + onTerminate, protocolProvider, blockNumberService, registry, diff --git a/packages/automated-dispute/tests/eboActor/onRequestFinalized.spec.ts b/packages/automated-dispute/tests/eboActor/onRequestFinalized.spec.ts new file mode 100644 index 0000000..0c40792 --- /dev/null +++ b/packages/automated-dispute/tests/eboActor/onRequestFinalized.spec.ts @@ -0,0 +1,69 @@ +import { ILogger } from "@ebo-agent/shared"; +import { describe, expect, it, vi } from "vitest"; + +import { InvalidActorState } from "../../src/exceptions/invalidActorState.exception.js"; +import { EboEvent } from "../../src/types/events.js"; +import { DEFAULT_MOCKED_REQUEST_CREATED_DATA } from "./fixtures.js"; +import mocks from "./mocks/index.js"; + +const logger: ILogger = { + info: vi.fn(), + warn: vi.fn(), + error: vi.fn(), + debug: vi.fn(), +}; + +describe("EboActor", () => { + describe("onRequestFinalized", () => { + const actorRequest = DEFAULT_MOCKED_REQUEST_CREATED_DATA; + + const event: EboEvent<"RequestFinalized"> = { + name: "RequestFinalized", + blockNumber: 1n, + logIndex: 1, + metadata: { + blockNumber: 1n, + caller: "0x01", + requestId: actorRequest.id, + responseId: "0x02", + }, + }; + + it("executes the actor's callback during termination", async () => { + const { actor, onTerminate, registry } = mocks.buildEboActor(actorRequest, logger); + + vi.spyOn(registry, "getRequest").mockReturnValue(actorRequest); + + onTerminate.mockImplementation(() => Promise.resolve()); + + await actor.onRequestFinalized(event); + + expect(onTerminate).toHaveBeenCalledWith(actorRequest); + }); + + it("throws if the event's request is not handled by actor", () => { + const { actor } = mocks.buildEboActor(actorRequest, logger); + + const otherRequestEvent = { + ...event, + metadata: { + ...event.metadata, + requestId: actorRequest.id + "123", + }, + }; + + expect(actor.onRequestFinalized(otherRequestEvent)).rejects.toThrow(InvalidActorState); + }); + + // The one who defines the callback is responsible for handling callback errors + it("throws if the callback throws", () => { + const { actor, onTerminate } = mocks.buildEboActor(actorRequest, logger); + + onTerminate.mockImplementation(() => { + throw new Error(); + }); + + expect(actor.onRequestFinalized(event)).rejects.toThrow(InvalidActorState); + }); + }); +});