Skip to content

Commit

Permalink
fix: response contains only block number (#56)
Browse files Browse the repository at this point in the history
# 🤖 Linear

Closes GRT-186

## Description
Adapts the response body to the newly defined body that contains only
the `block` proposed in the response.

Previous version contained `block, chainId, epoch`. New version contains
only `block`.
  • Loading branch information
0xyaco authored Oct 7, 2024
1 parent 183a212 commit 86b48ec
Show file tree
Hide file tree
Showing 8 changed files with 84 additions and 73 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -2,3 +2,4 @@ export * from "./commandAlreadyRun.js";
export * from "./commandNotRun.js";
export * from "./disputeNotFound.js";
export * from "./requestNotFound.js";
export * from "./responseNotFound.js";
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
export class ResponseNotFound extends Error {
constructor(responseId: string) {
super(`Response ${responseId} was not found.`);

this.name = "ResponseNotFound";
}
}
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
import { ResponseBody } from "../types/prophet.js";
import { Request, ResponseBody } from "../types/prophet.js";

export class ResponseAlreadyProposed extends Error {
constructor(response: ResponseBody) {
constructor(request: Request, response: ResponseBody) {
super(
`Block ${response.block} was already proposed for epoch ${response.epoch} on chain ${response.chainId}`,
`Block ${response.block} was already proposed for epoch ${request.epoch} on chain ${request.chainId}`,
);

this.name = "ResponseAlreadyProposed";
Expand Down
30 changes: 6 additions & 24 deletions packages/automated-dispute/src/providers/protocolProvider.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { Caip2ChainId, Caip2Utils, InvalidChainId } from "@ebo-agent/blocknumber";
import { Caip2ChainId } from "@ebo-agent/blocknumber";
import {
Address,
BaseError,
Expand Down Expand Up @@ -75,11 +75,7 @@ export const REQUEST_DISPUTE_MODULE_DATA_ABI_FIELDS = [
{ name: "disputeWindow", type: "uint256" },
] as const;

export const RESPONSE_ABI_FIELDS = [
{ name: "chainId", type: "string" },
{ name: "epoch", type: "uint256" },
{ name: "block", type: "uint256" },
] as const;
export const RESPONSE_ABI_FIELDS = [{ name: "block", type: "uint256" }] as const;

export class ProtocolProvider implements IProtocolProvider {
private readClient: PublicClient<FallbackTransport<HttpTransport[]>>;
Expand Down Expand Up @@ -424,11 +420,7 @@ export class ProtocolProvider implements IProtocolProvider {
static encodeResponse(
response: Response["decodedData"]["response"],
): Response["prophetData"]["response"] {
return encodeAbiParameters(RESPONSE_ABI_FIELDS, [
response.chainId,
response.epoch,
response.block,
]);
return encodeAbiParameters(RESPONSE_ABI_FIELDS, [response.block]);
}

/**
Expand All @@ -442,19 +434,9 @@ export class ProtocolProvider implements IProtocolProvider {
): Response["decodedData"]["response"] {
const decodedParameters = decodeAbiParameters(RESPONSE_ABI_FIELDS, response);

const chainId = decodedParameters[0];

if (Caip2Utils.isCaip2ChainId(chainId)) {
return {
chainId: chainId,
epoch: decodedParameters[1],
block: decodedParameters[2],
};
} else {
throw new InvalidChainId(
`Could not decode response chain ID while decoding:\n${response}`,
);
}
return {
block: decodedParameters[0],
};
}

// TODO: waiting for ChainId to be merged for _chains parameter
Expand Down
107 changes: 67 additions & 40 deletions packages/automated-dispute/src/services/eboActor.ts
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ import {
PastEventEnqueueError,
RequestMismatch,
ResponseAlreadyProposed,
ResponseNotFound,
UnknownEvent,
} from "../exceptions/index.js";
import { EboRegistry, EboRegistryCommand } from "../interfaces/index.js";
Expand Down Expand Up @@ -54,6 +55,12 @@ const EBO_EVENT_COMPARATOR = (e1: EboEvent<EboEventName>, e2: EboEvent<EboEventN
return e1.logIndex - e2.logIndex;
};

/** Response properties needed to check response equality */
type EqualResponseParameters = {
prophetData: Pick<Response["prophetData"], "requestId">;
decodedData: Response["decodedData"];
};

/**
* Actor that handles a singular Prophet's request asking for the block number that corresponds
* to an instant on an indexed chain.
Expand Down Expand Up @@ -498,26 +505,31 @@ export class EboActor {
/**
* 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) {
private alreadyProposed(blockNumber: bigint) {
const request = this.getActorRequest();
const responses = this.registry.getResponses();
const newResponse: ResponseBody = {
epoch,
chainId,
block: blockNumber,

const newResponse = {
prophetData: {
requestId: request.id,
},
decodedData: {
response: {
block: blockNumber,
},
},
};

for (const proposedResponse of responses) {
const responseId = proposedResponse.id;
const proposedBody = proposedResponse.decodedData.response;

if (this.equalResponses(proposedBody, newResponse)) {
if (this.equalResponses(newResponse, proposedResponse)) {
this.logger.info(
`Block ${blockNumber} for epoch ${epoch} and chain ${chainId} already proposed on response ${responseId}. Skipping...`,
`Block ${blockNumber} for epoch ${request.epoch} and chain ${request.chainId} already proposed on response ${responseId}. Skipping...`,
);

return true;
Expand All @@ -533,7 +545,7 @@ export class EboActor {
* @param chainId chain ID to use in the response body
* @returns a response body
*/
private async buildResponse(chainId: Caip2ChainId): Promise<ResponseBody> {
private async buildResponseBody(chainId: Caip2ChainId): Promise<ResponseBody> {
// FIXME(non-current epochs): adapt this code to fetch timestamps corresponding
// to the first block of any epoch, not just the current epoch
const { startTimestamp: epochStartTimestamp } =
Expand All @@ -545,8 +557,6 @@ export class EboActor {
);

return {
epoch: this.actorRequest.epoch,
chainId: chainId,
block: epochBlockNumber,
};
}
Expand All @@ -557,11 +567,11 @@ export class EboActor {
* @param chainId the CAIP-2 compliant chain ID
*/
private async proposeResponse(chainId: Caip2ChainId): Promise<void> {
const responseBody = await this.buildResponse(chainId);
const responseBody = await this.buildResponseBody(chainId);
const request = this.getActorRequest();

if (this.alreadyProposed(responseBody.epoch, responseBody.chainId, responseBody.block)) {
throw new ResponseAlreadyProposed(responseBody);
if (this.alreadyProposed(responseBody.block)) {
throw new ResponseAlreadyProposed(request, responseBody);
}

const proposerAddress = this.protocolProvider.getAccountAddress();
Expand All @@ -577,8 +587,8 @@ export class EboActor {
} catch (err) {
if (err instanceof ContractFunctionRevertedError) {
this.logger.warn(
`Block ${responseBody.block} for epoch ${responseBody.epoch} and ` +
`chain ${responseBody.chainId} was not proposed. Skipping proposal...`,
`Block ${responseBody.block} for epoch ${request.epoch} and ` +
`chain ${chainId} was not proposed. Skipping proposal...`,
);
} else {
this.logger.error(
Expand All @@ -597,38 +607,52 @@ export class EboActor {
* @returns void
*/
private async onResponseProposed(event: EboEvent<"ResponseProposed">): Promise<void> {
const eventResponse = event.metadata.response;
const decodedResponse = ProtocolProvider.decodeResponse(eventResponse.response);
const actorResponse = await this.buildResponse(decodedResponse.chainId);
const proposedResponse = this.registry.getResponse(event.metadata.responseId);

if (this.equalResponses(actorResponse, decodedResponse)) {
const request = this.getActorRequest();

if (!proposedResponse) {
throw new ResponseNotFound(event.metadata.responseId);
}

const actorResponse = {
prophetData: { requestId: request.id },
decodedData: {
response: await this.buildResponseBody(request.chainId),
},
};

if (this.equalResponses(actorResponse, proposedResponse)) {
this.logger.info(`Response ${event.metadata.responseId} was validated. Skipping...`);
return;
}

const request = this.getActorRequest();
const disputer = this.protocolProvider.getAccountAddress();
const dispute: Dispute["prophetData"] = {
disputer: disputer,
proposer: eventResponse.proposer,
proposer: proposedResponse.prophetData.proposer,
responseId: Address.normalize(event.metadata.responseId) as ResponseId,
requestId: request.id,
};

await this.protocolProvider.disputeResponse(request.prophetData, eventResponse, dispute);
await this.protocolProvider.disputeResponse(
request.prophetData,
proposedResponse.prophetData,
dispute,
);
}

/**
* Check for deep equality between two responses
*
* @param a response
* @param b response
* @param a {@link EqualResponseParameters} response a
* @param b {@link EqualResponseParameters} response b
*
* @returns true if all attributes on `a` are equal to attributes on `b`, false otherwise
*/
private equalResponses(a: ResponseBody, b: ResponseBody) {
if (a.block != b.block) return false;
if (a.chainId != b.chainId) return false;
if (a.epoch != b.epoch) return false;
private equalResponses(a: EqualResponseParameters, b: EqualResponseParameters) {
if (a.prophetData.requestId != b.prophetData.requestId) return false;
if (a.decodedData.response.block != b.decodedData.response.block) return false;

return true;
}
Expand Down Expand Up @@ -675,7 +699,7 @@ export class EboActor {

if (!proposedResponse) throw new InvalidActorState();

const isValidDispute = await this.isValidDispute(proposedResponse);
const isValidDispute = await this.isValidDispute(request, proposedResponse);

if (isValidDispute) await this.pledgeFor(request, dispute);
else await this.pledgeAgainst(request, dispute);
Expand All @@ -685,18 +709,21 @@ export class EboActor {
* Check if a dispute is valid, comparing the already submitted and disputed proposal with
* the response this actor would propose.
*
* @param request the request of the proposed response
* @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.decodedData.response.chainId,
);
private async isValidDispute(request: Request, proposedResponse: Response) {
const actorResponse = {
prophetData: {
requestId: request.id,
},
decodedData: {
response: await this.buildResponseBody(request.chainId),
},
};

const equalResponses = this.equalResponses(
actorResponse,
proposedResponse.decodedData.response,
);
const equalResponses = this.equalResponses(actorResponse, proposedResponse);

return !equalResponses;
}
Expand Down
2 changes: 0 additions & 2 deletions packages/automated-dispute/src/types/prophet.ts
Original file line number Diff line number Diff line change
Expand Up @@ -53,9 +53,7 @@ export interface Request {
}

export type ResponseBody = {
chainId: Caip2ChainId;
block: bigint;
epoch: bigint;
};

export interface Response {
Expand Down
2 changes: 0 additions & 2 deletions packages/automated-dispute/tests/mocks/eboActor.mocks.ts
Original file line number Diff line number Diff line change
Expand Up @@ -80,9 +80,7 @@ export function buildEboActor(request: Request, logger: ILogger) {
*/
export function buildResponse(request: Request, attributes: Partial<Response> = {}): Response {
const responseBody: ResponseBody = {
chainId: request.chainId,
block: 1n,
epoch: request.epoch,
};

const baseResponse: Response = {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -213,9 +213,7 @@ describe("ProtocolProvider", () => {

describe("encodeResponse", () => {
const response: Response["decodedData"]["response"] = {
chainId: "eip155:1",
block: 1n,
epoch: 1n,
};

it("generates a hex string with the response encoded", () => {
Expand Down

0 comments on commit 86b48ec

Please sign in to comment.