Skip to content

Commit

Permalink
feat: error classification (#50)
Browse files Browse the repository at this point in the history
# 🤖 Linear

Closes GIT-221

## Description
We make all custom exception on the project to extend Retriable or
NonRetriable base errors classes
Also, we create some new exceptions where needed

## Checklist before requesting a review

-   [x] I have conducted a self-review of my code.
-   [x] I have conducted a QA.
-   [ ] If it is a core feature, I have included comprehensive tests.
  • Loading branch information
0xnigir1 authored Jan 7, 2025
1 parent 36f8638 commit 042191f
Show file tree
Hide file tree
Showing 44 changed files with 809 additions and 268 deletions.
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

0 comments on commit 042191f

Please sign in to comment.