Skip to content

Commit

Permalink
feat: handle already proposed responses
Browse files Browse the repository at this point in the history
  • Loading branch information
0xyaco committed Aug 7, 2024
1 parent bd3f0db commit 33271d7
Show file tree
Hide file tree
Showing 5 changed files with 137 additions and 12 deletions.
32 changes: 27 additions & 5 deletions packages/automated-dispute/src/eboActor.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
import { BlockNumberService } from "@ebo-agent/blocknumber";
import { Caip2ChainId } from "@ebo-agent/blocknumber/dist/types.js";
import { ILogger } from "@ebo-agent/shared";

import { EboRegistry } from "./eboRegistry.js";
import { RequestMismatch } from "./exceptions/requestMismatch.js";
Expand All @@ -7,15 +9,13 @@ import { EboEvent } from "./types/events.js";
import { Dispute, Response } from "./types/prophet.js";

export class EboActor {
private registry: EboRegistry;

constructor(
private readonly protocolProvider: ProtocolProvider,
private readonly blockNumberService: BlockNumberService,
private readonly registry: EboRegistry,
private readonly requestId: string,
) {
this.registry = new EboRegistry();
}
private readonly logger: ILogger,
) {}

/**
* Handle RequestCreated event.
Expand All @@ -37,6 +37,10 @@ export class EboActor {
chainId,
);

if (this.alreadyProposed(currentEpoch, chainId, epochBlockNumber)) {
return;
}

await this.protocolProvider.proposeResponse(
this.requestId,
currentEpoch,
Expand All @@ -45,6 +49,24 @@ export class EboActor {
);
}

private alreadyProposed(epoch: bigint, chainId: Caip2ChainId, blockNumber: bigint) {
const responses = this.registry.getResponses();

for (const [responseId, response] of responses) {
if (response.response.block != blockNumber) continue;
if (response.response.chainId != chainId) continue;
if (response.response.epoch != epoch) continue;

this.logger.info(
`Block ${blockNumber} for epoch ${epoch} and chain ${chainId} already proposed on response ${responseId}. Skipping...`,
);

return true;
}

return false;
}

public async onResponseProposed(_event: EboEvent<"ResponseDisputed">): Promise<void> {
// TODO: implement
return;
Expand Down
9 changes: 9 additions & 0 deletions packages/automated-dispute/src/eboRegistry.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,4 +20,13 @@ export class EboRegistry {
public addRequest(requestId: string, request: Request) {
this.requests.set(requestId, request);
}

/**
* Return all responses
*
* @returns responses map
*/
public getResponses() {
return this.responses;
}
}
6 changes: 5 additions & 1 deletion packages/automated-dispute/src/protocolProvider.ts
Original file line number Diff line number Diff line change
Expand Up @@ -117,7 +117,11 @@ export class ProtocolProvider {
response: {
proposer: "0x12345678901234567890123456789012",
requestId: "0x01",
response: "0x01234",
response: {
block: 1n,
chainId: "eip155:1",
epoch: 20n,
},
},
},
} as EboEvent<"ResponseProposed">,
Expand Down
9 changes: 8 additions & 1 deletion packages/automated-dispute/src/types/prophet.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import { Caip2ChainId } from "@ebo-agent/blocknumber/dist/types.js";
import { Address } from "viem";

