-
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: handle ProposeDisputed event #21
Changes from all commits
5314681
0f369d6
66dc205
098eb08
094c12b
9f9a509
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,7 +1,7 @@ | ||
import { BlockNumberService } from "@ebo-agent/blocknumber"; | ||
import { Caip2ChainId } from "@ebo-agent/blocknumber/dist/types.js"; | ||
import { ILogger } from "@ebo-agent/shared"; | ||
import { ContractFunctionRevertedError, Hex } from "viem"; | ||
import { ContractFunctionRevertedError } from "viem"; | ||
|
||
import { InvalidActorState } from "./exceptions/invalidActorState.exception.js"; | ||
import { RequestMismatch } from "./exceptions/requestMismatch.js"; | ||
|
@@ -17,7 +17,7 @@ import { Dispute, Request, Response, ResponseBody } from "./types/prophet.js"; | |
export class EboActor { | ||
constructor( | ||
private readonly actorRequest: { | ||
id: Hex; | ||
id: string; | ||
epoch: bigint; | ||
epochTimestamp: bigint; | ||
}, | ||
|
@@ -216,19 +216,120 @@ export class EboActor { | |
} | ||
} | ||
|
||
public async onResponseDisputed(_event: EboEvent<"ResponseDisputed">): Promise<void> { | ||
// TODO: implement | ||
return; | ||
/** | ||
* Returns the active actor request. | ||
* | ||
* @throws {InvalidActorState} when the request has not been added to the registry yet. | ||
* @returns the actor `Request` | ||
*/ | ||
private getActorRequest() { | ||
const request = this.registry.getRequest(this.actorRequest.id); | ||
|
||
if (request === undefined) throw new InvalidActorState(); | ||
|
||
return request; | ||
} | ||
|
||
private async disputeProposal(_dispute: Dispute): Promise<void> { | ||
// TODO: implement | ||
return; | ||
/** | ||
* Handle the `ResponseDisputed` event. | ||
* | ||
* @param event `ResponseDisputed` event. | ||
*/ | ||
public async onResponseDisputed(event: EboEvent<"ResponseDisputed">): Promise<void> { | ||
this.shouldHandleRequest(event.metadata.dispute.requestId); | ||
|
||
const dispute: Dispute = { | ||
id: event.metadata.disputeId, | ||
status: "Active", | ||
prophetData: event.metadata.dispute, | ||
}; | ||
|
||
this.registry.addDispute(event.metadata.disputeId, dispute); | ||
|
||
const request = this.getActorRequest(); | ||
const proposedResponse = this.registry.getResponse(event.metadata.responseId); | ||
|
||
if (!proposedResponse) throw new InvalidActorState(); | ||
|
||
const isValidDispute = await this.isValidDispute(proposedResponse); | ||
|
||
if (isValidDispute) await this.pledgeFor(request, dispute); | ||
else await this.pledgeAgainst(request, dispute); | ||
} | ||
|
||
private async isValidDispute(_dispute: Dispute): Promise<boolean> { | ||
// TODO: implement | ||
return true; | ||
/** | ||
* Check if a dispute is valid, comparing the already submitted and disputed proposal with | ||
* the response this actor would propose. | ||
* | ||
* @param proposedResponse the already submitted response | ||
* @returns true if the hypothetical proposal is different that the submitted one, false otherwise | ||
*/ | ||
private async isValidDispute(proposedResponse: Response) { | ||
const actorResponse = await this.buildResponse( | ||
proposedResponse.prophetData.response.chainId, | ||
); | ||
|
||
const equalResponses = this.equalResponses( | ||
actorResponse, | ||
proposedResponse.prophetData.response, | ||
); | ||
|
||
return !equalResponses; | ||
} | ||
|
||
/** | ||
* Pledge in favor of the dispute. | ||
* | ||
* @param request the dispute's `Request` | ||
* @param dispute the `Dispute` | ||
*/ | ||
private async pledgeFor(request: Request, dispute: Dispute) { | ||
try { | ||
this.logger.info(`Pledging against dispute ${dispute.id}`); | ||
|
||
await this.protocolProvider.pledgeForDispute(request.prophetData, dispute.prophetData); | ||
} catch (err) { | ||
if (err instanceof ContractFunctionRevertedError) { | ||
// TODO: handle each error appropriately | ||
this.logger.warn(`Pledging for dispute ${dispute.id} was reverted. Skipping...`); | ||
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. should we log the error here also ? 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. Maybe what we want is to have more granular errors thrown from the wdyt ? I will write it down on a linear task 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. 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. That's right, the ideal would be a situation in which we are able to handle each I'll add some 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. perfect, just wanted to be sure we were on the same page with that :) |
||
} else { | ||
// TODO: handle each error appropriately | ||
this.logger.error( | ||
`Actor handling request ${this.actorRequest.id} is not able to continue.`, | ||
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. same here |
||
); | ||
|
||
throw err; | ||
} | ||
} | ||
} | ||
|
||
/** | ||
* Pledge against the dispute. | ||
* | ||
* @param request the dispute's `Request` | ||
* @param dispute the `Dispute` | ||
*/ | ||
private async pledgeAgainst(request: Request, dispute: Dispute) { | ||
try { | ||
this.logger.info(`Pledging for dispute ${dispute.id}`); | ||
|
||
await this.protocolProvider.pledgeAgainstDispute( | ||
request.prophetData, | ||
dispute.prophetData, | ||
); | ||
} catch (err) { | ||
if (err instanceof ContractFunctionRevertedError) { | ||
// TODO: handle each error appropriately | ||
this.logger.warn(`Pledging on dispute ${dispute.id} was reverted. Skipping...`); | ||
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. ditto |
||
} else { | ||
// TODO: handle each error appropriately | ||
this.logger.error( | ||
`Actor handling request ${this.actorRequest.id} is not able to continue.`, | ||
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. dittooo |
||
); | ||
|
||
throw err; | ||
} | ||
} | ||
} | ||
|
||
public async onFinalizeRequest(_event: EboEvent<"RequestFinalizable">): Promise<void> { | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -5,7 +5,7 @@ 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(), | ||
private disputes: Map<string, Dispute> = new Map(), | ||
) {} | ||
|
||
/** @inheritdoc */ | ||
|
@@ -27,4 +27,14 @@ export class EboMemoryRegistry implements EboRegistry { | |
public getResponses() { | ||
return this.responses; | ||
} | ||
|
||
/** @inheritdoc */ | ||
public getResponse(responseId: string): Response | undefined { | ||
return this.responses.get(responseId); | ||
} | ||
|
||
/** @inheritdoc */ | ||
public addDispute(disputeId: string, dispute: Dispute): void { | ||
this.disputes.set(disputeId, dispute); | ||
} | ||
Comment on lines
+31
to
+39
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. oop encapsulation 🤓 |
||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,79 @@ | ||
import { BlockNumberService } from "@ebo-agent/blocknumber"; | ||
import { Caip2ChainId } from "@ebo-agent/blocknumber/dist/types"; | ||
import { ILogger } from "@ebo-agent/shared"; | ||
|
||
import { EboActor } from "../../../src/eboActor"; | ||
import { EboMemoryRegistry } from "../../../src/eboMemoryRegistry"; | ||
import { ProtocolProvider } from "../../../src/protocolProvider"; | ||
import { Request, Response } from "../../../src/types/prophet"; | ||
import { DEFAULT_MOCKED_PROTOCOL_CONTRACTS } from "../fixtures"; | ||
|
||
/** | ||
* Builds a base `EboActor` scaffolded with all its dependencies. | ||
* | ||
* @param request a `Request` to populate the `EboActor` with | ||
* @param logger logger | ||
* @returns | ||
*/ | ||
function buildEboActor(request: Request, logger: ILogger) { | ||
const { id, chainId, epoch, epochTimestamp } = request; | ||
|
||
const protocolProviderRpcUrls = ["http://localhost:8538"]; | ||
const protocolProvider = new ProtocolProvider( | ||
protocolProviderRpcUrls, | ||
DEFAULT_MOCKED_PROTOCOL_CONTRACTS, | ||
); | ||
|
||
const blockNumberRpcUrls = new Map<Caip2ChainId, string[]>([ | ||
[chainId, ["http://localhost:8539"]], | ||
]); | ||
const blockNumberService = new BlockNumberService(blockNumberRpcUrls, logger); | ||
|
||
const registry = new EboMemoryRegistry(); | ||
|
||
const actor = new EboActor( | ||
{ id, epoch, epochTimestamp }, | ||
protocolProvider, | ||
blockNumberService, | ||
registry, | ||
logger, | ||
); | ||
|
||
return { | ||
actor, | ||
protocolProvider, | ||
blockNumberService, | ||
registry, | ||
logger, | ||
}; | ||
} | ||
|
||
/** | ||
* Helper function to build a response based on a request. | ||
* | ||
* @param request the `Request` to base the response on | ||
* @param attributes custom attributes to set into the response to build | ||
* @returns a `Response` | ||
*/ | ||
function buildResponse(request: Request, attributes: Partial<Response> = {}): Response { | ||
const baseResponse: Response = { | ||
id: "0x01", | ||
wasDisputed: false, | ||
prophetData: { | ||
proposer: "0x01", | ||
requestId: request.id, | ||
response: { | ||
chainId: request.chainId, | ||
block: 1n, | ||
epoch: request.epoch, | ||
}, | ||
}, | ||
}; | ||
|
||
return { | ||
...baseResponse, | ||
...attributes, | ||
}; | ||
} | ||
|
||
export default { buildEboActor, buildResponse }; |
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.
this is just a double check right ? EboActor is selected by the EboProcessor using the requestId right ?
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.
I was debating myself with this, between having this check or not.
I guess that we could rely on
EboProcessor
+EboActorsManager
to work as expected, but I like the defensiveness of having this check withinEboActor
. If something goes wrong in the event → request → actor routing between the other two classes, this check will catch the error before even trying to do anything.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.
excellent, would be different if it was a heavy computation , but in this case works perfectly :)