Skip to content

Commit

Permalink
refactor: actor manager create (#28)
Browse files Browse the repository at this point in the history
# 🤖 Linear

Part of GRT-58

## Description
* Removes `onTerminate` callback from `EboActor` as this functionality
will be handled by the `EboProcessor`.
* Allows the `EboActorsManager` to build new actors while registering
them inside its registry.
  • Loading branch information
0xyaco authored Aug 23, 2024
1 parent fabce18 commit edaa317
Show file tree
Hide file tree
Showing 9 changed files with 130 additions and 92 deletions.
8 changes: 2 additions & 6 deletions packages/automated-dispute/src/eboActor.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,8 +14,6 @@ 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<void>;

/**
* Actor that handles a singular Prophet's request asking for the block number that corresponds
* to an instant on an indexed chain.
Expand All @@ -39,7 +37,6 @@ export class EboActor {
epoch: bigint;
epochTimestamp: bigint;
},
private readonly onTerminate: OnTerminateActorCallback,
private readonly protocolProvider: ProtocolProvider,
private readonly blockNumberService: BlockNumberService,
private readonly registry: EboRegistry,
Expand Down Expand Up @@ -425,8 +422,7 @@ export class EboActor {

private async onDisputeEscalated(disputeId: string, request: Request) {
// TODO: notify

await this.onTerminate(request);
this.logger.info(`Dispute ${disputeId} for request ${request.id} has been escalated.`);
}

private async onDisputeWithNoResolution(disputeId: string, request: Request) {
Expand Down Expand Up @@ -462,6 +458,6 @@ export class EboActor {

const request = this.getActorRequest();

await this.onTerminate(request);
this.logger.info(`Request ${request.id} has been finalized.`);
}
}
34 changes: 30 additions & 4 deletions packages/automated-dispute/src/eboActorsManager.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,11 @@
import { BlockNumberService } from "@ebo-agent/blocknumber";
import { ILogger } from "@ebo-agent/shared";

import { EboActor } from "./eboActor.js";
import { EboMemoryRegistry } from "./eboMemoryRegistry.js";
import { RequestAlreadyHandled } from "./exceptions/index.js";
import { ProtocolProvider } from "./protocolProvider.js";
import { RequestId } from "./types/prophet.js";

export class EboActorsManager {
private readonly requestActorMap: Map<string, EboActor>;
Expand All @@ -9,16 +15,36 @@ export class EboActorsManager {
}

/**
* Registers the actor and makes it fetchable by the ID of the request the actor is handling.
* Creates and registers the actor by its request ID.
*
* @param actor an `EboActor` instance that handles the request
* @param actor an `EboActor` instance that handles a request.
*/
public registerActor(actor: EboActor): void {
const requestId = actor.getRequestId();
public createActor(
actorRequest: {
id: RequestId;
epoch: bigint;
epochTimestamp: bigint;
},
protocolProvider: ProtocolProvider,
blockNumberService: BlockNumberService,
logger: ILogger,
): EboActor {
const requestId = actorRequest.id;

if (this.requestActorMap.has(requestId)) throw new RequestAlreadyHandled(requestId);

const registry = new EboMemoryRegistry();
const actor = new EboActor(
actorRequest,
protocolProvider,
blockNumberService,
registry,
logger,
);

this.requestActorMap.set(requestId, actor);

return actor;
}

/**
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -42,30 +42,6 @@ describe("onDisputeStatusChanged", () => {
expect(mockUpdateDisputeStatus).toHaveBeenCalledWith(dispute.id, "Lost");
});

it("executes the onTerminate callback when dispute has been escalated", async () => {
const dispute = mocks.buildDispute(actorRequest, response, { status: "Won" });
const event: EboEvent<"DisputeStatusChanged"> = {
name: "DisputeStatusChanged",
blockNumber: 1n,
logIndex: 1,
metadata: {
disputeId: "0x01",
status: "Escalated",
dispute: dispute.prophetData,
blockNumber: 1n,
},
};

const { actor, registry, onTerminate } = mocks.buildEboActor(actorRequest, logger);

vi.spyOn(registry, "getRequest").mockReturnValue(actorRequest);
vi.spyOn(registry, "getDispute").mockReturnValue(dispute);

await actor.onDisputeStatusChanged(event);

expect(onTerminate).toHaveBeenCalledWith(actorRequest);
});

it.skip("notifies when dispute has been escalated");

it("proposes a new response when dispute status goes into NoResolution", async () => {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -40,8 +40,6 @@ describe("EboActor", () => {
},
};

const onTerminate = vi.fn();

let protocolProvider: ProtocolProvider;
let blockNumberService: BlockNumberService;
let registry: EboMemoryRegistry;
Expand Down Expand Up @@ -85,7 +83,6 @@ describe("EboActor", () => {

const actor = new EboActor(
requestConfig,
onTerminate,
protocolProvider,
blockNumberService,
registry,
Expand Down Expand Up @@ -128,7 +125,6 @@ describe("EboActor", () => {

const actor = new EboActor(
requestConfig,
onTerminate,
protocolProvider,
blockNumberService,
registry,
Expand Down Expand Up @@ -178,7 +174,6 @@ describe("EboActor", () => {

const actor = new EboActor(
requestConfig,
onTerminate,
protocolProvider,
blockNumberService,
registry,
Expand All @@ -201,7 +196,6 @@ describe("EboActor", () => {

const actor = new EboActor(
requestConfig,
onTerminate,
protocolProvider,
blockNumberService,
registry,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,16 +24,18 @@ describe("EboActor", () => {
},
};

it("executes the actor's callback during termination", async () => {
const { actor, onTerminate, registry } = mocks.buildEboActor(actorRequest, logger);
it("logs a message during request finalization", async () => {
const { actor, registry } = mocks.buildEboActor(actorRequest, logger);

vi.spyOn(registry, "getRequest").mockReturnValue(actorRequest);

onTerminate.mockImplementation(() => Promise.resolve());
const mockInfo = vi.spyOn(logger, "info");

await actor.onRequestFinalized(event);

expect(onTerminate).toHaveBeenCalledWith(actorRequest);
expect(mockInfo).toHaveBeenCalledWith(
expect.stringMatching(`Request ${actorRequest.id} has been finalized.`),
);
});

it("throws if the event's request is not handled by actor", () => {
Expand All @@ -49,16 +51,5 @@ describe("EboActor", () => {

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);
});
});
});
105 changes: 80 additions & 25 deletions packages/automated-dispute/tests/eboActorsManager.spec.ts
Original file line number Diff line number Diff line change
@@ -1,38 +1,89 @@
import { beforeEach } from "node:test";
import { BlockNumberService } from "@ebo-agent/blocknumber";
import { Caip2ChainId } from "@ebo-agent/blocknumber/dist/types.js";
import { ILogger } from "@ebo-agent/shared";
import { describe, expect, it, vi } from "vitest";
import { describe, expect, it } from "vitest";

import { EboActorsManager } from "../src/eboActorsManager.js";
import { RequestAlreadyHandled } from "../src/exceptions/index.js";
import { DEFAULT_MOCKED_REQUEST_CREATED_DATA } from "./eboActor/fixtures.js";
import { ProtocolProvider } from "../src/protocolProvider.js";
import {
DEFAULT_MOCKED_PROTOCOL_CONTRACTS,
DEFAULT_MOCKED_REQUEST_CREATED_DATA,
} from "./eboActor/fixtures.js";
import mocks from "./mocks/index.js";

const logger: ILogger = mocks.mockLogger();

describe("EboActorsManager", () => {
describe("registerActor", () => {
it("registers the actor correctly", () => {
const request = DEFAULT_MOCKED_REQUEST_CREATED_DATA;
const { actor } = mocks.buildEboActor(request, logger);
const request = DEFAULT_MOCKED_REQUEST_CREATED_DATA;
const actorRequest = {
id: request.id,
epoch: request.epoch,
epochTimestamp: request.epochTimestamp,
};
const chainId = request.chainId;

let protocolProvider: ProtocolProvider;
let blockNumberService: BlockNumberService;

beforeEach(() => {
const protocolProviderRpcUrls = ["http://localhost:8538"];
protocolProvider = new ProtocolProvider(
protocolProviderRpcUrls,
DEFAULT_MOCKED_PROTOCOL_CONTRACTS,
);

const blockNumberRpcUrls = new Map<Caip2ChainId, string[]>([
[chainId, ["http://localhost:8539"]],
]);
blockNumberService = new BlockNumberService(blockNumberRpcUrls, logger);
});

describe("createActor", () => {
it("creates the actor", () => {
const actorsManager = new EboActorsManager();
const actor = actorsManager.createActor(
actorRequest,
protocolProvider,
blockNumberService,
logger,
);

expect(actor).toMatchObject({
actorRequest: expect.objectContaining({
id: request.id,
epoch: request.epoch,
epochTimestamp: request.epochTimestamp,
}),
});
});

it("registers the actor to be fetchable by id", () => {
const actorsManager = new EboActorsManager();
const mockSetRequestActorMap = vi.spyOn(actorsManager["requestActorMap"], "set");

actorsManager.registerActor(actor);
expect(actorsManager.getActor(request.id)).toBeUndefined();

actorsManager.createActor(actorRequest, protocolProvider, blockNumberService, logger);

expect(mockSetRequestActorMap).toHaveBeenCalledWith(request.id, actor);
expect(actorsManager.getActor(actor.getRequestId())).toBe(actor);
const actor = actorsManager.getActor(request.id);

expect(actor).toBeDefined();
});

it("throws if the request has already an actor linked to it", () => {
const request = DEFAULT_MOCKED_REQUEST_CREATED_DATA;
const { actor: firstActor } = mocks.buildEboActor(request, logger);
const { actor: secondActor } = mocks.buildEboActor(request, logger);
const actorsManager = new EboActorsManager();

actorsManager.registerActor(firstActor);
actorsManager.createActor(actorRequest, protocolProvider, blockNumberService, logger);

expect(() => actorsManager.registerActor(secondActor)).toThrowError(
RequestAlreadyHandled,
);
expect(() => {
actorsManager.createActor(
actorRequest,
protocolProvider,
blockNumberService,
logger,
);
}).toThrowError(RequestAlreadyHandled);
});
});

Expand All @@ -44,25 +95,29 @@ describe("EboActorsManager", () => {
});

it("returns the request's linked actor", () => {
const request = DEFAULT_MOCKED_REQUEST_CREATED_DATA;
const { actor } = mocks.buildEboActor(request, logger);
const actorsManager = new EboActorsManager();

actorsManager.registerActor(actor);
actorsManager.createActor(actorRequest, protocolProvider, blockNumberService, logger);

const actor = actorsManager.getActor(request.id);

expect(actorsManager.getActor(request.id)).toBe(actor);
expect(actor).toMatchObject({
actorRequest: expect.objectContaining({
id: request.id,
epoch: request.epoch,
epochTimestamp: request.epochTimestamp,
}),
});
});
});

describe("deleteActor", () => {
it("deletes the actor linked to the request", () => {
const request = DEFAULT_MOCKED_REQUEST_CREATED_DATA;
const { actor } = mocks.buildEboActor(request, logger);
const actorsManager = new EboActorsManager();

actorsManager.registerActor(actor);
actorsManager.createActor(actorRequest, protocolProvider, blockNumberService, logger);

expect(actorsManager.getActor(request.id)).toBe(actor);
expect(actorsManager.getActor(request.id)).toBeDefined();

actorsManager.deleteActor(request.id);

Expand Down
Original file line number Diff line number Diff line change
@@ -1,13 +1,12 @@
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";
import { ProtocolProvider } from "../../src/protocolProvider";
import { EboActor } from "../../src/eboActor.js";
import { EboMemoryRegistry } from "../../src/eboMemoryRegistry.js";
import { ProtocolProvider } from "../../src/protocolProvider.js";
import { Dispute, Request, Response } from "../../src/types/index.js";
import { DEFAULT_MOCKED_PROTOCOL_CONTRACTS } from "../eboActor/fixtures";
import { DEFAULT_MOCKED_PROTOCOL_CONTRACTS } from "../eboActor/fixtures.js";

/**
* Builds a base `EboActor` scaffolded with all its dependencies.
Expand All @@ -19,8 +18,6 @@ import { DEFAULT_MOCKED_PROTOCOL_CONTRACTS } from "../eboActor/fixtures";
export 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,
Expand All @@ -36,7 +33,6 @@ export function buildEboActor(request: Request, logger: ILogger) {

const actor = new EboActor(
{ id, epoch, epochTimestamp },
onTerminate,
protocolProvider,
blockNumberService,
registry,
Expand All @@ -45,7 +41,6 @@ export function buildEboActor(request: Request, logger: ILogger) {

return {
actor,
onTerminate,
protocolProvider,
blockNumberService,
registry,
Expand Down
Loading

0 comments on commit edaa317

Please sign in to comment.