export interface Request {
Expand All @@ -13,7 +14,13 @@ export interface Request {
export interface Response {
proposer: Address;
requestId: string;
response: string;

// To be byte-encode when sending it to Prophet
response: {
chainId: Caip2ChainId; // Pending on-chain definition on CAIP-2 usage
block: bigint;
epoch: bigint;
};
}

export interface Dispute {
Expand Down
93 changes: 88 additions & 5 deletions packages/automated-dispute/tests/eboActor.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,14 @@ import { BlockNumberService } from "@ebo-agent/blocknumber";
import { Caip2ChainId } from "@ebo-agent/blocknumber/dist/types.js";
import { Logger } from "@ebo-agent/shared";
import { Address } from "viem";
import { beforeEach, describe, expect, it, vi } from "vitest";
import { beforeEach, describe, expect, it, Mock, vi } from "vitest";

import { EboActor } from "../src/eboActor.js";
import { EboRegistry } from "../src/eboRegistry.js";
import { RequestMismatch } from "../src/exceptions/requestMismatch.js";
import { ProtocolProvider } from "../src/protocolProvider.js";
import { EboEvent } from "../src/types/events.js";
import { Response } from "../src/types/prophet.js";

const logger = Logger.getInstance();

Expand All @@ -16,6 +18,15 @@ const protocolContracts = {
epochManager: "0x654321" as Address,
};

const BASE_REQUEST = {
disputeModule: "0x01" as Address,
finalityModule: "0x02" as Address,
requestModule: "0x03" as Address,
resolutionModule: "0x04" as Address,
responseModule: "0x05" as Address,
requester: "0x10" as Address,
};

describe("EboActor", () => {
describe("onRequestCreated", () => {
const requestId: Address = "0x12345";
Expand All @@ -35,11 +46,13 @@ describe("EboActor", () => {
chainId: "eip155:10",
epoch: protocolEpoch.currentEpoch,
requestId: requestId,
request: BASE_REQUEST,
},
};

let protocolProvider: ProtocolProvider;
let blockNumberService: BlockNumberService;
let registry: EboRegistry;

beforeEach(() => {
protocolProvider = new ProtocolProvider(["http://localhost:8538"], protocolContracts);
Expand All @@ -48,6 +61,7 @@ describe("EboActor", () => {
chainRpcUrls.set(indexedChainId, ["http://localhost:8539"]);

blockNumberService = new BlockNumberService(chainRpcUrls, logger);
registry = new EboRegistry();
});

it("proposes a response", async () => {
Expand All @@ -69,7 +83,13 @@ describe("EboActor", () => {
) => Promise.resolve(),
);

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

await actor.onRequestCreated(requestCreatedEvent);

Expand All @@ -81,6 +101,51 @@ describe("EboActor", () => {
);
});

it("does not propose when already proposed the same block", async () => {
const indexedEpochBlockNumber = 48n;

vi.spyOn(protocolProvider, "getCurrentEpoch").mockResolvedValue(protocolEpoch);
vi.spyOn(blockNumberService, "getEpochBlockNumber").mockResolvedValue(
indexedEpochBlockNumber,
);

const proposeResponseMock = vi.spyOn(protocolProvider, "proposeResponse");

proposeResponseMock.mockImplementation(
(
_requestId: string,
_epoch: bigint,
_chainId: Caip2ChainId,
_blockNumbre: bigint,
) => Promise.resolve(),
);

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

const previousResponses = new Map<string, Response>();
previousResponses.set("0x01", {
proposer: "0x02",
requestId: requestId,
response: {
block: indexedEpochBlockNumber,
chainId: requestCreatedEvent.metadata.chainId,
epoch: protocolEpoch.currentEpoch,
},
});

vi.spyOn(registry, "getResponses").mockReturnValue(previousResponses);

await actor.onRequestCreated(requestCreatedEvent);

expect(proposeResponseMock).not.toHaveBeenCalled();
});

it("throws if the event's request id does not match with actor's", () => {
const noMatchRequestCreatedEvent: EboEvent<"RequestCreated"> = {
blockNumber: 34n,
Expand All @@ -93,7 +158,13 @@ describe("EboActor", () => {
},
};

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

expect(actor.onRequestCreated(noMatchRequestCreatedEvent)).rejects.toBeInstanceOf(
RequestMismatch,
Expand All @@ -103,7 +174,13 @@ describe("EboActor", () => {
it("throws if current epoch cannot be fetched", () => {
vi.spyOn(protocolProvider, "getCurrentEpoch").mockRejectedValue(new Error());

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

expect(actor.onRequestCreated(requestCreatedEvent)).rejects.toBeDefined();
});
Expand All @@ -112,7 +189,13 @@ describe("EboActor", () => {
vi.spyOn(protocolProvider, "getCurrentEpoch").mockResolvedValue(protocolEpoch);
vi.spyOn(blockNumberService, "getEpochBlockNumber").mockRejectedValue(new Error());

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

expect(actor.onRequestCreated(requestCreatedEvent)).rejects.toBeDefined();
});
Expand Down

0 comments on commit 33271d7

Please sign in to comment.