Skip to content

Commit

Permalink
feat: add missing ebo registry commands
Browse files Browse the repository at this point in the history
  • Loading branch information
0xyaco committed Sep 3, 2024
1 parent 6a0bde0 commit 93d1461
Show file tree
Hide file tree
Showing 12 changed files with 337 additions and 48 deletions.
66 changes: 27 additions & 39 deletions packages/automated-dispute/src/eboActor.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,13 @@ import {
} from "./exceptions/index.js";
import { EboRegistry, EboRegistryCommand } from "./interfaces/index.js";
import { ProtocolProvider } from "./protocolProvider.js";
import { AddRequest, AddResponse } from "./services/index.js";
import {
AddDispute,
AddRequest,
AddResponse,
Noop,
UpdateDisputeStatus,
} from "./services/index.js";
import {
Dispute,
EboEvent,
Expand Down Expand Up @@ -176,6 +182,21 @@ export class EboActor {
this.registry,
);

case "ResponseDisputed":
return AddDispute.buildFromEvent(
event as EboEvent<"ResponseDisputed">,
this.registry,
);

case "DisputeStatusChanged":
return UpdateDisputeStatus.buildFromEvent(
event as EboEvent<"DisputeStatusChanged">,
this.registry,
);

case "RequestFinalized":
return Noop.buildFromEvent();

default:
throw new UnknownEvent(event.name);
}
Expand Down Expand Up @@ -378,9 +399,6 @@ export class EboActor {
* @param event `RequestCreated` event
*/
public async onRequestCreated(event: EboEvent<"RequestCreated">): Promise<void> {
if (event.metadata.requestId != this.actorRequest.id)
throw new RequestMismatch(this.actorRequest.id, event.metadata.requestId);

if (this.registry.getRequest(event.metadata.requestId)) {
this.logger.error(
`The request ${event.metadata.requestId} was already being handled by an actor.`,
Expand All @@ -389,16 +407,6 @@ export class EboActor {
throw new InvalidActorState();
}

const request: Request = {
id: this.actorRequest.id,
chainId: event.metadata.chainId,
epoch: this.actorRequest.epoch,
createdAt: event.blockNumber,
prophetData: event.metadata.request,
};

this.registry.addRequest(request);

if (this.anyActiveProposal()) {
// Skipping new proposal until the actor receives a ResponseDisputed event;
// at that moment, it will be possible to re-propose again.
Expand Down Expand Up @@ -526,16 +534,6 @@ export class EboActor {
* @returns void
*/
public async onResponseProposed(event: EboEvent<"ResponseProposed">): Promise<void> {
this.shouldHandleRequest(event.metadata.requestId);

const response: Response = {
id: event.metadata.responseId,
createdAt: event.blockNumber,
prophetData: event.metadata.response,
};

this.registry.addResponse(response);

const eventResponse = event.metadata.response;
const actorResponse = await this.buildResponse(eventResponse.response.chainId);

Expand Down Expand Up @@ -597,16 +595,12 @@ export class EboActor {
* @param event `ResponseDisputed` event.
*/
public async onResponseDisputed(event: EboEvent<"ResponseDisputed">): Promise<void> {
this.shouldHandleRequest(event.metadata.dispute.requestId);
const dispute = this.registry.getDispute(event.metadata.disputeId);

const dispute: Dispute = {
id: event.metadata.disputeId,
createdAt: event.blockNumber,
status: "Active",
prophetData: event.metadata.dispute,
};

this.registry.addDispute(event.metadata.disputeId, dispute);
if (!dispute)
throw new InvalidActorState(
`Dispute ${event.metadata.disputeId} needs to be added to the internal registry.`,
);

const request = this.getActorRequest();
const proposedResponse = this.registry.getResponse(event.metadata.responseId);
Expand Down Expand Up @@ -700,16 +694,10 @@ export class EboActor {
* @param event `DisputeStatusChanged` event
*/
public async onDisputeStatusChanged(event: EboEvent<"DisputeStatusChanged">): Promise<void> {
const requestId = event.metadata.dispute.requestId;

this.shouldHandleRequest(requestId);

const request = this.getActorRequest();
const disputeId = event.metadata.disputeId;
const disputeStatus = event.metadata.status;

this.registry.updateDisputeStatus(disputeId, disputeStatus);

this.logger.info(`Dispute ${disputeId} status changed to ${disputeStatus}.`);

switch (disputeStatus) {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,9 @@
export class InvalidActorState extends Error {
constructor() {
constructor(message?: string) {
// TODO: we'll want to dump the Actor state into stderr at this point
super("The actor is in an invalid state.");
super(
`The actor is in an invalid state. ${message ? `Reason: ${message}` : "Unknown reason."}`,
);

this.name = "InvalidActorState";
}
Expand Down
11 changes: 9 additions & 2 deletions packages/automated-dispute/src/interfaces/eboRegistry.ts
Original file line number Diff line number Diff line change
Expand Up @@ -65,10 +65,9 @@ export interface EboRegistry {
/**
* Add a dispute by ID.
*
* @param disputeId the ID of the `Dispute` to use as index
* @param dispute the `Dispute`
*/
addDispute(disputeId: string, dispute: Dispute): void;
addDispute(dispute: Dispute): void;

/**
* Get all disputes
Expand All @@ -92,4 +91,12 @@ export interface EboRegistry {
* @param status the `Dispute`
*/
updateDisputeStatus(disputeId: string, status: DisputeStatus): void;

/**
* Remove a `Dispute` by its ID.
*
* @param disputeId dispute ID
* @returns `true` if the dispute in the registry existed and has been removed, or `false` if the dispute does not exist
*/
removeDispute(disputeId: string): boolean;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
import { CommandAlreadyRun, CommandNotRun } from "../../../exceptions/index.js";
import { EboRegistry, EboRegistryCommand } from "../../../interfaces/index.js";
import { Dispute, EboEvent } from "../../../types/index.js";

export class AddDispute implements EboRegistryCommand {
private wasRun: boolean = false;

private constructor(
private readonly registry: EboRegistry,
private readonly dispute: Dispute,
) {}

public static buildFromEvent(
event: EboEvent<"ResponseDisputed">,
registry: EboRegistry,
): AddDispute {
const dispute: Dispute = {
id: event.metadata.disputeId,
createdAt: event.blockNumber,
status: "Active",
prophetData: event.metadata.dispute,
};

return new AddDispute(registry, dispute);
}

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

this.registry.addDispute(this.dispute);
this.wasRun = true;
}

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

this.registry.removeDispute(this.dispute.id);
}
}
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
export * from "./addRequest.js";
export * from "./addResponse.js";

// TODO: add the rest of the commands
export * from "./addDispute.js";
export * from "./noop.js";
export * from "./updateDisputeStatus.js";
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
import { CommandAlreadyRun, CommandNotRun } from "../../../exceptions/index.js";
import { EboRegistryCommand } from "../../../interfaces/index.js";

export class Noop implements EboRegistryCommand {
private wasRun: boolean = false;

private constructor() {}

public static buildFromEvent(): Noop {
return new Noop();
}

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

this.wasRun = true;
}

undo(): void {
if (!this.wasRun) throw new CommandNotRun(Noop.name);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
import { CommandAlreadyRun, CommandNotRun, DisputeNotFound } from "../../../exceptions/index.js";
import { EboRegistry, EboRegistryCommand } from "../../../interfaces/index.js";
import { DisputeStatus, EboEvent } from "../../../types/index.js";

export class UpdateDisputeStatus implements EboRegistryCommand {
private wasRun: boolean = false;
private previousStatus?: DisputeStatus;

private constructor(
private readonly registry: EboRegistry,
private readonly disputeId: string,
private readonly status: DisputeStatus,
) {}

public static buildFromEvent(
event: EboEvent<"DisputeStatusChanged">,
registry: EboRegistry,
): UpdateDisputeStatus {
const disputeId = event.metadata.disputeId;
const status = event.metadata.status;

return new UpdateDisputeStatus(registry, disputeId, status);
}

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

const dispute = this.registry.getDispute(this.disputeId);

if (!dispute) throw new DisputeNotFound(this.disputeId);

this.previousStatus = dispute.status;

this.registry.updateDisputeStatus(this.disputeId, this.status);

this.wasRun = true;
}

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

this.registry.updateDisputeStatus(this.disputeId, this.previousStatus);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -46,8 +46,8 @@ export class EboMemoryRegistry implements EboRegistry {
}

/** @inheritdoc */
public addDispute(disputeId: string, dispute: Dispute): void {
this.disputes.set(disputeId, dispute);
public addDispute(dispute: Dispute): void {
this.disputes.set(dispute.id, dispute);
this.responsesDisputes.set(dispute.prophetData.responseId, dispute.id);
}

Expand Down Expand Up @@ -81,4 +81,9 @@ export class EboMemoryRegistry implements EboRegistry {
status: status,
});
}

/** @inheritdoc */
removeDispute(disputeId: string): boolean {
return this.disputes.delete(disputeId);
}
}
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 @@ -27,7 +27,7 @@ export interface RequestCreated {
epoch: bigint;
chainId: Caip2ChainId;
request: Request["prophetData"];
requestId: string;
requestId: RequestId;
}

export interface ResponseDisputed {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
import { beforeEach, describe, expect, it, Mock, vi } from "vitest";

import { CommandAlreadyRun, CommandNotRun } from "../../../../src/exceptions/index.js";
import { EboRegistry } from "../../../../src/interfaces/index.js";
import { AddDispute } from "../../../../src/services/index.js";
import { EboEvent } from "../../../../src/types/index.js";
import { DEFAULT_MOCKED_REQUEST_CREATED_DATA } from "../../../eboActor/fixtures.js";
import mocks from "../../../mocks/index.js";

describe("AddDispute", () => {
let registry: EboRegistry;

const request = DEFAULT_MOCKED_REQUEST_CREATED_DATA;
const response = mocks.buildResponse(request);
const dispute = mocks.buildDispute(request, response);

const event: EboEvent<"ResponseDisputed"> = {
name: "ResponseDisputed",
blockNumber: 1n,
logIndex: 1,
requestId: request.id,
metadata: {
dispute: dispute.prophetData,
disputeId: dispute.id,
responseId: response.id,
},
};

beforeEach(() => {
registry = {
addDispute: vi.fn(),
removeDispute: vi.fn(),
} as unknown as EboRegistry;
});

describe("run", () => {
it("adds the dispute to the registry", () => {
const command = AddDispute.buildFromEvent(event, registry);

command.run();

expect(registry.addDispute).toHaveBeenCalledWith(
expect.objectContaining({
id: dispute.id,
}),
);
});

it("throws if the command was already run", () => {
const command = AddDispute.buildFromEvent(event, registry);

command.run();

expect(() => command.run()).toThrow(CommandAlreadyRun);
});
});

describe("undo", () => {
it("removes the added request", () => {
const command = AddDispute.buildFromEvent(event, registry);

const mockRemoveDispute = registry.removeDispute as Mock;

command.run();
command.undo();

expect(mockRemoveDispute).toHaveBeenCalledWith(request.id);
});

it("throws if undoing the command before being run", () => {
const command = AddDispute.buildFromEvent(event, registry);

expect(() => command.undo()).toThrow(CommandNotRun);
});
});
});
Loading

0 comments on commit 93d1461

Please sign in to comment.