From a920b3803b0c11f04ae2a92b2bda180978e485fe Mon Sep 17 00:00:00 2001 From: Rafael Cardenas Date: Mon, 26 Aug 2024 18:12:27 -0600 Subject: [PATCH] fix: demote server connection errors and contract clarity errors to be non-retryable --- src/token-processor/images/image-cache.ts | 2 +- .../queue/job/process-token-job.ts | 4 ++-- .../stacks-node/stacks-node-rpc-client.ts | 19 +++++++------------ src/token-processor/util/errors.ts | 4 ++-- src/token-processor/util/metadata-helpers.ts | 10 +--------- tests/token-queue/metadata-helpers.test.ts | 2 +- .../stacks-node-rpc-client.test.ts | 11 ++++------- 7 files changed, 18 insertions(+), 34 deletions(-) diff --git a/src/token-processor/images/image-cache.ts b/src/token-processor/images/image-cache.ts index a0187424..1be8ed3a 100644 --- a/src/token-processor/images/image-cache.ts +++ b/src/token-processor/images/image-cache.ts @@ -158,7 +158,7 @@ export async function processImageCache( throw new ImageSizeExceededError(`ImageCache image too large: ${imgUrl}`); } if ((typeError.cause as any).toString().includes('ECONNRESET')) { - throw new RetryableJobError(`ImageCache server connection interrupted`, typeError); + throw new ImageHttpError(`ImageCache server connection interrupted`, typeError); } } throw error; diff --git a/src/token-processor/queue/job/process-token-job.ts b/src/token-processor/queue/job/process-token-job.ts index d9c1f30b..7c919d42 100644 --- a/src/token-processor/queue/job/process-token-job.ts +++ b/src/token-processor/queue/job/process-token-job.ts @@ -9,7 +9,7 @@ import { DbTokenType, } from '../../../pg/types'; import { StacksNodeRpcClient } from '../../stacks-node/stacks-node-rpc-client'; -import { StacksNodeClarityError, TooManyRequestsHttpError } from '../../util/errors'; +import { SmartContractClarityError, TooManyRequestsHttpError } from '../../util/errors'; import { fetchAllMetadataLocalesFromBaseUri, getFetchableDecentralizedStorageUrl, @@ -108,7 +108,7 @@ export class ProcessTokenJob extends Job { } catch (error) { // We'll treat Clarity errors here as if the supply was `undefined` to accommodate ALEX's // wrapped tokens which return an error in `get-total-supply`. - if (!(error instanceof StacksNodeClarityError)) { + if (!(error instanceof SmartContractClarityError)) { throw error; } } diff --git a/src/token-processor/stacks-node/stacks-node-rpc-client.ts b/src/token-processor/stacks-node/stacks-node-rpc-client.ts index 16460842..871247be 100644 --- a/src/token-processor/stacks-node/stacks-node-rpc-client.ts +++ b/src/token-processor/stacks-node/stacks-node-rpc-client.ts @@ -9,7 +9,7 @@ import { request, errors } from 'undici'; import { ENV } from '../../env'; import { RetryableJobError } from '../queue/errors'; import { - StacksNodeClarityError, + SmartContractClarityError, StacksNodeJsonParseError, StacksNodeHttpError, } from '../util/errors'; @@ -72,7 +72,7 @@ export class StacksNodeRpcClient { try { return BigInt(uintVal.value.toString()); } catch (error) { - throw new RetryableJobError(`Invalid uint value '${uintVal.value}'`); + throw new SmartContractClarityError(`Invalid uint value '${uintVal.value}'`); } } @@ -131,23 +131,18 @@ export class StacksNodeRpcClient { functionName: string, functionArgs: ClarityValue[] ): Promise { - let result: ReadOnlyContractCallResponse; - try { - result = await this.sendReadOnlyContractCall(functionName, functionArgs); - } catch (error) { - throw new RetryableJobError(`Error making read-only contract call: ${error}`, error); - } + const result = await this.sendReadOnlyContractCall(functionName, functionArgs); if (!result.okay) { if (result.cause.startsWith('Runtime')) { throw new RetryableJobError( `Runtime error while calling read-only function ${functionName}` ); } else if (result.cause.includes('NoSuchContract')) { - throw new RetryableJobError( + throw new SmartContractClarityError( `Contract not available yet when calling read-only function ${functionName}` ); } - throw new StacksNodeClarityError(`Read-only error ${functionName}: ${result.cause}`); + throw new SmartContractClarityError(`Read-only error ${functionName}: ${result.cause}`); } return decodeClarityValue(result.result); } @@ -168,7 +163,7 @@ export class StacksNodeRpcClient { if (unwrappedClarityValue.type_id === ClarityTypeID.UInt) { return unwrappedClarityValue; } - throw new StacksNodeClarityError( + throw new SmartContractClarityError( `Unexpected Clarity type '${unwrappedClarityValue.type_id}' while unwrapping uint` ); } @@ -183,7 +178,7 @@ export class StacksNodeRpcClient { } else if (unwrappedClarityValue.type_id === ClarityTypeID.OptionalNone) { return undefined; } - throw new StacksNodeClarityError( + throw new SmartContractClarityError( `Unexpected Clarity type '${unwrappedClarityValue.type_id}' while unwrapping string` ); } diff --git a/src/token-processor/util/errors.ts b/src/token-processor/util/errors.ts index 92b1f82b..07119aa5 100644 --- a/src/token-processor/util/errors.ts +++ b/src/token-processor/util/errors.ts @@ -44,7 +44,7 @@ export class MetadataParseError extends UserError { export class ImageParseError extends MetadataParseError {} -export class StacksNodeClarityError extends UserError { +export class SmartContractClarityError extends UserError { constructor(message: string) { super(); this.message = message; @@ -111,7 +111,7 @@ export function getUserErrorInvalidReason(error: UserError): DbJobInvalidReason return DbJobInvalidReason.metadataHttpError; case error instanceof ImageHttpError: return DbJobInvalidReason.imageHttpError; - case error instanceof StacksNodeClarityError: + case error instanceof SmartContractClarityError: return DbJobInvalidReason.tokenContractClarityError; default: return DbJobInvalidReason.unknown; diff --git a/src/token-processor/util/metadata-helpers.ts b/src/token-processor/util/metadata-helpers.ts index 9d12fe60..42b37870 100644 --- a/src/token-processor/util/metadata-helpers.ts +++ b/src/token-processor/util/metadata-helpers.ts @@ -97,14 +97,6 @@ export async function fetchAllMetadataLocalesFromBaseUri( break; } catch (error) { fetchImmediateRetryCount++; - if ( - error instanceof MetadataTimeoutError && - isUriFromDecentralizedStorage(error.url.toString()) - ) { - // Gateways like IPFS and Arweave commonly time out when a resource can't be found quickly. - // Try again later if this is the case. - throw new RetryableJobError(`Gateway timeout for ${error.url}`, error); - } if (error instanceof TooManyRequestsHttpError) { // 429 status codes are common when fetching metadata for thousands of tokens in the same // server. @@ -268,7 +260,7 @@ export async function fetchMetadata(httpUrl: URL): Promise { error instanceof TypeError && ((error as UndiciCauseTypeError).cause as any).toString().includes('ECONNRESET') ) { - throw new RetryableJobError(`Server connection interrupted`, error); + throw new MetadataHttpError(`Server connection interrupted`, error); } throw new MetadataHttpError(`${url}: ${error}`, error); } diff --git a/tests/token-queue/metadata-helpers.test.ts b/tests/token-queue/metadata-helpers.test.ts index e6dddd53..c512ce5d 100644 --- a/tests/token-queue/metadata-helpers.test.ts +++ b/tests/token-queue/metadata-helpers.test.ts @@ -228,6 +228,6 @@ describe('Metadata Helpers', () => { .replyWithError(Object.assign(new TypeError(), { cause: new Error('read ECONNRESET') })); setGlobalDispatcher(agent); - await expect(fetchMetadata(url)).rejects.toThrow(RetryableJobError); + await expect(fetchMetadata(url)).rejects.toThrow(MetadataHttpError); }); }); diff --git a/tests/token-queue/stacks-node-rpc-client.test.ts b/tests/token-queue/stacks-node-rpc-client.test.ts index 47d02f62..0877b670 100644 --- a/tests/token-queue/stacks-node-rpc-client.test.ts +++ b/tests/token-queue/stacks-node-rpc-client.test.ts @@ -13,6 +13,7 @@ import { StacksNodeRpcClient } from '../../src/token-processor/stacks-node/stack import { StacksNodeJsonParseError, StacksNodeHttpError, + SmartContractClarityError, } from '../../src/token-processor/util/errors'; describe('StacksNodeRpcClient', () => { @@ -69,7 +70,7 @@ describe('StacksNodeRpcClient', () => { setGlobalDispatcher(agent); await expect(client.readStringFromContract('get-token-uri', [])).rejects.toThrow( - RetryableJobError + SmartContractClarityError ); }); @@ -110,9 +111,7 @@ describe('StacksNodeRpcClient', () => { try { await client.readStringFromContract('get-token-uri', []); } catch (error) { - expect(error).toBeInstanceOf(RetryableJobError); - const err = error as RetryableJobError; - expect(err.cause).toBeInstanceOf(StacksNodeHttpError); + expect(error).toBeInstanceOf(StacksNodeHttpError); } }); @@ -131,9 +130,7 @@ describe('StacksNodeRpcClient', () => { try { await client.readStringFromContract('get-token-uri', []); } catch (error) { - expect(error).toBeInstanceOf(RetryableJobError); - const err = error as RetryableJobError; - expect(err.cause).toBeInstanceOf(StacksNodeJsonParseError); + expect(error).toBeInstanceOf(StacksNodeJsonParseError); } });