Skip to content

Commit

Permalink
feat: build onRequestCreated (#18)
Browse files Browse the repository at this point in the history
# 🤖 Linear

Closes GRT-81

## Description

* Standardizes timestamps used through our providers to `BigInt` type,
to be consistent with viem `Block.timestamp` type
* Set up `EboActor.onRequestCreated` handler
  • Loading branch information
0xyaco authored Aug 8, 2024
1 parent 70dd4b6 commit 3933c43
Show file tree
Hide file tree
Showing 20 changed files with 480 additions and 50 deletions.
5 changes: 3 additions & 2 deletions packages/automated-dispute/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,8 @@
"author": "",
"license": "ISC",
"dependencies": {
"viem": "2.17.11",
"@ebo-agent/blocknumber": "workspace:*"
"@ebo-agent/blocknumber": "workspace:*",
"@ebo-agent/shared": "workspace:*",
"viem": "2.17.11"
}
}
112 changes: 105 additions & 7 deletions packages/automated-dispute/src/eboActor.ts
Original file line number Diff line number Diff line change
@@ -1,23 +1,121 @@
import { BlockNumberService } from "@ebo-agent/blocknumber";
import { Caip2ChainId } from "@ebo-agent/blocknumber/dist/types.js";
import { ILogger } from "@ebo-agent/shared";
import { ContractFunctionRevertedError } from "viem";

import { InvalidActorState } from "./exceptions/invalidActorState.exception.js";
import { RequestMismatch } from "./exceptions/requestMismatch.js";
import { EboRegistry } from "./interfaces/eboRegistry.js";
import { ProtocolProvider } from "./protocolProvider.js";
import { EboEvent } from "./types/events.js";
import { Dispute, Response } from "./types/prophet.js";

