Skip to content
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

fix: handle DisputeEscalated events for not first disputes #81

Merged
merged 4 commits into from
Nov 1, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
32 changes: 32 additions & 0 deletions apps/agent/config.tenderly.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
protocolProvider:
rpcsConfig:
l1:
chainId: eip155:11155111
transactionReceiptConfirmations: 1
timeout: 10000
retryInterval: 150
l2:
chainId: eip155:421614
transactionReceiptConfirmations: 1
timeout: 10000
retryInterval: 150
contracts:
oracle: "0x10224eff6B1Caaf5daC49B2e7104b7161484B128"
epochManager: "0x7975475801BEf845f10Ce7784DC69aB1e0344f11"
eboRequestCreator: "0xa13318684281a820304C164427396385C306d870"
bondEscalationModule: "0x52d7728fE87826FfF51b21b303e2FF7cB04F6Aec"
horizonAccountingExtension: "0xbDAB27D1903da4e18B0D1BE873E18924514E52eC"

blockNumberService:
blockmetaConfig:
baseUrl: "localhost:443"
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

just a double check that it's the correct port since its HTTPS one

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It's kinda a placeholder for Tenderly config tbh, we are not indexing any chain that needs the blockmeta service.

servicePaths:
blockByTime: /sf.blockmeta.v2.BlockByTime
block: /sf.blockmeta.v2.Block
bearerTokenExpirationWindow: 31536000000

