Skip to content

Commit

Permalink
fix: remove unnecessary backoff (#1441)
Browse files Browse the repository at this point in the history
  • Loading branch information
PolyProgrammist authored Dec 18, 2024
1 parent 9fac616 commit c49fd67
Show file tree
Hide file tree
Showing 4 changed files with 59 additions and 75 deletions.
37 changes: 22 additions & 15 deletions packages/providers/src/fetch_json.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,22 +4,26 @@ import unfetch from 'isomorphic-unfetch';

const BACKOFF_MULTIPLIER = 1.5;
const RETRY_NUMBER = 10;
const RETRY_DELAY = 0;

const retryConfig = {
numOfAttempts: RETRY_NUMBER,
timeMultiple: BACKOFF_MULTIPLIER,
retry: (e: ProviderError) => {
if ([503, 500, 408].includes(e.cause)) {
return true;
}
export function retryConfig(numOfAttempts=RETRY_NUMBER, timeMultiple=BACKOFF_MULTIPLIER, startingDelay=RETRY_DELAY) {
return {
numOfAttempts: numOfAttempts,
timeMultiple: timeMultiple,
startingDelay: startingDelay,
retry: (e: ProviderError) => {
if ([503, 500, 408].includes(e.cause)) {
return true;
}

if (e.toString().includes('FetchError') || e.toString().includes('Failed to fetch')) {
return true;
}
if (e.toString().includes('FetchError') || e.toString().includes('Failed to fetch')) {
return true;
}

return false;
}
};
return false;
}
};
}

export interface ConnectionInfo {
url: string;
Expand All @@ -30,6 +34,9 @@ export class ProviderError extends Error {
cause: number;
constructor(message: string, options: any) {
super(message, options);
if (options.cause) {
this.cause = options.cause;
}
}
}

Expand All @@ -47,7 +54,7 @@ interface JsonRpcRequest {
* @param headers HTTP headers to include with the request
* @returns Promise<any> }arsed JSON response from the HTTP request.
*/
export async function fetchJsonRpc(url: string, json: JsonRpcRequest, headers: object): Promise<any> {
export async function fetchJsonRpc(url: string, json: JsonRpcRequest, headers: object, retryConfig: object): Promise<any> {
const response = await backOff(async () => {
const res = await unfetch(url, {
method: 'POST',
Expand All @@ -58,7 +65,7 @@ export async function fetchJsonRpc(url: string, json: JsonRpcRequest, headers: o
const { ok, status } = res;

if (status === 500) {
throw new ProviderError(`Internal server error`, { cause: status });
throw new ProviderError('Internal server error', { cause: status });
} else if (status === 408) {
throw new ProviderError('Timeout error', { cause: status });
} else if (status === 400) {
Expand Down
77 changes: 27 additions & 50 deletions packages/providers/src/json-rpc-provider.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,6 @@ import {
baseEncode,
formatError,
getErrorTypeFromErrorMessage,
Logger,
parseRpcError,
ServerError,
} from '@near-js/utils';
Expand Down Expand Up @@ -39,9 +38,8 @@ import {
SignedTransaction,
} from '@near-js/transactions';

import { exponentialBackoff } from './exponential-backoff';
import { Provider } from './provider';
import { ConnectionInfo, fetchJsonRpc } from './fetch_json';
import { ConnectionInfo, fetchJsonRpc, retryConfig } from './fetch_json';
import { TxExecutionStatus } from '@near-js/types';

/** @hidden */
Expand Down Expand Up @@ -378,56 +376,35 @@ export class JsonRpcProvider extends Provider {
* @param params Parameters to the method
*/
async sendJsonRpc<T>(method: string, params: object): Promise<T> {
const response = await exponentialBackoff(this.options.wait, this.options.retries, this.options.backoff, async () => {
try {
const request = {
method,
params,
id: (_nextId++),
jsonrpc: '2.0'
};
const response = await fetchJsonRpc(this.connection.url, request, this.connection.headers);
if (response.error) {
if (typeof response.error.data === 'object') {
if (typeof response.error.data.error_message === 'string' && typeof response.error.data.error_type === 'string') {
// if error data has error_message and error_type properties, we consider that node returned an error in the old format
throw new TypedError(response.error.data.error_message, response.error.data.error_type);
}

throw parseRpcError(response.error.data);
} else {
const errorMessage = `[${response.error.code}] ${response.error.message}: ${response.error.data}`;
// NOTE: All this hackery is happening because structured errors not implemented
// TODO: Fix when https://github.com/nearprotocol/nearcore/issues/1839 gets resolved
if (response.error.data === 'Timeout' || errorMessage.includes('Timeout error')
|| errorMessage.includes('query has timed out')) {
throw new TypedError(errorMessage, 'TimeoutError');
}

const errorType = getErrorTypeFromErrorMessage(response.error.data, '');
if (errorType) {
throw new TypedError(formatError(errorType, params), errorType);
}
throw new TypedError(errorMessage, response.error.name);
}
} else if (typeof response.result?.error === 'string') {
const errorType = getErrorTypeFromErrorMessage(response.result.error, '');

if (errorType) {
throw new ServerError(formatError(errorType, params), errorType);
}
}
// Success when response.error is not exist
return response;
} catch (error) {
if (error.type === 'TimeoutError') {
Logger.warn(`Retrying request to ${method} as it has timed out`, params);
return null;
const request = {
method,
params,
id: (_nextId++),
jsonrpc: '2.0'
};
const response = await fetchJsonRpc(this.connection.url, request, this.connection.headers, retryConfig(this.options.retries, this.options.backoff, this.options.wait));
if (response.error) {
if (typeof response.error.data === 'object') {
if (typeof response.error.data.error_message === 'string' && typeof response.error.data.error_type === 'string') {
// if error data has error_message and error_type properties, we consider that node returned an error in the old format
throw new TypedError(response.error.data.error_message, response.error.data.error_type);
}
throw parseRpcError(response.error.data);
} else {
const errorMessage = `[${response.error.code}] ${response.error.message}: ${response.error.data}`;

throw error;
const errorType = getErrorTypeFromErrorMessage(response.error.data, '');
if (errorType) {
throw new TypedError(formatError(errorType, params), errorType);
}
throw new TypedError(errorMessage, response.error.name);
}
});
} else if (typeof response.result?.error === 'string') {
const errorType = getErrorTypeFromErrorMessage(response.result.error, '');
if (errorType) {
throw new ServerError(formatError(errorType, params), errorType);
}
}
const { result } = response;
// From jsonrpc spec:
// result
Expand Down
6 changes: 3 additions & 3 deletions packages/providers/test/fetch_json.test.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { describe, expect, test } from '@jest/globals';
import { fetchJsonRpc } from '../src/fetch_json';
import { fetchJsonRpc, retryConfig } from '../src/fetch_json';

describe('fetchJson', () => {
test('string parameter in fetchJson', async () => {
Expand All @@ -11,7 +11,7 @@ describe('fetchJson', () => {
params: []
};
// @ts-expect-error test input
const result = await fetchJsonRpc(RPC_URL, statusRequest, undefined);
const result = await fetchJsonRpc(RPC_URL, statusRequest, undefined, retryConfig());
expect(result.result.chain_id).toBe('testnet');
});
test('object parameter in fetchJson', async () => {
Expand All @@ -23,7 +23,7 @@ describe('fetchJson', () => {
params: []
};
// @ts-expect-error test input
const result = await fetchJsonRpc(connection.url, statusRequest, undefined);
const result = await fetchJsonRpc(connection.url, statusRequest, undefined, retryConfig());
expect(result.result.chain_id).toBe('testnet');
});
});
14 changes: 7 additions & 7 deletions packages/providers/test/fetch_json_error.test.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { describe, expect, test, jest, afterEach } from '@jest/globals';
import { fetchJsonRpc } from '../src/fetch_json';
import { fetchJsonRpc, retryConfig } from '../src/fetch_json';
import unfetch from 'isomorphic-unfetch';
import { ProviderError } from '../src/fetch_json';

Expand Down Expand Up @@ -27,7 +27,7 @@ describe('fetchJsonError', () => {
});

// @ts-expect-error test input
await expect(fetchJsonRpc(RPC_URL, statusRequest, undefined))
await expect(fetchJsonRpc(RPC_URL, statusRequest, undefined, retryConfig()))
.rejects
.toThrowError(new ProviderError('Internal server error', { cause: 500 }));
});
Expand All @@ -39,7 +39,7 @@ describe('fetchJsonError', () => {
json: {},
});
// @ts-expect-error test input
await expect(fetchJsonRpc(RPC_URL, statusRequest, undefined))
await expect(fetchJsonRpc(RPC_URL, statusRequest, undefined, retryConfig()))
.rejects
.toThrowError(new ProviderError('Timeout error', { cause: 408 }));
});
Expand All @@ -52,8 +52,8 @@ describe('fetchJsonError', () => {
text: '',
json: {},
});
// @ts-expect-error test input
await expect(fetchJsonRpc(RPC_URL, statusRequest, undefined))
// @ts-expect-error test input
await expect(fetchJsonRpc(RPC_URL, statusRequest, undefined, retryConfig()))
.rejects
.toThrowError(new ProviderError('Request validation error', { cause: 400 }));
});
Expand All @@ -66,8 +66,8 @@ describe('fetchJsonError', () => {
json: {},
});

// @ts-expect-error test input
await expect(fetchJsonRpc(RPC_URL, statusRequest, undefined))
// @ts-expect-error test input
await expect(fetchJsonRpc(RPC_URL, statusRequest, undefined, retryConfig()))
.rejects
.toThrowError(new ProviderError(`${RPC_URL} unavailable`, { cause: 503 }));
});
Expand Down

0 comments on commit c49fd67

Please sign in to comment.