export class EboActor {
private requestActivity: unknown[];

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

/**
* Handle RequestCreated event.
*
* @param event RequestCreated event
*/
public async onRequestCreated(event: EboEvent<"RequestCreated">): Promise<void> {
if (event.metadata.requestId != this.requestId)
throw new RequestMismatch(this.requestId, 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.`,
);

throw new InvalidActorState();
}

this.registry.addRequest(event.metadata.requestId, event.metadata.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.
this.logger.info(
`There is an active proposal for request ${this.requestId}. Skipping...`,
);

return;
}

const { chainId } = event.metadata;
const { currentEpoch, currentEpochTimestamp } =
await this.protocolProvider.getCurrentEpoch();

const epochBlockNumber = await this.blockNumberService.getEpochBlockNumber(
currentEpochTimestamp,
chainId,
);

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

try {
await this.protocolProvider.proposeResponse(
this.requestId,
currentEpoch,
chainId,
epochBlockNumber,
);
} catch (err) {
if (err instanceof ContractFunctionRevertedError) {
this.logger.warn(
`Block ${epochBlockNumber} for epoch ${currentEpoch} and ` +
`chain ${chainId} was not proposed. Skipping proposal...`,
);
} else {
this.logger.error(
`Actor handling request ${this.requestId} is not able to continue.`,
);

throw err;
}
}
}

public async onRequestCreated(_event: EboEvent<"RequestCreated">): Promise<void> {
// TODO: implement
return;
/**
* Check if there's at least one proposal that has not received any dispute yet.
*
* @returns
*/
private anyActiveProposal() {
// TODO: implement this function
return false;
}

/**
* Check if the same proposal has already been made in the past.
*
* @param epoch epoch of the request
* @param chainId chain id of the request
* @param blockNumber proposed block number
* @returns true if there's a registry of a proposal with the same attributes, false otherwise
*/
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> {
Expand Down
25 changes: 25 additions & 0 deletions packages/automated-dispute/src/eboMemoryRegistry.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
import { EboRegistry } from "./interfaces/eboRegistry.js";
import { Dispute, Request, Response } from "./types/prophet.js";

export class EboMemoryRegistry implements EboRegistry {
constructor(
private requests: Map<string, Request> = new Map(),
private responses: Map<string, Response> = new Map(),
private dispute: Map<string, Dispute> = new Map(),
) {}

/** @inheritdoc */
public addRequest(requestId: string, request: Request) {
this.requests.set(requestId, request);
}

/** @inheritdoc */
public getRequest(requestId: string) {
return this.requests.get(requestId);
}

/** @inheritdoc */
public getResponses() {
return this.responses;
}
}
1 change: 1 addition & 0 deletions packages/automated-dispute/src/exceptions/index.ts
Original file line number Diff line number Diff line change
@@ -1 +1,2 @@
export * from "./rpcUrlsEmpty.exception.js";
export * from "./invalidActorState.exception.js";
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
export class InvalidActorState extends Error {
constructor() {
// TODO: we'll want to dump the Actor state into stderr at this point
super("The actor is in an invalid state.");

this.name = "InvalidActorState";
}
}
6 changes: 6 additions & 0 deletions packages/automated-dispute/src/exceptions/requestMismatch.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
export class RequestMismatch extends Error {
constructor(requestId: string, eventRequestId: string) {
super(`Actor handling request ${requestId} received a request ${eventRequestId} event.`);
this.name = "RequestMismatch";
}
}
27 changes: 27 additions & 0 deletions packages/automated-dispute/src/interfaces/eboRegistry.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
import { Request, Response } from "../types/prophet.js";

/** Registry that stores Prophet entities (ie. requests, responses and disputes) */
export interface EboRegistry {
/**
* Add a `Request` by ID.
*
* @param requestId the ID of the `Request`
* @param request the `Request`
*/
addRequest(requestId: string, request: Request): void;

/**
* Get a `Request` by ID.
*
* @param requestId request ID
* @returns the request if already added into registry, `undefined` otherwise
*/
getRequest(requestId: string): Request | undefined;

/**
* Return all responses
*
* @returns responses map
*/
getResponses(): Map<string, Response>;
}
38 changes: 31 additions & 7 deletions packages/automated-dispute/src/protocolProvider.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
import { Caip2ChainId } from "@ebo-agent/blocknumber/dist/types.js";
import { Timestamp } from "@ebo-agent/shared";
import {
Address,
createPublicClient,
Expand Down Expand Up @@ -53,17 +55,28 @@ export class ProtocolProvider {
}

/**
* Gets the current epoch and the block number of the current epoch
* @returns The current epoch and the block number of the current epoch
* Gets the current epoch, the block number and its timestamp of the current epoch
*
* @returns The current epoch, its block number and its timestamp
*/
async getCurrentEpoch(): Promise<{ currentEpoch: bigint; currentEpochBlock: bigint }> {
const [currentEpoch, currentEpochBlock] = await Promise.all([
async getCurrentEpoch(): Promise<{
currentEpoch: bigint;
currentEpochBlockNumber: bigint;
currentEpochTimestamp: Timestamp;
}> {
const [currentEpoch, currentEpochBlockNumber] = await Promise.all([
this.epochManagerContract.read.currentEpoch(),
this.epochManagerContract.read.currentEpochBlock(),
]);

const currentEpochBlock = await this.client.getBlock({
blockNumber: currentEpochBlockNumber,
});

return {
currentEpoch,
currentEpochBlock,
currentEpochBlockNumber,
currentEpochTimestamp: currentEpochBlock.timestamp,
};
}

Expand All @@ -79,6 +92,8 @@ export class ProtocolProvider {
logIndex: 1,
metadata: {
requestId: "0x01",
chainId: "eip155:1",
epoch: 1n,
request: {
requester: "0x12345678901234567890123456789012",
requestModule: "0x12345678901234567890123456789012",
Expand All @@ -102,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 Expand Up @@ -173,7 +192,12 @@ export class ProtocolProvider {
return;
}

async proposeResponse(_request: Request, _response: Response): Promise<void> {
async proposeResponse(
_requestId: string,
_epoch: bigint,
_chainId: Caip2ChainId,
_blockNumber: bigint,
): Promise<void> {
// TODO: implement actual method
return;
}
Expand Down
16 changes: 10 additions & 6 deletions packages/automated-dispute/src/types/events.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import { Caip2ChainId } from "@ebo-agent/blocknumber/dist/types.js";
import { Log } from "viem";

import { Dispute, Request } from "./prophet.js";
import { Dispute, Request, Response } from "./prophet.js";

export type EboEventName =
| "NewEpoch"
Expand All @@ -17,14 +18,17 @@ export interface NewEpoch {
epochBlockNumber: bigint;
}

export interface ResponseCreated {
export interface ResponseProposed {
requestId: string;
request: Request;
responseId: string;
response: Response;
}

export interface RequestCreated {
requestId: string;
epoch: bigint;
chainId: Caip2ChainId;
request: Request;
requestId: string;
}

export interface ResponseDisputed {
Expand Down Expand Up @@ -60,8 +64,8 @@ export type EboEventData<E extends EboEventName> = E extends "NewEpoch"
? NewEpoch
: E extends "RequestCreated"
? RequestCreated
: E extends "ResponseCreated"
? ResponseCreated
: E extends "ResponseProposed"
? ResponseProposed
: E extends "ResponseDisputed"
? ResponseDisputed
: E extends "DisputeStatusChanged"
Expand Down
10 changes: 8 additions & 2 deletions 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 @@ -7,13 +8,18 @@ export interface Request {
disputeModule: Address;
resolutionModule: Address;
finalityModule: Address;
// We might need here modules' data too
}

export interface Response {
proposer: Address;
requestId: string;
response: Uint8Array;

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

export interface Dispute {
Expand Down
Loading

0 comments on commit 3933c43

Please sign in to comment.