From fccd01fb5d5f111d9ea05036aa44f4bdb154ae9f Mon Sep 17 00:00:00 2001 From: 0xyaco Date: Thu, 5 Sep 2024 15:01:35 -0300 Subject: [PATCH] feat: on last event (#34) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit # 🤖 Linear Closes GRT-153 ## Description * Renames `onNewEvent` to `onLastEvent` (seems more accurate) * Uses the new "last event" handlers during the enqueued events processing * Fixes last event handlers tests (most of their cases were skipped until this was implemented) * Refactors the `shouldHandleEvent` function to reflect its actual name lol --- packages/automated-dispute/src/eboActor.ts | 77 +++++--- .../automated-dispute/src/types/events.ts | 34 ++-- .../eboActor/onDisputeStatusChanged.spec.ts | 22 ++- .../tests/eboActor/onRequestCreated.spec.ts | 168 ++++-------------- .../tests/eboActor/onRequestFinalized.spec.ts | 24 +-- .../tests/eboActor/onResponseDisputed.spec.ts | 92 ++++------ .../tests/eboActor/onResponseProposed.spec.ts | 35 ++-- .../tests/services/eboActor.spec.ts | 18 +- .../tests/services/eboProcessor.spec.ts | 55 +++--- 9 files changed, 208 insertions(+), 317 deletions(-) diff --git a/packages/automated-dispute/src/eboActor.ts b/packages/automated-dispute/src/eboActor.ts index dc9ed53..1bb9b7d 100644 --- a/packages/automated-dispute/src/eboActor.ts +++ b/packages/automated-dispute/src/eboActor.ts @@ -92,7 +92,7 @@ export class EboActor { * @param event EBO event */ public enqueue(event: EboEvent): void { - if (this.shouldHandleRequest(event.requestId)) { + if (!this.shouldHandleRequest(event.requestId)) { this.logger.error(`The request ${event.requestId} is not handled by this actor.`); throw new RequestMismatch(this.actorRequest.id, event.requestId); @@ -143,7 +143,7 @@ export class EboActor { if (this.eventsQueue.isEmpty()) { // `event` is the last and most recent event thus // it needs to run some RPCs to keep Prophet's flow going on - await this.onNewEvent(event); + await this.onLastEvent(event); } } catch (err) { this.logger.error(`Error processing event ${event.name}: ${err}`); @@ -209,15 +209,47 @@ export class EboActor { } /** - * Handle a new event and triggers reactive interactions with smart contracts. + * Handle the last known event and triggers reactive interactions with smart contracts. * * A basic example would be reacting to a new request by proposing a response. * - * @param _event EBO event + * @param event EBO event */ - private async onNewEvent(_event: EboEvent) { - // TODO - return; + private async onLastEvent(event: EboEvent) { + switch (event.name) { + case "RequestCreated": + await this.onRequestCreated(event as EboEvent<"RequestCreated">); + + break; + + case "ResponseProposed": + await this.onResponseProposed(event as EboEvent<"ResponseProposed">); + + break; + + case "ResponseDisputed": + await this.onResponseDisputed(event as EboEvent<"ResponseDisputed">); + + break; + + case "DisputeStatusChanged": + await this.onDisputeStatusChanged(event as EboEvent<"DisputeStatusChanged">); + + break; + + case "DisputeEscalated": + await this.onDisputeEscalated(event as EboEvent<"DisputeEscalated">); + + break; + + case "RequestFinalized": + await this.onRequestFinalized(event as EboEvent<"RequestFinalized">); + + break; + + default: + throw new UnknownEvent(event.name); + } } /** @@ -404,15 +436,7 @@ export class EboActor { * * @param event `RequestCreated` event */ - public async onRequestCreated(event: EboEvent<"RequestCreated">): Promise { - if (this.registry.getRequest(event.metadata.requestId)) { - this.logger.error( - `The request ${event.metadata.requestId} was already being handled by an actor.`, - ); - - throw new InvalidActorState(); - } - + private async onRequestCreated(event: EboEvent<"RequestCreated">): Promise { if (this.anyActiveProposal()) { // Skipping new proposal until the actor receives a ResponseDisputed event; // at that moment, it will be possible to re-propose again. @@ -539,7 +563,7 @@ export class EboActor { * @param event a `ResponseProposed` event * @returns void */ - public async onResponseProposed(event: EboEvent<"ResponseProposed">): Promise { + private async onResponseProposed(event: EboEvent<"ResponseProposed">): Promise { const eventResponse = event.metadata.response; const actorResponse = await this.buildResponse(eventResponse.response.chainId); @@ -578,7 +602,7 @@ export class EboActor { * @returns `true` if the actor is handling the request, `false` otherwise */ private shouldHandleRequest(requestId: string) { - return this.actorRequest.id.toLowerCase() !== requestId.toLowerCase(); + return this.actorRequest.id.toLowerCase() === requestId.toLowerCase(); } /** @@ -600,7 +624,7 @@ export class EboActor { * * @param event `ResponseDisputed` event. */ - public async onResponseDisputed(event: EboEvent<"ResponseDisputed">): Promise { + private async onResponseDisputed(event: EboEvent<"ResponseDisputed">): Promise { const dispute = this.registry.getDispute(event.metadata.disputeId); if (!dispute) @@ -699,7 +723,7 @@ export class EboActor { * * @param event `DisputeStatusChanged` event */ - public async onDisputeStatusChanged(event: EboEvent<"DisputeStatusChanged">): Promise { + private async onDisputeStatusChanged(event: EboEvent<"DisputeStatusChanged">): Promise { const request = this.getActorRequest(); const disputeId = event.metadata.disputeId; const disputeStatus = event.metadata.status; @@ -730,9 +754,14 @@ export class EboActor { } } - private async onDisputeEscalated(disputeId: string, request: Request) { + private async onDisputeEscalated(event: EboEvent<"DisputeEscalated">) { + const request = this.getActorRequest(); + // TODO: notify - this.logger.info(`Dispute ${disputeId} for request ${request.id} has been escalated.`); + + this.logger.info( + `Dispute ${event.metadata.disputeId} for request ${request.id} has been escalated.`, + ); } private async onDisputeWithNoResolution(disputeId: string, request: Request) { @@ -763,9 +792,7 @@ export class EboActor { * * @param event `ResponseFinalized` event */ - public async onRequestFinalized(event: EboEvent<"RequestFinalized">): Promise { - this.shouldHandleRequest(event.metadata.requestId); - + private async onRequestFinalized(_event: EboEvent<"RequestFinalized">): Promise { const request = this.getActorRequest(); this.logger.info(`Request ${request.id} has been finalized.`); diff --git a/packages/automated-dispute/src/types/events.ts b/packages/automated-dispute/src/types/events.ts index 096b522..0709879 100644 --- a/packages/automated-dispute/src/types/events.ts +++ b/packages/automated-dispute/src/types/events.ts @@ -4,7 +4,6 @@ import { Address, Log } from "viem"; import { Dispute, DisputeStatus, Request, RequestId, Response } from "./prophet.js"; export type EboEventName = - | "NewEpoch" | "RequestCreated" | "ResponseProposed" | "ResponseDisputed" @@ -12,11 +11,6 @@ export type EboEventName = | "DisputeEscalated" | "RequestFinalized"; -export interface NewEpoch { - epoch: bigint; - epochBlockNumber: bigint; -} - export interface ResponseProposed { requestId: string; responseId: string; @@ -56,21 +50,19 @@ export interface RequestFinalized { blockNumber: bigint; } -export type EboEventData = E extends "NewEpoch" - ? NewEpoch - : E extends "RequestCreated" - ? RequestCreated - : E extends "ResponseProposed" - ? ResponseProposed - : E extends "ResponseDisputed" - ? ResponseDisputed - : E extends "DisputeStatusChanged" - ? DisputeStatusChanged - : E extends "DisputeEscalated" - ? DisputeEscalated - : E extends "RequestFinalized" - ? RequestFinalized - : never; +export type EboEventData = E extends "RequestCreated" + ? RequestCreated + : E extends "ResponseProposed" + ? ResponseProposed + : E extends "ResponseDisputed" + ? ResponseDisputed + : E extends "DisputeStatusChanged" + ? DisputeStatusChanged + : E extends "DisputeEscalated" + ? DisputeEscalated + : E extends "RequestFinalized" + ? RequestFinalized + : never; export type EboEvent = { name: T; diff --git a/packages/automated-dispute/tests/eboActor/onDisputeStatusChanged.spec.ts b/packages/automated-dispute/tests/eboActor/onDisputeStatusChanged.spec.ts index b353df7..3849fef 100644 --- a/packages/automated-dispute/tests/eboActor/onDisputeStatusChanged.spec.ts +++ b/packages/automated-dispute/tests/eboActor/onDisputeStatusChanged.spec.ts @@ -12,7 +12,7 @@ const logger: ILogger = { debug: vi.fn(), }; -describe.skip("onDisputeStatusChanged", () => { +describe("onDisputeStatusChanged", () => { const actorRequest = DEFAULT_MOCKED_REQUEST_CREATED_DATA; const response = mocks.buildResponse(actorRequest); @@ -20,6 +20,7 @@ describe.skip("onDisputeStatusChanged", () => { const dispute = mocks.buildDispute(actorRequest, response, { status: "None" }); const event: EboEvent<"DisputeStatusChanged"> = { name: "DisputeStatusChanged", + requestId: actorRequest.id, blockNumber: 1n, logIndex: 1, metadata: { @@ -37,17 +38,18 @@ describe.skip("onDisputeStatusChanged", () => { const mockUpdateDisputeStatus = vi.spyOn(registry, "updateDisputeStatus"); - await actor.onDisputeStatusChanged(event); + actor.enqueue(event); + + await actor.processEvents(); expect(mockUpdateDisputeStatus).toHaveBeenCalledWith(dispute.id, "Lost"); }); - it.skip("notifies when dispute has been escalated"); - it("proposes a new response when dispute status goes into NoResolution", async () => { const dispute = mocks.buildDispute(actorRequest, response, { status: "Escalated" }); const event: EboEvent<"DisputeStatusChanged"> = { name: "DisputeStatusChanged", + requestId: actorRequest.id, blockNumber: 1n, logIndex: 1, metadata: { @@ -65,13 +67,22 @@ describe.skip("onDisputeStatusChanged", () => { vi.spyOn(registry, "getRequest").mockReturnValue(actorRequest); vi.spyOn(registry, "getDispute").mockReturnValue(dispute); + + vi.spyOn(protocolProvider, "getCurrentEpoch").mockResolvedValue({ + currentEpoch: actorRequest.epoch, + currentEpochBlockNumber: actorRequest.createdAt, + currentEpochTimestamp: BigInt(Date.UTC(2024, 1, 1, 0, 0, 0)), + }); + vi.spyOn(blockNumberService, "getEpochBlockNumber").mockResolvedValue( response.prophetData.response.block + 1n, ); const mockProposeResponse = vi.spyOn(protocolProvider, "proposeResponse"); - await actor.onDisputeStatusChanged(event); + actor.enqueue(event); + + await actor.processEvents(); expect(mockProposeResponse).toHaveBeenCalledWith( actorRequest.id, @@ -81,5 +92,6 @@ describe.skip("onDisputeStatusChanged", () => { ); }); + it.skip("notifies when dispute has been escalated"); it.skip("notifies if it will duplicate old proposal when handling NoResolution"); }); diff --git a/packages/automated-dispute/tests/eboActor/onRequestCreated.spec.ts b/packages/automated-dispute/tests/eboActor/onRequestCreated.spec.ts index f80dee1..bb31d50 100644 --- a/packages/automated-dispute/tests/eboActor/onRequestCreated.spec.ts +++ b/packages/automated-dispute/tests/eboActor/onRequestCreated.spec.ts @@ -1,31 +1,24 @@ -import { BlockNumberService } from "@ebo-agent/blocknumber"; import { Caip2ChainId } from "@ebo-agent/blocknumber/dist/types.js"; import { ILogger } from "@ebo-agent/shared"; -import { Mutex } from "async-mutex"; import { Address } from "viem"; -import { beforeEach, describe, expect, it, vi } from "vitest"; +import { describe, expect, it, vi } from "vitest"; -import { EboActor } from "../../src/eboActor.js"; -import { RequestMismatch } from "../../src/exceptions/index.js"; -import { ProtocolProvider } from "../../src/protocolProvider.js"; -import { EboMemoryRegistry } from "../../src/services/index.js"; import { EboEvent, Response } from "../../src/types/index.js"; import mocks from "../mocks/index.js"; -import { - DEFAULT_MOCKED_PROTOCOL_CONTRACTS, - DEFAULT_MOCKED_REQUEST_CREATED_DATA, -} from "./fixtures.js"; +import { DEFAULT_MOCKED_REQUEST_CREATED_DATA } from "./fixtures.js"; const logger: ILogger = mocks.mockLogger(); describe("EboActor", () => { describe("processEvents", () => { describe("when RequestCreated is enqueued", () => { - const requestId: Address = "0x12345"; - const indexedChainId: Caip2ChainId = "eip155:137"; + const request = DEFAULT_MOCKED_REQUEST_CREATED_DATA; + + const requestId: Address = request.id; + const indexedChainId: Caip2ChainId = request.chainId; const protocolEpoch = { - currentEpoch: 1n, + currentEpoch: request.epoch, currentEpochBlockNumber: 1n, currentEpochTimestamp: BigInt(Date.UTC(2024, 1, 1, 0, 0, 0, 0)), }; @@ -39,30 +32,14 @@ describe("EboActor", () => { chainId: indexedChainId, epoch: protocolEpoch.currentEpoch, requestId: requestId, - request: DEFAULT_MOCKED_REQUEST_CREATED_DATA.prophetData, + request: request.prophetData, }, }; - let protocolProvider: ProtocolProvider; - let blockNumberService: BlockNumberService; - let registry: EboMemoryRegistry; - let eventProcessingMutex: Mutex; - - beforeEach(() => { - protocolProvider = new ProtocolProvider( - ["http://localhost:8538"], - DEFAULT_MOCKED_PROTOCOL_CONTRACTS, - ); - - const chainRpcUrls = new Map(); - chainRpcUrls.set(indexedChainId, ["http://localhost:8539"]); - - blockNumberService = new BlockNumberService(chainRpcUrls, logger); - registry = new EboMemoryRegistry(); - eventProcessingMutex = new Mutex(); - }); - it("stores the new request", async () => { + const { actor, blockNumberService, protocolProvider, registry } = + mocks.buildEboActor(request, logger); + const indexedEpochBlockNumber = 48n; vi.spyOn(blockNumberService, "getEpochBlockNumber").mockResolvedValue( @@ -73,21 +50,6 @@ describe("EboActor", () => { Promise.resolve(), ); - const requestConfig = { - id: requestId, - epoch: protocolEpoch.currentEpoch, - epochTimestamp: protocolEpoch.currentEpochTimestamp, - }; - - const actor = new EboActor( - requestConfig, - protocolProvider, - blockNumberService, - registry, - eventProcessingMutex, - logger, - ); - const mockRegistryAddRequest = vi .spyOn(registry, "addRequest") .mockImplementation(() => {}); @@ -103,15 +65,20 @@ describe("EboActor", () => { ); }); - it.skip("rollbacks state updates if the rpc call fails"); + it("proposes a response", async () => { + const { actor, blockNumberService, protocolProvider } = mocks.buildEboActor( + request, + logger, + ); - it.skip("proposes a response", async () => { const indexedEpochBlockNumber = 48n; vi.spyOn(blockNumberService, "getEpochBlockNumber").mockResolvedValue( indexedEpochBlockNumber, ); + vi.spyOn(protocolProvider, "getCurrentEpoch").mockResolvedValue(protocolEpoch); + const proposeResponseMock = vi.spyOn(protocolProvider, "proposeResponse"); proposeResponseMock.mockImplementation( @@ -123,21 +90,6 @@ describe("EboActor", () => { ) => Promise.resolve(), ); - const requestConfig = { - id: requestId, - epoch: protocolEpoch.currentEpoch, - epochTimestamp: protocolEpoch.currentEpochTimestamp, - }; - - const actor = new EboActor( - requestConfig, - protocolProvider, - blockNumberService, - registry, - eventProcessingMutex, - logger, - ); - actor.enqueue(requestCreatedEvent); await actor.processEvents(); @@ -150,7 +102,10 @@ describe("EboActor", () => { ); }); - it.skip("does not propose when already proposed the same block", async () => { + it("does not propose when already proposed the same block", async () => { + const { actor, protocolProvider, blockNumberService, registry } = + mocks.buildEboActor(request, logger); + const indexedEpochBlockNumber = 48n; vi.spyOn(blockNumberService, "getEpochBlockNumber").mockResolvedValue( @@ -168,25 +123,10 @@ describe("EboActor", () => { ) => Promise.resolve(), ); - const requestConfig = { - id: requestId, - epoch: protocolEpoch.currentEpoch, - epochTimestamp: protocolEpoch.currentEpochTimestamp, - }; - - const actor = new EboActor( - requestConfig, - protocolProvider, - blockNumberService, - registry, - eventProcessingMutex, - logger, - ); - const previousResponses = new Map(); previousResponses.set("0x01", { id: "0x01", - wasDisputed: false, + createdAt: BigInt(Date.UTC(2024, 1, 1, 0, 0, 0, 0)), prophetData: { proposer: "0x02", requestId: requestId, @@ -198,66 +138,18 @@ describe("EboActor", () => { }, }); - vi.spyOn(registry, "getResponses").mockReturnValue(previousResponses); - - await actor.onRequestCreated(requestCreatedEvent); - - expect(proposeResponseMock).not.toHaveBeenCalled(); - }); - - it.skip("throws if the event's request id does not match with actor's", () => { - const noMatchRequestCreatedEvent: EboEvent<"RequestCreated"> = { - blockNumber: 34n, - logIndex: 1, - name: "RequestCreated", - metadata: { - chainId: "eip155:10", - epoch: protocolEpoch.currentEpoch, - requestId: "0x000000" as Address, - request: DEFAULT_MOCKED_REQUEST_CREATED_DATA.prophetData, - }, - }; - - const requestConfig = { - id: requestId, - epoch: protocolEpoch.currentEpoch, - epochTimestamp: protocolEpoch.currentEpochTimestamp, - }; - - const actor = new EboActor( - requestConfig, - protocolProvider, - blockNumberService, - registry, - eventProcessingMutex, - logger, + vi.spyOn(registry, "getResponses").mockReturnValue( + Object.values(previousResponses), ); - expect(actor.onRequestCreated(noMatchRequestCreatedEvent)).rejects.toThrowError( - RequestMismatch, - ); - }); - - it.skip("throws if the indexed chain block number cannot be fetched", () => { - vi.spyOn(blockNumberService, "getEpochBlockNumber").mockRejectedValue(new Error()); + actor.enqueue(requestCreatedEvent); - const requestConfig = { - id: requestId, - epoch: protocolEpoch.currentEpoch, - epochTimestamp: protocolEpoch.currentEpochTimestamp, - }; - - const actor = new EboActor( - requestConfig, - protocolProvider, - blockNumberService, - registry, - eventProcessingMutex, - logger, - ); + await actor.processEvents(); - expect(actor.onRequestCreated(requestCreatedEvent)).rejects.toBeDefined(); + expect(proposeResponseMock).not.toHaveBeenCalled(); }); + + it.todo("throws if the indexed chain block number cannot be fetched"); }); }); }); diff --git a/packages/automated-dispute/tests/eboActor/onRequestFinalized.spec.ts b/packages/automated-dispute/tests/eboActor/onRequestFinalized.spec.ts index 690b2f4..6f716eb 100644 --- a/packages/automated-dispute/tests/eboActor/onRequestFinalized.spec.ts +++ b/packages/automated-dispute/tests/eboActor/onRequestFinalized.spec.ts @@ -1,19 +1,19 @@ 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 { EboEvent } from "../../src/types/index.js"; import mocks from "../mocks/index.js"; import { DEFAULT_MOCKED_REQUEST_CREATED_DATA } from "./fixtures.js"; const logger: ILogger = mocks.mockLogger(); -describe.skip("EboActor", () => { +describe("EboActor", () => { describe("onRequestFinalized", () => { const actorRequest = DEFAULT_MOCKED_REQUEST_CREATED_DATA; const event: EboEvent<"RequestFinalized"> = { name: "RequestFinalized", + requestId: actorRequest.id, blockNumber: 1n, logIndex: 1, metadata: { @@ -31,25 +31,13 @@ describe.skip("EboActor", () => { const mockInfo = vi.spyOn(logger, "info"); - await actor.onRequestFinalized(event); + actor.enqueue(event); + + await actor.processEvents(); expect(mockInfo).toHaveBeenCalledWith( expect.stringMatching(`Request ${actorRequest.id} has been finalized.`), ); }); - - 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); - }); }); }); diff --git a/packages/automated-dispute/tests/eboActor/onResponseDisputed.spec.ts b/packages/automated-dispute/tests/eboActor/onResponseDisputed.spec.ts index cc40912..c3eba6d 100644 --- a/packages/automated-dispute/tests/eboActor/onResponseDisputed.spec.ts +++ b/packages/automated-dispute/tests/eboActor/onResponseDisputed.spec.ts @@ -1,8 +1,6 @@ import { ILogger } from "@ebo-agent/shared"; -import { ContractFunctionRevertedError } from "viem"; import { describe, expect, it, vi } from "vitest"; -import { InvalidActorState } from "../../src/exceptions/invalidActorState.exception.js"; import { EboEvent } from "../../src/types/events.js"; import { Response } from "../../src/types/prophet.js"; import mocks from "../mocks/index.js"; @@ -10,12 +8,13 @@ import { DEFAULT_MOCKED_REQUEST_CREATED_DATA } from "./fixtures.js"; const logger: ILogger = mocks.mockLogger(); -describe.skip("onResponseDisputed", () => { +describe("onResponseDisputed", () => { const actorRequest = DEFAULT_MOCKED_REQUEST_CREATED_DATA; const response: Response = mocks.buildResponse(actorRequest); const event: EboEvent<"ResponseDisputed"> = { name: "ResponseDisputed", + requestId: actorRequest.id, blockNumber: 1n, logIndex: 1, metadata: { @@ -39,13 +38,21 @@ describe.skip("onResponseDisputed", () => { vi.spyOn(registry, "getRequest").mockReturnValue(actorRequest); vi.spyOn(registry, "getResponse").mockReturnValue(response); + vi.spyOn(protocolProvider, "getCurrentEpoch").mockResolvedValue({ + currentEpoch: actorRequest.epoch, + currentEpochBlockNumber: response.prophetData.response.block, + currentEpochTimestamp: BigInt(Date.UTC(2024, 1, 1, 1, 0, 0, 0)), + }); + vi.spyOn(blockNumberService, "getEpochBlockNumber").mockResolvedValue( response.prophetData.response.block + 1n, ); const mockPledgeForDispute = vi.spyOn(protocolProvider, "pledgeForDispute"); - await actor.onResponseDisputed(event); + actor.enqueue(event); + + await actor.processEvents(); expect(mockPledgeForDispute).toHaveBeenCalled(); }); @@ -59,13 +66,21 @@ describe.skip("onResponseDisputed", () => { vi.spyOn(registry, "getRequest").mockReturnValue(actorRequest); vi.spyOn(registry, "getResponse").mockReturnValue(response); + vi.spyOn(protocolProvider, "getCurrentEpoch").mockResolvedValue({ + currentEpoch: actorRequest.epoch, + currentEpochBlockNumber: response.prophetData.response.block, + currentEpochTimestamp: BigInt(Date.UTC(2024, 1, 1, 1, 0, 0, 0)), + }); + vi.spyOn(blockNumberService, "getEpochBlockNumber").mockResolvedValue( response.prophetData.response.block, ); const mockPledgeAgainstDispute = vi.spyOn(protocolProvider, "pledgeAgainstDispute"); - await actor.onResponseDisputed(event); + actor.enqueue(event); + + await actor.processEvents(); expect(mockPledgeAgainstDispute).toHaveBeenCalled(); }); @@ -79,6 +94,12 @@ describe.skip("onResponseDisputed", () => { vi.spyOn(registry, "getRequest").mockReturnValue(actorRequest); vi.spyOn(registry, "getResponse").mockReturnValue(response); + vi.spyOn(protocolProvider, "getCurrentEpoch").mockResolvedValue({ + currentEpoch: actorRequest.epoch, + currentEpochBlockNumber: response.prophetData.response.block, + currentEpochTimestamp: BigInt(Date.UTC(2024, 1, 1, 1, 0, 0, 0)), + }); + vi.spyOn(blockNumberService, "getEpochBlockNumber").mockResolvedValue( response.prophetData.response.block, ); @@ -87,63 +108,14 @@ describe.skip("onResponseDisputed", () => { const addResponseMock = vi.spyOn(registry, "addDispute"); - await actor.onResponseDisputed(event); - - expect(addResponseMock).toHaveBeenCalled(); - }); - - it("resolves if the pledge is reverted", async () => { - const { actor, blockNumberService, protocolProvider, registry } = mocks.buildEboActor( - actorRequest, - logger, - ); - - vi.spyOn(registry, "getRequest").mockReturnValue(actorRequest); - vi.spyOn(registry, "getResponse").mockReturnValue(response); - - vi.spyOn(blockNumberService, "getEpochBlockNumber").mockResolvedValue( - response.prophetData.response.block + 1n, - ); - - vi.spyOn(protocolProvider, "pledgeForDispute").mockRejectedValue( - Object.create(ContractFunctionRevertedError.prototype), - ); - - expect(actor.onResponseDisputed(event)).resolves.toBeUndefined(); - }); - - it("throws if protocol provider cannot complete pledge", () => { - const { actor, blockNumberService, protocolProvider, registry } = mocks.buildEboActor( - actorRequest, - logger, - ); + actor.enqueue(event); - vi.spyOn(registry, "getRequest").mockReturnValue(actorRequest); - vi.spyOn(registry, "getResponse").mockReturnValue(response); + await actor.processEvents(); - vi.spyOn(blockNumberService, "getEpochBlockNumber").mockResolvedValue( - response.prophetData.response.block + 1n, - ); - - vi.spyOn(protocolProvider, "pledgeForDispute").mockRejectedValue(new Error()); - - expect(actor.onResponseDisputed(event)).rejects.toThrow(); + expect(addResponseMock).toHaveBeenCalled(); }); - it("throws if the response's request is not handled by actor", () => { - const { actor } = mocks.buildEboActor(actorRequest, logger); - - const otherRequestEvent = { - ...event, - metadata: { - ...event.metadata, - dispute: { - ...event.metadata.dispute, - requestId: "0x02", - }, - }, - }; - - expect(actor.onResponseDisputed(otherRequestEvent)).rejects.toThrow(InvalidActorState); - }); + // TODO: handle when error handling of reverts is implemented + it.todo("resolves if the pledge is reverted"); + it.todo("throws if protocol provider cannot complete pledge"); }); diff --git a/packages/automated-dispute/tests/eboActor/onResponseProposed.spec.ts b/packages/automated-dispute/tests/eboActor/onResponseProposed.spec.ts index 9a803b7..11da0da 100644 --- a/packages/automated-dispute/tests/eboActor/onResponseProposed.spec.ts +++ b/packages/automated-dispute/tests/eboActor/onResponseProposed.spec.ts @@ -1,7 +1,6 @@ import { ILogger } from "@ebo-agent/shared"; import { describe, expect, it, vi } from "vitest"; -import { InvalidActorState } from "../../src/exceptions/invalidActorState.exception"; import { EboEvent } from "../../src/types/events"; import mocks from "../mocks/index.ts"; import { DEFAULT_MOCKED_REQUEST_CREATED_DATA } from "./fixtures.ts"; @@ -56,23 +55,7 @@ describe("EboActor", () => { expect(addResponseMock).toHaveBeenCalled(); }); - it.skip("throws if the response's request is not handled by actor", () => { - const { actor } = mocks.buildEboActor(actorRequest, logger); - - const otherRequestEvent = { - ...responseProposedEvent, - metadata: { - ...responseProposedEvent.metadata, - requestId: responseProposedEvent.metadata.requestId + "123", - }, - }; - - expect(actor.onResponseProposed(otherRequestEvent)).rejects.toThrowError( - InvalidActorState, - ); - }); - - it.skip("does not dispute the response if seems valid", async () => { + it("does not dispute the response if seems valid", async () => { const { actor, registry, blockNumberService, protocolProvider } = mocks.buildEboActor(actorRequest, logger); @@ -84,24 +67,34 @@ describe("EboActor", () => { const mockDisputeResponse = vi.spyOn(protocolProvider, "disputeResponse"); - await actor.onResponseProposed(responseProposedEvent); + actor.enqueue(responseProposedEvent); + + await actor.processEvents(); expect(mockDisputeResponse).not.toHaveBeenCalled(); }); - it.skip("dispute the response if it should be different", async () => { + it("disputes the response if it should be different", async () => { const { actor, registry, blockNumberService, protocolProvider } = mocks.buildEboActor(actorRequest, logger); vi.spyOn(registry, "getRequest").mockReturnValue(actorRequest); + vi.spyOn(protocolProvider, "getCurrentEpoch").mockResolvedValue({ + currentEpoch: proposeData.epoch, + currentEpochBlockNumber: 1n, + currentEpochTimestamp: BigInt(Date.UTC(2024, 1, 1, 0, 0, 0, 0)), + }); + vi.spyOn(blockNumberService, "getEpochBlockNumber").mockResolvedValue( proposeData.block + 1n, ); const mockDisputeResponse = vi.spyOn(protocolProvider, "disputeResponse"); - await actor.onResponseProposed(responseProposedEvent); + actor.enqueue(responseProposedEvent); + + await actor.processEvents(); expect(mockDisputeResponse).toHaveBeenCalled(); }); diff --git a/packages/automated-dispute/tests/services/eboActor.spec.ts b/packages/automated-dispute/tests/services/eboActor.spec.ts index c58cf58..6b1bc32 100644 --- a/packages/automated-dispute/tests/services/eboActor.spec.ts +++ b/packages/automated-dispute/tests/services/eboActor.spec.ts @@ -54,7 +54,7 @@ describe("EboActor", () => { const { actor } = mocks.buildEboActor(request, logger); // TODO: mock the procol provider instead - actor["onNewEvent"] = vi.fn().mockImplementation(() => Promise.resolve()); + actor["onLastEvent"] = vi.fn().mockImplementation(() => Promise.resolve()); actor.enqueue(processedEvent); @@ -77,6 +77,8 @@ describe("EboActor", () => { const { actor } = mocks.buildEboActor(request, logger); const queue = actor["eventsQueue"]; + actor["onLastEvent"] = vi.fn().mockImplementation(() => Promise.resolve()); + actor.enqueue(event); expect(queue.size()).toEqual(1); @@ -92,7 +94,7 @@ describe("EboActor", () => { const mockEventsQueuePush = vi.spyOn(queue, "push"); - actor["onNewEvent"] = vi.fn().mockImplementation(() => Promise.reject()); + actor["onLastEvent"] = vi.fn().mockImplementation(() => Promise.reject()); actor.enqueue(event); @@ -109,7 +111,7 @@ describe("EboActor", () => { const firstEvent = { ...event }; const secondEvent = { ...firstEvent, blockNumber: firstEvent.blockNumber + 1n }; - actor["onNewEvent"] = vi.fn().mockImplementation(() => { + actor["onLastEvent"] = vi.fn().mockImplementation(() => { return new Promise((resolve, reject) => { setTimeout(() => { reject(); @@ -186,7 +188,7 @@ describe("EboActor", () => { const { actor } = mocks.buildEboActor(request, logger); - const onNewEventDelay20 = () => { + const onLastEventDelay20 = () => { callOrder.push(1); return new Promise((resolve) => { @@ -198,7 +200,7 @@ describe("EboActor", () => { }); }; - const onNewEventDelay1 = () => { + const onLastEventDelay1 = () => { callOrder.push(2); return new Promise((resolve) => { @@ -210,10 +212,10 @@ describe("EboActor", () => { }); }; - actor["onNewEvent"] = vi + actor["onLastEvent"] = vi .fn() - .mockImplementationOnce(onNewEventDelay20) - .mockImplementationOnce(onNewEventDelay1); + .mockImplementationOnce(onLastEventDelay20) + .mockImplementationOnce(onLastEventDelay1); setTimeout(() => { actor.enqueue(firstEvent); diff --git a/packages/automated-dispute/tests/services/eboProcessor.spec.ts b/packages/automated-dispute/tests/services/eboProcessor.spec.ts index 8439226..ac55fb7 100644 --- a/packages/automated-dispute/tests/services/eboProcessor.spec.ts +++ b/packages/automated-dispute/tests/services/eboProcessor.spec.ts @@ -4,8 +4,7 @@ import { afterEach, beforeEach, describe, expect, it, vi } from "vitest"; import { ProcessorAlreadyStarted } from "../../src/exceptions/index.js"; import { ProtocolProvider } from "../../src/protocolProvider.js"; -import { EboEvent, EboEventName } from "../../src/types/events.js"; -import { RequestId } from "../../src/types/prophet.js"; +import { EboEvent, EboEventName, RequestId } from "../../src/types/index.js"; import { DEFAULT_MOCKED_REQUEST_CREATED_DATA } from "../eboActor/fixtures.js"; import mocks from "../mocks/index.js"; @@ -14,6 +13,8 @@ const msBetweenChecks = 1; describe("EboProcessor", () => { describe("start", () => { + const request = DEFAULT_MOCKED_REQUEST_CREATED_DATA; + beforeEach(() => { vi.useFakeTimers(); }); @@ -35,12 +36,12 @@ describe("EboProcessor", () => { name: "RequestCreated", blockNumber: 1n, logIndex: 1, - requestId: DEFAULT_MOCKED_REQUEST_CREATED_DATA.id, + requestId: request.id, metadata: { - requestId: DEFAULT_MOCKED_REQUEST_CREATED_DATA.id, - epoch: DEFAULT_MOCKED_REQUEST_CREATED_DATA.epoch, - chainId: DEFAULT_MOCKED_REQUEST_CREATED_DATA.chainId, - request: DEFAULT_MOCKED_REQUEST_CREATED_DATA["prophetData"], + requestId: request.id, + epoch: request.epoch, + chainId: request.chainId, + request: request.prophetData, }, }; @@ -50,17 +51,18 @@ describe("EboProcessor", () => { ); vi.spyOn(protocolProvider, "getEvents").mockResolvedValue([requestCreatedEvent]); - const mockCreateActor = vi.spyOn(actorsManager, "createActor"); + const { actor } = mocks.buildEboActor(request, logger); + const mockCreateActor = vi.spyOn(actorsManager, "createActor").mockReturnValue(actor); await processor.start(msBetweenChecks); - const expectedNewActor = expect.objectContaining({ + const expectedActorRequest = expect.objectContaining({ id: requestCreatedEvent.requestId, epoch: currentEpoch.currentEpoch, }); expect(mockCreateActor).toHaveBeenCalledWith( - expectedNewActor, + expectedActorRequest, expect.any(ProtocolProvider), expect.any(BlockNumberService), logger, @@ -83,11 +85,13 @@ describe("EboProcessor", () => { vi.spyOn(protocolProvider, "getEvents").mockResolvedValue([]); await processor.start(1); + expect(processor.start(1)).rejects.toThrow(ProcessorAlreadyStarted); }); it("fetches events since epoch start when starting", async () => { - const { processor, protocolProvider } = mocks.buildEboProcessor(logger); + const { processor, protocolProvider, actorsManager } = mocks.buildEboProcessor(logger); + const { actor } = mocks.buildEboActor(request, logger); const currentEpoch = { currentEpoch: 1n, @@ -101,17 +105,21 @@ describe("EboProcessor", () => { name: "RequestCreated", blockNumber: 1n, logIndex: 1, - requestId: DEFAULT_MOCKED_REQUEST_CREATED_DATA.id, + requestId: request.id, metadata: { - requestId: DEFAULT_MOCKED_REQUEST_CREATED_DATA.id, - epoch: DEFAULT_MOCKED_REQUEST_CREATED_DATA.epoch, - chainId: DEFAULT_MOCKED_REQUEST_CREATED_DATA.chainId, - request: DEFAULT_MOCKED_REQUEST_CREATED_DATA["prophetData"], + requestId: request.id, + epoch: request.epoch, + chainId: request.chainId, + request: request.prophetData, }, }; vi.spyOn(protocolProvider, "getCurrentEpoch").mockResolvedValue(currentEpoch); vi.spyOn(protocolProvider, "getLastFinalizedBlock").mockResolvedValue(currentBlock); + vi.spyOn(actorsManager, "createActor").mockReturnValue(actor); + vi.spyOn(actor, "processEvents").mockImplementation(() => Promise.resolve()); + vi.spyOn(actor, "onLastBlockUpdated").mockImplementation(() => Promise.resolve()); + vi.spyOn(actor, "canBeTerminated").mockResolvedValue(false); const mockGetEvents = vi.spyOn(protocolProvider, "getEvents"); mockGetEvents.mockResolvedValue([requestCreatedEvent]); @@ -125,7 +133,8 @@ describe("EboProcessor", () => { }); it("fetches events since last block checked after first events fetch", async () => { - const { processor, protocolProvider } = mocks.buildEboProcessor(logger); + const { processor, protocolProvider, actorsManager } = mocks.buildEboProcessor(logger); + const { actor } = mocks.buildEboActor(request, logger); const mockLastCheckedBlock = 5n; processor["lastCheckedBlock"] = mockLastCheckedBlock; @@ -153,6 +162,10 @@ describe("EboProcessor", () => { vi.spyOn(protocolProvider, "getCurrentEpoch").mockResolvedValue(currentEpoch); vi.spyOn(protocolProvider, "getLastFinalizedBlock").mockResolvedValue(currentBlock); + vi.spyOn(actorsManager, "createActor").mockReturnValue(actor); + vi.spyOn(actor, "processEvents").mockImplementation(() => Promise.resolve()); + vi.spyOn(actor, "onLastBlockUpdated").mockImplementation(() => Promise.resolve()); + vi.spyOn(actor, "canBeTerminated").mockResolvedValue(false); const mockGetEvents = vi.spyOn(protocolProvider, "getEvents"); mockGetEvents.mockResolvedValue([requestCreatedEvent]); @@ -216,7 +229,7 @@ describe("EboProcessor", () => { .spyOn(actor, "processEvents") .mockImplementation(() => Promise.resolve()); - vi.spyOn(actor, "onLastBlockUpdated").mockImplementation(() => {}); + vi.spyOn(actor, "onLastBlockUpdated").mockImplementation(() => Promise.resolve()); vi.spyOn(actorsManager, "createActor").mockResolvedValue(actor); vi.spyOn(actorsManager, "getActor").mockReturnValue(actor); @@ -294,8 +307,8 @@ describe("EboProcessor", () => { vi.spyOn(actor1, "processEvents").mockImplementation(() => Promise.resolve()); vi.spyOn(actor2, "processEvents").mockImplementation(() => Promise.resolve()); - vi.spyOn(actor1, "onLastBlockUpdated").mockImplementation(() => {}); - vi.spyOn(actor2, "onLastBlockUpdated").mockImplementation(() => {}); + vi.spyOn(actor1, "onLastBlockUpdated").mockImplementation(() => Promise.resolve()); + vi.spyOn(actor2, "onLastBlockUpdated").mockImplementation(() => Promise.resolve()); vi.spyOn(actorsManager, "getActor").mockImplementation((requestId: RequestId) => { switch (requestId) { @@ -338,7 +351,7 @@ describe("EboProcessor", () => { const { actor } = mocks.buildEboActor(request, logger); - vi.spyOn(actor, "onLastBlockUpdated").mockImplementation(() => {}); + vi.spyOn(actor, "onLastBlockUpdated").mockImplementation(() => Promise.resolve()); vi.spyOn(actor, "canBeTerminated").mockReturnValue(true); vi.spyOn(actorsManager, "createActor").mockResolvedValue(actor);