Skip to content

Commit

Permalink
fix: handle decoding errors (#80)
Browse files Browse the repository at this point in the history
# 🤖 Linear

Closes GRT-242

## Description

* Catches all errors thrown by decoding `bytes` properties read from
Prophet events
* Removes `ProphetCodec.decode...` calls from ProtocolProvider
`getXXXEvent` to use them during actual event processing and not event
fetching
  • Loading branch information
0xyaco authored Nov 1, 2024
1 parent 7c18d94 commit 42ee7c4
Show file tree
Hide file tree
Showing 22 changed files with 344 additions and 83 deletions.
19 changes: 9 additions & 10 deletions packages/automated-dispute/src/exceptions/index.ts
Original file line number Diff line number Diff line change
@@ -1,21 +1,20 @@
export * from "./blockNumberServiceRequired.exception.js";
export * from "./customContractError.js";
export * from "./decodeLogDataFailure.js";
export * from "./eboActor/index.js";
export * from "./eboProcessor/index.js";
export * from "./eboRegistry/index.js";

export * from "./errorFactory.js";
export * from "./invalidAccountOnClient.exception.js";
export * from "./invalidActorState.exception.js";
export * from "./invalidBlockHash.exception.js";
export * from "./invalidBlockRangeError.exception.js";
export * from "./invalidDisputeStatus.exception.js";
export * from "./prophetDecodingError.exception.js";
export * from "./requestAlreadyHandled.exception.js";
export * from "./requestMismatch.exception.js";
export * from "./responseAlreadyProposed.exception.js";
export * from "./rpcUrlsEmpty.exception.js";
export * from "./transactionExecutionError.exception.js";
export * from "./invalidAccountOnClient.exception.js";
export * from "./unsupportedEvent.exception.js";
export * from "./decodeLogDataFailure.js";
export * from "./invalidBlockRangeError.exception.js";
export * from "./unknownCustomError.exception.js";
export * from "./invalidBlockHash.exception.js";
export * from "./unknownDisputeStatus.exception.js";
export * from "./blockNumberServiceRequired.exception.js";
export * from "./customContractError.js";
export * from "./errorFactory.js";
export * from "./unsupportedEvent.exception.js";
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
import { ByteArray, Hex } from "viem";

export class ProphetDecodingError extends Error {
constructor(
public readonly id: string,
public readonly data: ByteArray | Hex,
public readonly err?: Error,
) {
super(`Failed to decode ${id} with data ${data}.`);

this.name = "ProphetDecodingError";
}
}

This file was deleted.

3 changes: 1 addition & 2 deletions packages/automated-dispute/src/providers/protocolProvider.ts
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,6 @@ import {
RpcUrlsEmpty,
TransactionExecutionError,
} from "../exceptions/index.js";
import { ProphetCodec } from "../external.js";
import {
IProtocolProvider,
IReadProvider,
Expand Down Expand Up @@ -467,7 +466,7 @@ export class ProtocolProvider implements IProtocolProvider {
responseId: HexUtils.normalize(_dispute.responseId) as ResponseId,
requestId: HexUtils.normalize(_dispute.requestId) as RequestId,
},
status: ProphetCodec.decodeDisputeStatus(_status),
status: _status,
blockNumber: event.blockNumber,
},
} as EboEvent<"DisputeStatusUpdated">;
Expand Down
32 changes: 25 additions & 7 deletions packages/automated-dispute/src/services/eboActor.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ import {
InvalidActorState,
InvalidDisputeStatus,
PastEventEnqueueError,
ProphetDecodingError,
RequestMismatch,
ResponseAlreadyProposed,
ResponseNotFound,
Expand Down Expand Up @@ -149,9 +150,26 @@ export class EboActor {
while ((event = this.eventsQueue.pop())) {
this.lastEventProcessed = event;

const updateStateCommand = this.buildUpdateStateCommand(event);
let updateStateCommand: EboRegistryCommand;

updateStateCommand.run();
try {
updateStateCommand = this.buildUpdateStateCommand(event);
updateStateCommand.run();
} catch (err) {
if (err instanceof ProphetDecodingError) {
// Skipping malformed entities
this.logger.warn(
stringify({
reason: err.err?.name,
message: err.message,
}),
);

continue;
} else {
throw err;
}
}

try {
if (this.eventsQueue.isEmpty()) {
Expand Down Expand Up @@ -391,12 +409,12 @@ export class EboActor {
private getActiveDisputes(): Dispute[] {
const disputes = this.registry.getDisputes();

return disputes.filter((dispute) => dispute.status === "Active");
return disputes.filter((dispute) => dispute.decodedData.status === "Active");
}

// TODO: extract this into another service
private canBeSettled(request: Request, dispute: Dispute, atTimestamp: UnixTimestamp): boolean {
if (dispute.status !== "Active") return false;
if (dispute.decodedData.status !== "Active") return false;

const { bondEscalationDeadline, tyingBuffer } = request.decodedData.disputeModuleData;
const deadline = (dispute.createdAt.timestamp +
Expand Down Expand Up @@ -499,7 +517,7 @@ export class EboActor {
// Response is still able to be disputed
if (atTimestamp <= disputeWindow) return false;

return dispute ? ["Lost", "None"].includes(dispute.status) : true;
return dispute ? ["Lost", "None"].includes(dispute.decodedData.status) : true;
}

/**
Expand Down Expand Up @@ -548,7 +566,7 @@ export class EboActor {
// the proposal non-active.
const activeStatus: DisputeStatus[] = ["None", "Active"];

return activeStatus.includes(dispute.status);
return activeStatus.includes(dispute.decodedData.status);
});
}

Expand Down Expand Up @@ -941,7 +959,7 @@ export class EboActor {
private async onDisputeStatusChanged(event: EboEvent<"DisputeStatusUpdated">): Promise<void> {
const request = this.getActorRequest();
const disputeId = event.metadata.disputeId;
const disputeStatus = event.metadata.status;
const disputeStatus = ProphetCodec.decodeDisputeStatus(event.metadata.status);

this.logger.info(`Dispute ${disputeId} status changed to ${disputeStatus}.`);

Expand Down
1 change: 1 addition & 0 deletions packages/automated-dispute/src/services/eboProcessor.ts
Original file line number Diff line number Diff line change
Expand Up @@ -177,6 +177,7 @@ export class EboProcessor {
const currentEpoch = await this.protocolProvider.getCurrentEpoch();

this.logger.info(`Current epoch fetched.`);
this.logger.debug(stringify(currentEpoch));

return currentEpoch;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,9 @@ export class AddDispute implements EboRegistryCommand {
blockNumber: event.blockNumber,
logIndex: event.logIndex,
},
status: "Active",
decodedData: {
status: "Active",
},
prophetData: event.metadata.dispute,
};

Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import { CommandAlreadyRun, CommandNotRun, DisputeNotFound } from "../../../exceptions/index.js";
import { EboRegistry, EboRegistryCommand } from "../../../interfaces/index.js";
import { DisputeStatus, EboEvent, EboEventName } from "../../../types/index.js";
import { ProphetCodec } from "../../prophetCodec.js";

export class UpdateDisputeStatus implements EboRegistryCommand {
private wasRun: boolean = false;
Expand All @@ -19,7 +20,7 @@ export class UpdateDisputeStatus implements EboRegistryCommand {
const disputeId = event.metadata.disputeId;

const status = this.isDisputeStatusChangedEvent(event)
? event.metadata.status
? ProphetCodec.decodeDisputeStatus(event.metadata.status)
: "Escalated";

return new UpdateDisputeStatus(registry, disputeId, status);
Expand All @@ -38,7 +39,7 @@ export class UpdateDisputeStatus implements EboRegistryCommand {

if (!dispute) throw new DisputeNotFound(this.disputeId);

this.previousStatus = dispute.status;
this.previousStatus = dispute.decodedData.status;

this.registry.updateDisputeStatus(this.disputeId, this.status);

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -96,7 +96,9 @@ export class EboMemoryRegistry implements EboRegistry {

this.disputes.set(disputeId, {
...dispute,
status: status,
decodedData: {
status: status,
},
});
}

Expand Down
68 changes: 59 additions & 9 deletions packages/automated-dispute/src/services/prophetCodec.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,15 @@
import { Caip2ChainId } from "@ebo-agent/shared";
import { Address, decodeAbiParameters, encodeAbiParameters } from "viem";
import {
AbiParameter,
Address,
ByteArray,
decodeAbiParameters,
encodeAbiParameters,
Hex,
toHex,
} from "viem";

import { UnknownDisputeStatus } from "../exceptions/unknownDisputeStatus.exception.js";
import { ProphetDecodingError } from "../exceptions/index.js";
import { DisputeStatus, Request, Response } from "../types/prophet.js";

const REQUEST_MODULE_DATA_REQUEST_ABI_FIELDS = [
Expand Down Expand Up @@ -60,6 +68,27 @@ const DISPUTE_STATUS_ENUM: DisputeStatus[] = [

/** Class to encode/decode Prophet's structs into/from a byte array */
export class ProphetCodec {
/**
* Decodes byte-serialized data.
*
* @param id identifier of the decoding to be used in case it fails
* @param params abi fields to use for decoding
* @param data data to be decoded
* @throws {ProphetDecodingError}
* @returns the decoded data
*/
private static decode<const params extends readonly AbiParameter[]>(
id: string,
params: params,
data: ByteArray | Hex,
) {
try {
return decodeAbiParameters(params, data);
} catch (err) {
throw new ProphetDecodingError(id, data, err instanceof Error ? err : undefined);
}
}

/**
* Decodes the request's request module data bytes into an object.
*
Expand All @@ -70,7 +99,8 @@ export class ProphetCodec {
static decodeRequestRequestModuleData(
requestModuleData: Request["prophetData"]["requestModuleData"],
): Request["decodedData"]["requestModuleData"] {
const decodeParameters = decodeAbiParameters(
const decodeParameters = this.decode(
"request.requestModuleData",
REQUEST_MODULE_DATA_REQUEST_ABI_FIELDS,
requestModuleData,
);
Expand Down Expand Up @@ -107,7 +137,8 @@ export class ProphetCodec {
static decodeRequestResponseModuleData(
responseModuleData: Request["prophetData"]["responseModuleData"],
): Request["decodedData"]["responseModuleData"] {
const decodedParameters = decodeAbiParameters(
const decodedParameters = this.decode(
"request.responseModuleData",
RESPONSE_MODULE_DATA_REQUEST_ABI_FIELDS,
responseModuleData,
);
Expand Down Expand Up @@ -138,7 +169,8 @@ export class ProphetCodec {
static decodeRequestDisputeModuleData(
disputeModuleData: Request["prophetData"]["disputeModuleData"],
): Request["decodedData"]["disputeModuleData"] {
const decodedParameters = decodeAbiParameters(
const decodedParameters = this.decode(
"request.disputeModuleData",
DISPUTE_MODULE_DATA_REQUEST_ABI_FIELDS,
disputeModuleData,
);
Expand Down Expand Up @@ -182,7 +214,11 @@ export class ProphetCodec {
static decodeResponse(
response: Response["prophetData"]["response"],
): Response["decodedData"]["response"] {
const decodedParameters = decodeAbiParameters(RESPONSE_RESPONSE_ABI_FIELDS, response);
const decodedParameters = this.decode(
"response.response",
RESPONSE_RESPONSE_ABI_FIELDS,
response,
);

return {
block: decodedParameters[0],
Expand All @@ -201,9 +237,23 @@ export class ProphetCodec {
* @returns The DisputeStatus string corresponding to the input value.
*/
static decodeDisputeStatus(status: number): DisputeStatus {
const disputeStatus = DISPUTE_STATUS_ENUM[status];
const disputeStatus: DisputeStatus | undefined = DISPUTE_STATUS_ENUM[status];

if (disputeStatus) return disputeStatus;
else throw new ProphetDecodingError("dispute.status", toHex(status.toString()));
}

/**
* Encodes a DisputeStatus string into its enum uint8 index.
*
* @param {Response["prophetData"]["response"]} response - The response body bytes.
* @throws {DecodeAbiParametersErrorType}
* @returns {Response["decodedData"]["response"]} Decoded response body object.
*/
static encodeDisputeStatus(status: DisputeStatus): number {
const index = DISPUTE_STATUS_ENUM.indexOf(status);

if (!disputeStatus) throw new UnknownDisputeStatus(status);
else return disputeStatus;
// TODO: throw ProphetEncodingError
return index;
}
}
12 changes: 2 additions & 10 deletions packages/automated-dispute/src/types/events.ts
Original file line number Diff line number Diff line change
@@ -1,15 +1,7 @@
import { UnixTimestamp } from "@ebo-agent/shared";
import { Address, Hex, Log } from "viem";

import {
Dispute,
DisputeId,
DisputeStatus,
Request,
RequestId,
Response,
ResponseId,
} from "./prophet.js";
import { Dispute, DisputeId, Request, RequestId, Response, ResponseId } from "./prophet.js";

export type EboEventName =
| "RequestCreated"
Expand Down Expand Up @@ -40,7 +32,7 @@ export interface ResponseDisputed {
export interface DisputeStatusUpdated {
disputeId: DisputeId;
dispute: Dispute["prophetData"];
status: DisputeStatus;
status: number;
blockNumber: bigint;
}

Expand Down
5 changes: 4 additions & 1 deletion packages/automated-dispute/src/types/prophet.ts
Original file line number Diff line number Diff line change
Expand Up @@ -96,7 +96,10 @@ export interface Dispute {
blockNumber: bigint;
logIndex: number;
};
status: DisputeStatus;

decodedData: {
status: DisputeStatus;
};

prophetData: {
disputer: Address;
Expand Down
4 changes: 3 additions & 1 deletion packages/automated-dispute/tests/mocks/eboActor.mocks.ts
Original file line number Diff line number Diff line change
Expand Up @@ -196,12 +196,14 @@ export function buildDispute(
): Dispute {
const baseDispute: Dispute = {
id: "0x01" as DisputeId,
status: "Active",
createdAt: {
timestamp: (response.createdAt.timestamp + 1n) as UnixTimestamp,
blockNumber: response.createdAt.blockNumber + 1n,
logIndex: response.createdAt.logIndex + 1,
},
decodedData: {
status: "Active",
},
prophetData: {
disputer: "0x01",
proposer: response.prophetData.proposer,
Expand Down
Loading

0 comments on commit 42ee7c4

Please sign in to comment.