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

feat: error classification #50

Merged
merged 8 commits into from
Jan 7, 2025
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
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
export class DataDecodeException extends Error {
import { NonRetriableError } from "@grants-stack-indexer/shared";

export class DataDecodeException extends NonRetriableError {
constructor(message: string) {
super(message);
this.name = "DataDecodeException";
}
}
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
export class InvalidArgumentException extends Error {
import { NonRetriableError } from "@grants-stack-indexer/shared";

export class InvalidArgumentException extends NonRetriableError {
constructor(message: string) {
super(message);
this.name = "InvalidArgumentException";
}
}
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
export class MulticallNotFound extends Error {
import { NonRetriableError } from "@grants-stack-indexer/shared";

export class MulticallNotFound extends NonRetriableError {
constructor() {
super("Multicall contract address not found");
}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
export class RpcUrlsEmpty extends Error {
import { NonRetriableError } from "@grants-stack-indexer/shared";

export class RpcUrlsEmpty extends NonRetriableError {
constructor() {
super("RPC URLs array cannot be empty");
this.name = "RpcUrlsEmpty";
}
}
149 changes: 112 additions & 37 deletions packages/chain-providers/src/providers/evmProvider.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,10 +22,12 @@ import {
HttpTransport,
MulticallParameters,
MulticallReturnType,
RpcError,
TimeoutError,
toHex,
} from "viem";

import { ILogger } from "@grants-stack-indexer/shared";
import { ILogger, NonRetriableError, RetriableError } from "@grants-stack-indexer/shared";

import {
AbiWithConstructor,
Expand Down Expand Up @@ -67,7 +69,11 @@ export class EvmProvider {
}

async getTransaction(hash: Hex): Promise<GetTransactionReturnType> {
return this.client.getTransaction({ hash });
try {
return await this.client.getTransaction({ hash });
} catch (e) {
throw this.wrapError(e, "getTransaction", "Failed to get transaction", { hash });
}
}

/**
Expand All @@ -76,35 +82,57 @@ export class EvmProvider {
* @returns {Promise<bigint>} A Promise that resolves to the balance of the address.
*/
async getBalance(address: Address): Promise<bigint> {
return this.client.getBalance({ address });
try {
return await this.client.getBalance({ address });
} catch (e) {
throw this.wrapError(e, "getBalance", "Failed to get balance", { address });
}
}

/**
* Retrieves the current block number.
* @returns {Promise<bigint>} A Promise that resolves to the latest block number.
*/
async getBlockNumber(): Promise<bigint> {
return this.client.getBlockNumber();
try {
return await this.client.getBlockNumber();
} catch (e) {
throw this.wrapError(e, "getBlockNumber", "Failed to get block number");
}
}

/**
* Retrieves the current block number.
* @returns {Promise<GetBlockReturnType>} Latest block number.
*/
async getBlockByNumber(blockNumber: bigint): Promise<GetBlockReturnType> {
return this.client.getBlock({ blockNumber });
try {
return await this.client.getBlock({ blockNumber });
} catch (e) {
throw this.wrapError(e, "getBlockByNumber", "Failed to get block", {
blockNumber: blockNumber.toString(),
});
}
}

/**
* Retrieves the current estimated gas price on the chain.
* @returns {Promise<bigint>} A Promise that resolves to the current gas price.
*/
async getGasPrice(): Promise<bigint> {
return this.client.getGasPrice();
try {
return await this.client.getGasPrice();
} catch (e) {
throw this.wrapError(e, "getGasPrice", "Failed to get gas price");
}
}

async estimateGas(args: EstimateGasParameters<typeof this.chain>): Promise<bigint> {
return this.client.estimateGas(args);
try {
return await this.client.estimateGas(args);
} catch (e) {
throw this.wrapError(e, "estimateGas", "Failed to estimate gas", args);
}
}

/**
Expand All @@ -121,10 +149,14 @@ export class EvmProvider {
);
}

return this.client.getStorageAt({
address,
slot: typeof slot === "string" ? slot : toHex(slot),
});
try {
return await this.client.getStorageAt({
address,
slot: typeof slot === "string" ? slot : toHex(slot),
});
} catch (e) {
throw this.wrapError(e, "getStorageAt", "Failed to get storage", { address, slot });
}
}

/**
Expand All @@ -137,27 +169,28 @@ export class EvmProvider {
*/
async readContract<
TAbi extends Abi,
TFunctionName extends ContractFunctionName<TAbi, "pure" | "view"> = ContractFunctionName<
TAbi,
"pure" | "view"
>,
TArgs extends ContractFunctionArgs<
TAbi,
"pure" | "view",
TFunctionName
> = ContractFunctionArgs<TAbi, "pure" | "view", TFunctionName>,
TFunctionName extends ContractFunctionName<TAbi, "pure" | "view">,
TArgs extends ContractFunctionArgs<TAbi, "pure" | "view", TFunctionName>,
>(
contractAddress: Address,
abi: TAbi,
functionName: TFunctionName,
args?: TArgs,
): Promise<ContractFunctionReturnType<TAbi, "pure" | "view", TFunctionName, TArgs>> {
return this.client.readContract({
address: contractAddress,
abi,
functionName,
args,
});
try {
return await this.client.readContract({
address: contractAddress,
abi,
functionName,
args,
});
} catch (e) {
throw this.wrapError(e, "readContract", "Failed to read contract", {
contractAddress,
functionName,
args,
});
}
}

/**
Expand All @@ -177,19 +210,27 @@ export class EvmProvider {
): Promise<DecodeAbiParametersReturnType<ReturnType>> {
const deploymentData = args ? encodeDeployData({ abi, bytecode, args }) : bytecode;

const { data: returnData } = await this.client.call({
data: deploymentData,
});
try {
const { data: returnData } = await this.client.call({
data: deploymentData,
});

if (!returnData) {
throw new DataDecodeException("No return data");
}
if (!returnData) {
throw new DataDecodeException("No return data");
}

try {
const decoded = decodeAbiParameters(constructorReturnParams, returnData);
return decoded;
try {
return decodeAbiParameters(constructorReturnParams, returnData);
} catch (e) {
throw new DataDecodeException(
"Error decoding return data with given AbiParameters",
);
}
} catch (e) {
throw new DataDecodeException("Error decoding return data with given AbiParameters");
if (e instanceof DataDecodeException) {
throw e;
}
throw this.wrapError(e, "batchRequest", "Failed to execute batch request", { args });
}
}

Expand All @@ -208,6 +249,40 @@ export class EvmProvider {
): Promise<MulticallReturnType<contracts, allowFailure>> {
if (!this.chain?.contracts?.multicall3?.address) throw new MulticallNotFound();

return this.client.multicall<contracts, allowFailure>(args);
try {
return await this.client.multicall<contracts, allowFailure>(args);
} catch (e) {
throw this.wrapError(e, "multicall", "Failed to execute multicall", { args });
}
}

private wrapError(
error: unknown,
methodName: Exclude<
keyof typeof EvmProvider.prototype,
"constructor" | "wrapError" | "logger" | "chain" | "client"
>,
errorMessage: string,
additionalData?: Record<string, unknown>,
): RetriableError | NonRetriableError {
const err = error as Error;
if (err instanceof TimeoutError || err instanceof RpcError) {
return new RetriableError(errorMessage, {
className: EvmProvider.name,
methodName,
chainId: this.chain?.id?.toString(),
additionalData,
});
}
return new NonRetriableError(
err.message,
{
className: "EvmProvider",
methodName,
chainId: this.chain?.id?.toString(),
additionalData,
},
err,
);
}
}
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
export class InvalidChangeset extends Error {
import { NonRetriableError } from "@grants-stack-indexer/shared";

export class InvalidChangeset extends NonRetriableError {
constructor(invalidTypes: string[]) {
super(`Invalid changeset types: ${invalidTypes.join(", ")}`);
}
Expand Down
10 changes: 8 additions & 2 deletions packages/data-flow/src/exceptions/invalidEvent.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,12 @@
import { AnyEvent, ContractName, ProcessorEvent, stringify } from "@grants-stack-indexer/shared";
import {
AnyEvent,
ContractName,
NonRetriableError,
ProcessorEvent,
stringify,
} from "@grants-stack-indexer/shared";

export class InvalidEvent extends Error {
export class InvalidEvent extends NonRetriableError {
constructor(event: ProcessorEvent<ContractName, AnyEvent>) {
super(`Event couldn't be processed: ${stringify(event)}`);

Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
export class IndexerClientError extends Error {
constructor(message: string) {
super(`Indexer client error - ${message}`);
this.name = "IndexerClientError";
import { ErrorContext, NonRetriableError } from "@grants-stack-indexer/shared";

export class IndexerClientError extends NonRetriableError {
constructor(message: string, context?: ErrorContext, error?: Error) {
super(`Indexer client error - ${message}`, context, error);
}
}
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
export class InvalidIndexerResponse extends Error {
import { NonRetriableError } from "@grants-stack-indexer/shared";

export class InvalidIndexerResponse extends NonRetriableError {
constructor(response: string) {
super(`Indexer response is invalid - ${response}`);
this.name = "InvalidIndexerResponse";
}
}
9 changes: 8 additions & 1 deletion packages/indexer-client/src/providers/envioIndexerClient.ts
Original file line number Diff line number Diff line change
Expand Up @@ -70,7 +70,14 @@ export class EnvioIndexerClient implements IIndexerClient {
if (error instanceof InvalidIndexerResponse) {
throw error;
}
throw new IndexerClientError(stringify(error, Object.getOwnPropertyNames(error)));
throw new IndexerClientError(
stringify(error, Object.getOwnPropertyNames(error)),
{
className: EnvioIndexerClient.name,
methodName: "getEventsAfterBlockNumberAndLogIndex",
},
error as Error,
);
}
}

Expand Down
4 changes: 3 additions & 1 deletion packages/metadata/src/exceptions/emptyGateways.exception.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
export class EmptyGatewaysUrlsException extends Error {
import { NonRetriableError } from "@grants-stack-indexer/shared";

export class EmptyGatewaysUrlsException extends NonRetriableError {
constructor() {
super("Gateways array cannot be empty");
}
Expand Down
5 changes: 3 additions & 2 deletions packages/metadata/src/exceptions/invalidCid.exception.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
export class InvalidCidException extends Error {
import { NonRetriableError } from "@grants-stack-indexer/shared";

export class InvalidCidException extends NonRetriableError {
constructor(cid: string) {
super(`Invalid CID: ${cid}`);
this.name = "InvalidCidException";
}
}
4 changes: 3 additions & 1 deletion packages/metadata/src/exceptions/invalidContent.exception.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
export class InvalidContentException extends Error {
import { NonRetriableError } from "@grants-stack-indexer/shared";

export class InvalidContentException extends NonRetriableError {
constructor(message: string) {
super(message);
}
Expand Down
1 change: 0 additions & 1 deletion packages/pricing/src/exceptions/index.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
export * from "./network.exception.js";
export * from "./unsupportedChain.exception.js";
export * from "./unknownPricing.exception.js";
export * from "./unsupportedToken.exception.js";
Expand Down
8 changes: 0 additions & 8 deletions packages/pricing/src/exceptions/network.exception.ts

This file was deleted.

9 changes: 5 additions & 4 deletions packages/pricing/src/exceptions/unknownPricing.exception.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
export class UnknownPricingException extends Error {
constructor(message: string, stack?: string) {
super(message);
this.stack = stack;
import { ErrorContext, RetriableError } from "@grants-stack-indexer/shared";

export class UnknownPricingException extends RetriableError {
constructor(message: string, context: ErrorContext) {
super(message, context);
}
}
8 changes: 4 additions & 4 deletions packages/pricing/src/exceptions/unsupportedToken.exception.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { TokenCode } from "@grants-stack-indexer/shared";
import { ErrorContext, NonRetriableError, TokenCode } from "@grants-stack-indexer/shared";

export class UnsupportedToken extends Error {
constructor(tokenCode: TokenCode) {
super(`Unsupported token: ${tokenCode}`);
export class UnsupportedToken extends NonRetriableError {
constructor(tokenCode: TokenCode, context: ErrorContext) {
super(`Unsupported token: ${tokenCode}`, context);
}
}
Loading
Loading