-
Notifications
You must be signed in to change notification settings - Fork 0
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
feat: build onRequestCreated #18
Changes from 7 commits
6cf15f3
6bb11e6
40f0e03
6c7e02e
c8d6702
03e75c7
bd3f0db
33271d7
6647610
4a16f80
be694f1
c9bca60
2aba0ac
a91e4ef
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,23 +1,48 @@ | ||
import { BlockNumberService } from "@ebo-agent/blocknumber"; | ||
|
||
import { EboRegistry } from "./eboRegistry.js"; | ||
import { RequestMismatch } from "./exceptions/requestMismatch.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[]; | ||
private registry: EboRegistry; | ||
|
||
constructor( | ||
private readonly protocolProvider: ProtocolProvider, | ||
private readonly blockNumberService: BlockNumberService, | ||
private readonly requestId: string, | ||
) { | ||
this.requestActivity = []; | ||
this.registry = new EboRegistry(); | ||
} | ||
|
||
public async onRequestCreated(_event: EboEvent<"RequestCreated">): Promise<void> { | ||
// TODO: implement | ||
return; | ||
/** | ||
* 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); | ||
|
||
this.registry.addRequest(event.metadata.requestId, event.metadata.request); | ||
|
||
const { chainId } = event.metadata; | ||
const { currentEpoch, currentEpochTimestamp } = | ||
await this.protocolProvider.getCurrentEpoch(); | ||
|
||
const epochBlockNumber = await this.blockNumberService.getEpochBlockNumber( | ||
currentEpochTimestamp, | ||
chainId, | ||
); | ||
|
||
await this.protocolProvider.proposeResponse( | ||
this.requestId, | ||
currentEpoch, | ||
chainId, | ||
epochBlockNumber, | ||
); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. we should handle the case in which proposeResponse fails, if it is an rpc failure or if a propose was already posted for the requestId |
||
} | ||
|
||
public async onResponseProposed(_event: EboEvent<"ResponseDisputed">): Promise<void> { | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. probably not used now but getRequest should exist right? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. We'll probably need that, yep. Figured we'll add methods in the registry as needed, it'd be easier to know what are we going to use while implementing the |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,23 @@ | ||
import { Dispute, Request, Response } from "./types/prophet.js"; | ||
|
||
export class EboRegistry { | ||
private requests: Map<string, Request>; | ||
private responses: Map<string, Response>; | ||
private dispute: Map<string, Dispute>; | ||
|
||
constructor() { | ||
this.requests = new Map(); | ||
this.responses = new Map(); | ||
this.dispute = new Map(); | ||
} | ||
|
||
/** | ||
* Add a `Request` by ID. | ||
* | ||
* @param requestId the ID of the `Request` | ||
* @param request the `Request` | ||
*/ | ||
public addRequest(requestId: string, request: Request) { | ||
this.requests.set(requestId, request); | ||
} | ||
} |
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"; | ||
} | ||
} |
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" | ||
|
@@ -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 { | ||
|
@@ -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 | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🤣 |
||
: E extends "ResponseDisputed" | ||
? ResponseDisputed | ||
: E extends "DisputeStatusChanged" | ||
|
Original file line number | Diff line number | Diff line change | ||||||
---|---|---|---|---|---|---|---|---|
@@ -1,7 +1,123 @@ | ||||||||
import { describe } from "vitest"; | ||||||||
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 { EboActor } from "../src/eboActor.js"; | ||||||||
import { RequestMismatch } from "../src/exceptions/requestMismatch.js"; | ||||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
this is related to the other comment |
||||||||
import { ProtocolProvider } from "../src/protocolProvider.js"; | ||||||||
import { EboEvent } from "../src/types/events.js"; | ||||||||
|
||||||||
const logger = Logger.getInstance(); | ||||||||
|
||||||||
const protocolContracts = { | ||||||||
oracle: "0x123456" as Address, | ||||||||
epochManager: "0x654321" as Address, | ||||||||
}; | ||||||||
|
||||||||
describe("EboActor", () => { | ||||||||
describe.skip("onRequestCreated"); | ||||||||
describe("onRequestCreated", () => { | ||||||||
const requestId: Address = "0x12345"; | ||||||||
const indexedChainId: Caip2ChainId = "eip155:137"; | ||||||||
|
||||||||
const protocolEpoch = { | ||||||||
currentEpoch: 1n, | ||||||||
currentEpochBlockNumber: 1n, | ||||||||
currentEpochTimestamp: BigInt(Date.UTC(2024, 1, 1, 0, 0, 0, 0)), | ||||||||
}; | ||||||||
|
||||||||
const requestCreatedEvent: EboEvent<"RequestCreated"> = { | ||||||||
blockNumber: 34n, | ||||||||
logIndex: 1, | ||||||||
name: "RequestCreated", | ||||||||
metadata: { | ||||||||
chainId: "eip155:10", | ||||||||
epoch: protocolEpoch.currentEpoch, | ||||||||
requestId: requestId, | ||||||||
}, | ||||||||
}; | ||||||||
|
||||||||
let protocolProvider: ProtocolProvider; | ||||||||
let blockNumberService: BlockNumberService; | ||||||||
|
||||||||
beforeEach(() => { | ||||||||
protocolProvider = new ProtocolProvider(["http://localhost:8538"], protocolContracts); | ||||||||
|
||||||||
const chainRpcUrls = new Map<Caip2ChainId, string[]>(); | ||||||||
chainRpcUrls.set(indexedChainId, ["http://localhost:8539"]); | ||||||||
|
||||||||
blockNumberService = new BlockNumberService(chainRpcUrls, logger); | ||||||||
}); | ||||||||
|
||||||||
it("proposes a response", 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, requestId); | ||||||||
|
||||||||
await actor.onRequestCreated(requestCreatedEvent); | ||||||||
|
||||||||
expect(proposeResponseMock).toHaveBeenCalledWith( | ||||||||
requestCreatedEvent.metadata.requestId, | ||||||||
protocolEpoch.currentEpoch, | ||||||||
requestCreatedEvent.metadata.chainId, | ||||||||
indexedEpochBlockNumber, | ||||||||
); | ||||||||
}); | ||||||||
|
||||||||
it("throws if the event's request id does not match with actor's", () => { | ||||||||
const noMatchRequestCreatedEvent: EboEvent<"RequestCreated"> = { | ||||||||
blockNumber: 34n, | ||||||||
logIndex: 1, | ||||||||
name: "RequestCreated", | ||||||||
metadata: { | ||||||||
chainId: "eip155:10", | ||||||||
epoch: protocolEpoch.currentEpoch, | ||||||||
requestId: "0x000000" as Address, | ||||||||
}, | ||||||||
}; | ||||||||
|
||||||||
const actor = new EboActor(protocolProvider, blockNumberService, requestId); | ||||||||
|
||||||||
expect(actor.onRequestCreated(noMatchRequestCreatedEvent)).rejects.toBeInstanceOf( | ||||||||
RequestMismatch, | ||||||||
); | ||||||||
}); | ||||||||
|
||||||||
it("throws if current epoch cannot be fetched", () => { | ||||||||
vi.spyOn(protocolProvider, "getCurrentEpoch").mockRejectedValue(new Error()); | ||||||||
|
||||||||
const actor = new EboActor(protocolProvider, blockNumberService, requestId); | ||||||||
|
||||||||
expect(actor.onRequestCreated(requestCreatedEvent)).rejects.toBeDefined(); | ||||||||
}); | ||||||||
|
||||||||
it("throws if the indexed chain block number cannot be fetched", () => { | ||||||||
vi.spyOn(protocolProvider, "getCurrentEpoch").mockResolvedValue(protocolEpoch); | ||||||||
vi.spyOn(blockNumberService, "getEpochBlockNumber").mockRejectedValue(new Error()); | ||||||||
|
||||||||
const actor = new EboActor(protocolProvider, blockNumberService, requestId); | ||||||||
|
||||||||
expect(actor.onRequestCreated(requestCreatedEvent)).rejects.toBeDefined(); | ||||||||
}); | ||||||||
}); | ||||||||
|
||||||||
describe.skip("onResponseProposed"); | ||||||||
describe.skip("onResponseDisputed"); | ||||||||
describe.skip("onFinalizeRequest"); | ||||||||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
shouldn't the registry be injected as a Dependency here?
also, maybe use an interface IEboRegistry, so if we wanted to later change for a real DB instead of in-memory maps we can easily switch to that. (i understand that the registry is sort of Database/Cache)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
You're absolutely right!