processor:
msBetweenChecks: 7500
accountingModules:
responseModule: "0xb97C59331F89a852Ae21aee215Da28820c533649"
escalationModule: "0x52d7728fE87826FfF51b21b303e2FF7cB04F6Aec"
Original file line number Diff line number Diff line change
@@ -1,4 +1,9 @@
export interface EboRegistryCommand {
/**
* Return the command name
*/
name(): string;

/**
* Run a command to update the registry
*/
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -524,7 +524,6 @@ export class ProtocolProvider implements IProtocolProvider {
requestId: HexUtils.normalize(_dispute.requestId) as RequestId,
},
caller: _caller as Address,
blockNumber: event.blockNumber,
},
} as EboEvent<"DisputeEscalated">;
}),
Expand Down
60 changes: 49 additions & 11 deletions packages/automated-dispute/src/services/eboActor.ts
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@ import {
AddRequest,
AddResponse,
FinalizeRequest,
NoOp,
ProphetCodec,
UpdateDisputeStatus,
} from "../services/index.js";
Expand Down Expand Up @@ -154,7 +155,13 @@ export class EboActor {

try {
updateStateCommand = this.buildUpdateStateCommand(event);

this.logger.debug("Running command...");
this.logger.debug(stringify({ command: updateStateCommand.name() }));

updateStateCommand.run();

this.logger.debug("Command run successfully.");
} catch (err) {
if (err instanceof ProphetDecodingError) {
// Skipping malformed entities
Expand All @@ -172,7 +179,10 @@ export class EboActor {
}

try {
if (this.eventsQueue.isEmpty()) {
const wasLastEvent = this.eventsQueue.isEmpty();
const isDisputeEscalatedEvent = event.name === "DisputeEscalated";

if (wasLastEvent || isDisputeEscalatedEvent) {
// `event` is the last and most recent event thus
// it needs to run some RPCs to keep Prophet's flow going on
await this.onLastEvent(event);
Expand Down Expand Up @@ -225,23 +235,44 @@ export class EboActor {
this.registry,
);

case "ResponseDisputed":
return AddDispute.buildFromEvent(
event as EboEvent<"ResponseDisputed">,
this.registry,
);
case "ResponseDisputed": {
const disputeId = (event as EboEvent<"ResponseDisputed">).metadata.disputeId;
const dispute = this.registry.getDispute(disputeId);

// Prophet's might emit the DisputeEscalated event prior the ResponseDisputed
// event (starting from the 2nd dispute within an EBO request due to the
// BondEscalationModule behavior).
//
// This force the agent to add the Dispute when processing the DisputeEscalated
// event, causing this event to be a no-op.
return dispute
? NoOp.build()
: AddDispute.buildFromEvent(
event as EboEvent<"ResponseDisputed">,
this.registry,
);
}

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

case "DisputeEscalated":
return UpdateDisputeStatus.buildFromEvent(
event as EboEvent<"DisputeEscalated">,
this.registry,
);
case "DisputeEscalated": {
const disputeId = (event as EboEvent<"DisputeEscalated">).metadata.disputeId;
const dispute = this.registry.getDispute(disputeId);

return dispute
? UpdateDisputeStatus.buildFromEvent(
event as EboEvent<"DisputeEscalated">,
this.registry,
)
: AddDispute.buildFromEvent(
event as EboEvent<"DisputeEscalated">,
this.registry,
);
}

case "OracleRequestFinalized":
return FinalizeRequest.buildFromEvent(
Expand Down Expand Up @@ -827,6 +858,12 @@ export class EboActor {
`Dispute ${event.metadata.disputeId} needs to be added to the internal registry.`,
);

if (dispute.decodedData.status === "Escalated") {
this.logger.warn(`Skipping dispute ${dispute.id} as it's already been escalated`);

return;
}

const request = this.getActorRequest();
const proposedResponse = this.registry.getResponse(event.metadata.responseId);

Expand Down Expand Up @@ -989,6 +1026,7 @@ export class EboActor {

private async onDisputeEscalated(event: EboEvent<"DisputeEscalated">) {
const request = this.getActorRequest();

this.logger.info(
`Dispute ${event.metadata.disputeId} for request ${request.id} has been escalated.`,
);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ export class AddDispute implements EboRegistryCommand {
) {}

public static buildFromEvent(
event: EboEvent<"ResponseDisputed">,
event: EboEvent<"ResponseDisputed" | "DisputeEscalated">,
registry: EboRegistry,
): AddDispute {
const dispute: Dispute = {
Expand All @@ -22,14 +22,18 @@ export class AddDispute implements EboRegistryCommand {
logIndex: event.logIndex,
},
decodedData: {
status: "Active",
status: event.name === "ResponseDisputed" ? "Active" : "Escalated",
},
prophetData: event.metadata.dispute,
};

return new AddDispute(registry, dispute);
}

name(): string {
return "AddDispute";
}

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

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,10 @@ export class AddRequest implements EboRegistryCommand {
return new AddRequest(registry, request);
}

name(): string {
return "AddRequest";
}

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

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,10 @@ export class AddResponse implements EboRegistryCommand {
return new AddResponse(registry, response);
}

name(): string {
return "AddResponse";
}

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

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,10 @@ export class FinalizeRequest implements EboRegistryCommand {
return new FinalizeRequest(registry, request);
}

name(): string {
return "FinalizeRequest";
}

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

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,4 +2,5 @@ export * from "./addDispute.js";
export * from "./addRequest.js";
export * from "./addResponse.js";
export * from "./finalizeRequest.js";
export * from "./noOp.js";
export * from "./updateDisputeStatus.js";
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
import { CommandAlreadyRun, CommandNotRun } from "../../../exceptions/index.js";
import { EboRegistryCommand } from "../../../interfaces/index.js";

export class NoOp implements EboRegistryCommand {
Copy link
Collaborator

@jahabeebs jahabeebs Oct 31, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Might be good to have some docs in this class as I'm curious what you mean by noop in this context---I know that it means "do nothing" usually but why it's not immediately clear to me why we need the build() command. Also, do you think we need some logging in build() would be useful for when it's invoked in ResponseDisputed so we know when a new noop instance is returned?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We could technically make the constructor public and use new NoOp(); I wanted to keep the same approach the other commands are using, just for the sake of things consistent.

Seems like a wise decision to log the no-op "execution", I agree!

private wasRun: boolean = false;

private constructor() {}

public static build(): NoOp {
return new NoOp();
}

name(): string {
return "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
Expand Up @@ -32,6 +32,10 @@ export class UpdateDisputeStatus implements EboRegistryCommand {
return event.name === "DisputeStatusUpdated";
}

name(): string {
return "UpdateDisputeStatus";
}

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

Expand Down
4 changes: 1 addition & 3 deletions packages/automated-dispute/src/types/events.ts
Original file line number Diff line number Diff line change
Expand Up @@ -33,20 +33,18 @@ export interface DisputeStatusUpdated {
disputeId: DisputeId;
dispute: Dispute["prophetData"];
status: number;
blockNumber: bigint;
}

export interface DisputeEscalated {
caller: Address;
disputeId: DisputeId;
blockNumber: bigint;
dispute: Dispute["prophetData"];
}

export interface OracleRequestFinalized {
requestId: RequestId;
responseId: ResponseId;
caller: Address;
blockNumber: bigint;
}

export type EboEventData<E extends EboEventName> = E extends "RequestCreated"
Expand Down
11 changes: 4 additions & 7 deletions packages/automated-dispute/tests/mocks/eboActor.mocks.ts
Original file line number Diff line number Diff line change
Expand Up @@ -93,13 +93,9 @@ export function buildEboActor(request: Request, logger: ILogger) {

const eventProcessingMutex = new Mutex();

let notificationService: NotificationService | undefined;

if (!notificationService) {
notificationService = {
notifyError: vi.fn().mockResolvedValue(undefined),
};
}
const notificationService: NotificationService = {
notifyError: vi.fn().mockResolvedValue(undefined),
};

const actor = new EboActor(
{ id, epoch, chainId },
Expand All @@ -118,6 +114,7 @@ export function buildEboActor(request: Request, logger: ILogger) {
registry,
eventProcessingMutex,
logger,
notificationService,
};
}

Expand Down
Loading