Skip to content

Commit

Permalink
Merge branch 'dev' into feat/grt-140-implement-eborequestcreator-rpc-…
Browse files Browse the repository at this point in the history
…calls
  • Loading branch information
jahabeebs authored Sep 10, 2024
2 parents 848efa7 + 6c359c5 commit f77aad9
Show file tree
Hide file tree
Showing 17 changed files with 354 additions and 82 deletions.
46 changes: 40 additions & 6 deletions packages/automated-dispute/src/eboActor.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,11 +24,12 @@ import {
AddDispute,
AddRequest,
AddResponse,
Noop,
FinalizeRequest,
UpdateDisputeStatus,
} from "./services/index.js";
import {
Dispute,
DisputeStatus,
EboEvent,
EboEventName,
Request,
Expand Down Expand Up @@ -205,7 +206,10 @@ export class EboActor {
);

case "RequestFinalized":
return Noop.buildFromEvent();
return FinalizeRequest.buildFromEvent(
event as EboEvent<"RequestFinalized">,
this.registry,
);

default:
throw new UnknownEvent(event.name);
Expand Down Expand Up @@ -412,7 +416,7 @@ export class EboActor {
const request = this.getActorRequest();
const dispute = this.registry.getResponseDispute(response);
const disputeWindow =
response.createdAt + request.prophetData.responseModuleData.disputeWindow;
response.createdAt + request.prophetData.disputeModuleData.disputeWindow;

// Response is still able to be disputed
if (blockNumber <= disputeWindow) return false;
Expand All @@ -428,11 +432,41 @@ export class EboActor {
*
* Be aware that a request can be finalized but some of its disputes can still be pending resolution.
*
* @param blockNumber block number to check entities at
* @returns `true` if all entities are settled, otherwise `false`
*/
public canBeTerminated(): boolean {
// TODO
throw new Error("Implement me");
public canBeTerminated(blockNumber: bigint): boolean {
const request = this.getActorRequest();
const isRequestFinalized = request.status === "Finalized";
const nonSettledProposals = this.activeProposals(blockNumber);

return isRequestFinalized && nonSettledProposals.length === 0;
}

/**
* Check for any active proposals at a specific block number.
*
* @param blockNumber block number to check proposals' status against
* @returns an array of `Response` instances
*/
private activeProposals(blockNumber: bigint): Response[] {
const responses = this.registry.getResponses();

return responses.filter((response) => {
if (this.isResponseAccepted(response, blockNumber)) return false;

const dispute = this.registry.getResponseDispute(response);

// Response has not been disputed but is not accepted yet, so it's active.
if (!dispute) return true;

// The rest of the status (ie "Escalated" | "Won" | "Lost" | "NoResolution")
// cannot be changed by the EBO agent once they've been reached so they make
// the proposal non-active.
const activeStatus: DisputeStatus[] = ["None", "Active"];

return activeStatus.includes(dispute.status);
});
}

/**
Expand Down
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
export * from "./commandAlreadyRun.js";
export * from "./commandNotRun.js";
export * from "./disputeNotFound.js";
export * from "./requestNotFound.js";
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
export class RequestNotFound extends Error {
constructor(requestId: string) {
super(`Request ${requestId} was not found.`);

this.name = "RequestNotFound";
}
}
17 changes: 16 additions & 1 deletion packages/automated-dispute/src/interfaces/eboRegistry.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,11 @@
import { Dispute, DisputeStatus, Request, RequestId, Response } from "../types/prophet.js";
import {
Dispute,
DisputeStatus,
Request,
RequestId,
RequestStatus,
Response,
} from "../types/index.js";

/** Registry that stores Prophet entities (ie. requests, responses and disputes) */
export interface EboRegistry {
Expand All @@ -17,6 +24,14 @@ export interface EboRegistry {
*/
getRequest(requestId: RequestId): Request | undefined;

/**
* Update the request status based on its ID.
*
* @param requestId the ID of the `Request`
* @param status the `Request` status
*/
updateRequestStatus(requestId: string, status: RequestStatus): void;

/**
* Remove a `Request` by its ID.
*
Expand Down
2 changes: 1 addition & 1 deletion packages/automated-dispute/src/services/eboProcessor.ts
Original file line number Diff line number Diff line change
Expand Up @@ -144,7 +144,7 @@ export class EboProcessor {
await actor.processEvents();
await actor.onLastBlockUpdated(lastBlock);

if (actor.canBeTerminated()) {
if (actor.canBeTerminated(lastBlock)) {
this.terminateActor(requestId);
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ export class AddRequest implements EboRegistryCommand {
epoch: event.metadata.epoch,
createdAt: event.blockNumber,
prophetData: event.metadata.request,
status: "Active",
};

return new AddRequest(registry, request);
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
import { CommandAlreadyRun, CommandNotRun, RequestNotFound } from "../../../exceptions/index.js";
import { EboRegistry, EboRegistryCommand } from "../../../interfaces/index.js";
import { EboEvent, Request, RequestStatus } from "../../../types/index.js";

export class FinalizeRequest implements EboRegistryCommand {
private wasRun: boolean = false;
private previousStatus?: RequestStatus;

private constructor(
private readonly registry: EboRegistry,
private readonly request: Request,
) {}

public static buildFromEvent(
event: EboEvent<"RequestFinalized">,
registry: EboRegistry,
): FinalizeRequest {
const requestId = event.metadata.requestId;
const request = registry.getRequest(requestId);

if (!request) throw new RequestNotFound(requestId);

return new FinalizeRequest(registry, request);
}

run(): void {
if (this.wasRun) throw new CommandAlreadyRun(FinalizeRequest.name);

this.previousStatus = this.request.status;

this.registry.updateRequestStatus(this.request.id, "Finalized");

this.wasRun = true;
}

undo(): void {
if (!this.wasRun || !this.previousStatus) throw new CommandNotRun(FinalizeRequest.name);

this.registry.updateRequestStatus(this.request.id, this.previousStatus);
}
}
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
export * from "./addDispute.js";
export * from "./addRequest.js";
export * from "./addResponse.js";
export * from "./noop.js";
export * from "./finalizeRequest.js";
export * from "./updateDisputeStatus.js";

This file was deleted.

Original file line number Diff line number Diff line change
@@ -1,6 +1,13 @@
import { DisputeNotFound } from "../../exceptions/index.js";
import { DisputeNotFound, RequestNotFound } from "../../exceptions/index.js";
import { EboRegistry } from "../../interfaces/index.js";
import { Dispute, DisputeStatus, Request, RequestId, Response } from "../../types/index.js";
import {
Dispute,
DisputeStatus,
Request,
RequestId,
RequestStatus,
Response,
} from "../../types/index.js";

export class EboMemoryRegistry implements EboRegistry {
constructor(
Expand All @@ -20,6 +27,17 @@ export class EboMemoryRegistry implements EboRegistry {
return this.requests.get(requestId);
}

public updateRequestStatus(requestId: RequestId, status: RequestStatus): void {
const request = this.getRequest(requestId);

if (request === undefined) throw new RequestNotFound(requestId);

this.requests.set(requestId, {
...request,
status: status,
});
}

/** @inheritdoc */
public removeRequest(requestId: RequestId): boolean {
return this.requests.delete(requestId);
Expand Down
2 changes: 1 addition & 1 deletion packages/automated-dispute/src/types/events.ts
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,7 @@ export interface DisputeEscalated {
}

export interface RequestFinalized {
requestId: string;
requestId: RequestId;
responseId: string;
caller: string;
blockNumber: bigint;
Expand Down
2 changes: 2 additions & 0 deletions packages/automated-dispute/src/types/prophet.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,12 +3,14 @@ import { NormalizedAddress } from "@ebo-agent/shared";
import { Address } from "viem";

export type RequestId = NormalizedAddress;
export type RequestStatus = "Active" | "Finalized";

export interface Request {
id: RequestId;
chainId: Caip2ChainId;
epoch: bigint;
createdAt: bigint;
status: RequestStatus;

prophetData: Readonly<{
requester: Address;
Expand Down
1 change: 1 addition & 0 deletions packages/automated-dispute/tests/eboActor/fixtures.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ export const DEFAULT_MOCKED_REQUEST_CREATED_DATA: Request = {
chainId: "eip155:1",
epoch: 1n,
createdAt: 1n,
status: "Active",
prophetData: {
disputeModule: "0x01" as Address,
finalityModule: "0x02" as Address,
Expand Down
Original file line number Diff line number Diff line change
@@ -1,43 +1,68 @@
import { ILogger } from "@ebo-agent/shared";
import { describe, expect, it, vi } from "vitest";

import { FinalizeRequest } from "../../src/services/index.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("EboActor", () => {
describe("onRequestFinalized", () => {
const actorRequest = DEFAULT_MOCKED_REQUEST_CREATED_DATA;

const event: EboEvent<"RequestFinalized"> = {
name: "RequestFinalized",
requestId: actorRequest.id,
blockNumber: 1n,
logIndex: 1,
metadata: {
blockNumber: 1n,
caller: "0x01",
describe("processEvents", () => {
describe("when RequestFinalized is enqueued", () => {
const actorRequest = DEFAULT_MOCKED_REQUEST_CREATED_DATA;

const event: EboEvent<"RequestFinalized"> = {
name: "RequestFinalized",
requestId: actorRequest.id,
responseId: "0x02",
},
};
blockNumber: 1n,
logIndex: 1,
metadata: {
blockNumber: 1n,
caller: "0x01",
requestId: actorRequest.id,
responseId: "0x02",
},
};

it("logs a message during request finalization", async () => {
const { actor, registry } = mocks.buildEboActor(actorRequest, logger);

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

const mockInfo = vi.spyOn(logger, "info");

actor.enqueue(event);

await actor.processEvents();

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

it("uses the FinalizeRequest registry command", async () => {
const { actor, 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);

vi.spyOn(registry, "getRequest").mockReturnValue(actorRequest);
const mockFinalizeRequest = {
run: vi.fn(),
undo: vi.fn(),
} as unknown as FinalizeRequest;

const mockInfo = vi.spyOn(logger, "info");
const mockBuildFromEvent = vi
.spyOn(FinalizeRequest, "buildFromEvent")
.mockReturnValue(mockFinalizeRequest);

actor.enqueue(event);
actor.enqueue(event);

await actor.processEvents();
await actor.processEvents();

expect(mockInfo).toHaveBeenCalledWith(
expect.stringMatching(`Request ${actorRequest.id} has been finalized.`),
);
expect(mockBuildFromEvent).toHaveBeenCalledWith(event, registry);
expect(mockFinalizeRequest.run).toHaveBeenCalled();
});
});
});
});
Loading

0 comments on commit f77aad9

Please sign in to comment.