From 0369e2bdfe9cb352349b350a12b0d148abec94d9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Francisco=20L=C3=B3pez?= Date: Thu, 6 Nov 2025 17:05:09 +0100 Subject: [PATCH 1/7] implement retry logic for GraphQL requests --- .../human-protocol-sdk/src/escrow.ts | 73 ++++--- .../human-protocol-sdk/src/interfaces.ts | 19 ++ .../human-protocol-sdk/src/kvstore.ts | 21 +- .../human-protocol-sdk/src/staking.ts | 23 ++- .../human-protocol-sdk/src/transaction.ts | 59 +++--- .../human-protocol-sdk/src/utils.ts | 60 ++++++ .../human-protocol-sdk/src/worker.ts | 42 ++-- .../human-protocol-sdk/test/utils.test.ts | 195 +++++++++++++++++- 8 files changed, 401 insertions(+), 91 deletions(-) diff --git a/packages/sdk/typescript/human-protocol-sdk/src/escrow.ts b/packages/sdk/typescript/human-protocol-sdk/src/escrow.ts index cf1f946b96..93ec500127 100644 --- a/packages/sdk/typescript/human-protocol-sdk/src/escrow.ts +++ b/packages/sdk/typescript/human-protocol-sdk/src/escrow.ts @@ -10,7 +10,6 @@ import { HMToken__factory, } from '@human-protocol/core/typechain-types'; import { ContractRunner, EventLog, Overrides, Signer, ethers } from 'ethers'; -import gqlFetch from 'graphql-request'; import { BaseEthersClient } from './base'; import { ESCROW_BULK_PAYOUT_MAX_ITEMS, NETWORKS } from './constants'; import { requiresSigner } from './decorators'; @@ -62,13 +61,16 @@ import { IStatusEventFilter, IStatusEvent, ICancellationRefund, + ICancellationRefundFilter, IPayout, IEscrowWithdraw, + SubgraphRetryConfig, } from './interfaces'; import { EscrowStatus, NetworkData, TransactionLikeWithNonce } from './types'; import { getSubgraphUrl, getUnixTimestamp, + gqlFetchWithRetry, isValidJson, isValidUrl, throwError, @@ -1989,7 +1991,7 @@ export class EscrowUtils { statuses = Array.isArray(filter.status) ? filter.status : [filter.status]; statuses = statuses.map((status) => EscrowStatus[status]); } - const { escrows } = await gqlFetch<{ escrows: EscrowData[] }>( + const { escrows } = await gqlFetchWithRetry<{ escrows: EscrowData[] }>( getSubgraphUrl(networkData), GET_ESCROWS_QUERY(filter), { @@ -2004,7 +2006,8 @@ export class EscrowUtils { orderDirection: orderDirection, first: first, skip: skip, - } + }, + filter.retryConfig ); return (escrows || []).map((e) => mapEscrow(e, networkData.chainId)); } @@ -2074,7 +2077,8 @@ export class EscrowUtils { */ public static async getEscrow( chainId: ChainId, - escrowAddress: string + escrowAddress: string, + retryConfig?: SubgraphRetryConfig ): Promise { const networkData = NETWORKS[chainId]; @@ -2086,10 +2090,11 @@ export class EscrowUtils { throw ErrorInvalidAddress; } - const { escrow } = await gqlFetch<{ escrow: EscrowData | null }>( + const { escrow } = await gqlFetchWithRetry<{ escrow: EscrowData | null }>( getSubgraphUrl(networkData), GET_ESCROW_BY_ADDRESS_QUERY(), - { escrowAddress: escrowAddress.toLowerCase() } + { escrowAddress: escrowAddress.toLowerCase() }, + retryConfig ); if (!escrow) return null; @@ -2187,7 +2192,7 @@ export class EscrowUtils { const statusNames = effectiveStatuses.map((status) => EscrowStatus[status]); - const data = await gqlFetch<{ + const data = await gqlFetchWithRetry<{ escrowStatusEvents: StatusEvent[]; }>( getSubgraphUrl(networkData), @@ -2200,7 +2205,8 @@ export class EscrowUtils { orderDirection, first: Math.min(first, 1000), skip, - } + }, + filter.retryConfig ); if (!data || !data['escrowStatusEvents']) { @@ -2258,7 +2264,7 @@ export class EscrowUtils { const skip = filter.skip || 0; const orderDirection = filter.orderDirection || OrderDirection.DESC; - const { payouts } = await gqlFetch<{ payouts: PayoutData[] }>( + const { payouts } = await gqlFetchWithRetry<{ payouts: PayoutData[] }>( getSubgraphUrl(networkData), GET_PAYOUTS_QUERY(filter), { @@ -2269,7 +2275,8 @@ export class EscrowUtils { first: Math.min(first, 1000), skip, orderDirection, - } + }, + filter.retryConfig ); if (!payouts) { return []; @@ -2332,16 +2339,9 @@ export class EscrowUtils { * console.log(cancellationRefunds); * ``` */ - public static async getCancellationRefunds(filter: { - chainId: ChainId; - escrowAddress?: string; - receiver?: string; - from?: Date; - to?: Date; - first?: number; - skip?: number; - orderDirection?: OrderDirection; - }): Promise { + public static async getCancellationRefunds( + filter: ICancellationRefundFilter + ): Promise { const networkData = NETWORKS[filter.chainId]; if (!networkData) throw ErrorUnsupportedChainID; if (filter.escrowAddress && !ethers.isAddress(filter.escrowAddress)) { @@ -2356,17 +2356,22 @@ export class EscrowUtils { const skip = filter.skip || 0; const orderDirection = filter.orderDirection || OrderDirection.DESC; - const { cancellationRefundEvents } = await gqlFetch<{ + const { cancellationRefundEvents } = await gqlFetchWithRetry<{ cancellationRefundEvents: CancellationRefundData[]; - }>(getSubgraphUrl(networkData), GET_CANCELLATION_REFUNDS_QUERY(filter), { - escrowAddress: filter.escrowAddress?.toLowerCase(), - receiver: filter.receiver?.toLowerCase(), - from: filter.from ? getUnixTimestamp(filter.from) : undefined, - to: filter.to ? getUnixTimestamp(filter.to) : undefined, - first, - skip, - orderDirection, - }); + }>( + getSubgraphUrl(networkData), + GET_CANCELLATION_REFUNDS_QUERY(filter), + { + escrowAddress: filter.escrowAddress?.toLowerCase(), + receiver: filter.receiver?.toLowerCase(), + from: filter.from ? getUnixTimestamp(filter.from) : undefined, + to: filter.to ? getUnixTimestamp(filter.to) : undefined, + first, + skip, + orderDirection, + }, + filter.retryConfig + ); if (!cancellationRefundEvents || cancellationRefundEvents.length === 0) { return []; @@ -2430,7 +2435,8 @@ export class EscrowUtils { */ public static async getCancellationRefund( chainId: ChainId, - escrowAddress: string + escrowAddress: string, + retryConfig?: SubgraphRetryConfig ): Promise { const networkData = NETWORKS[chainId]; if (!networkData) throw ErrorUnsupportedChainID; @@ -2439,12 +2445,13 @@ export class EscrowUtils { throw ErrorInvalidEscrowAddressProvided; } - const { cancellationRefundEvents } = await gqlFetch<{ + const { cancellationRefundEvents } = await gqlFetchWithRetry<{ cancellationRefundEvents: CancellationRefundData[]; }>( getSubgraphUrl(networkData), GET_CANCELLATION_REFUND_BY_ADDRESS_QUERY(), - { escrowAddress: escrowAddress.toLowerCase() } + { escrowAddress: escrowAddress.toLowerCase() }, + retryConfig ); if (!cancellationRefundEvents || cancellationRefundEvents.length === 0) { diff --git a/packages/sdk/typescript/human-protocol-sdk/src/interfaces.ts b/packages/sdk/typescript/human-protocol-sdk/src/interfaces.ts index c47656c690..ccb7eb7006 100644 --- a/packages/sdk/typescript/human-protocol-sdk/src/interfaces.ts +++ b/packages/sdk/typescript/human-protocol-sdk/src/interfaces.ts @@ -35,6 +35,7 @@ export interface IOperatorsFilter extends IPagination { roles?: string[]; minStakedAmount?: number; orderBy?: string; + retryConfig?: SubgraphRetryConfig; } export interface IReputationNetwork { @@ -81,6 +82,7 @@ export interface IEscrowsFilter extends IPagination { from?: Date; to?: Date; chainId: ChainId; + retryConfig?: SubgraphRetryConfig; } export interface IEscrowConfig { @@ -104,6 +106,7 @@ export interface IKeyPair { export interface IStatisticsFilter extends IPagination { from?: Date; to?: Date; + retryConfig?: SubgraphRetryConfig; } export interface IHMTHoldersParams extends IPagination { @@ -116,6 +119,7 @@ export interface IPayoutFilter extends IPagination { recipient?: string; from?: Date; to?: Date; + retryConfig?: SubgraphRetryConfig; } export interface IKVStore { @@ -158,6 +162,7 @@ export interface ITransactionsFilter extends IPagination { method?: string; escrow?: string; token?: string; + retryConfig?: SubgraphRetryConfig; } export interface IPagination { @@ -179,6 +184,7 @@ export interface IStatusEventFilter extends IPagination { from?: Date; to?: Date; launcher?: string; + retryConfig?: SubgraphRetryConfig; } export interface IWorker { @@ -192,6 +198,7 @@ export interface IWorkersFilter extends IPagination { chainId: ChainId; address?: string; orderBy?: string; + retryConfig?: SubgraphRetryConfig; } export interface IStaker { @@ -220,6 +227,7 @@ export interface IStakersFilter extends IPagination { | 'withdrawnAmount' | 'slashedAmount' | 'lastDepositTimestamp'; + retryConfig?: SubgraphRetryConfig; } export interface ICancellationRefundFilter extends IPagination { chainId: ChainId; @@ -227,6 +235,7 @@ export interface ICancellationRefundFilter extends IPagination { receiver?: string; from?: Date; to?: Date; + retryConfig?: SubgraphRetryConfig; } export interface IDailyEscrow { @@ -312,3 +321,13 @@ export interface IEscrowWithdraw { tokenAddress: string; withdrawnAmount: bigint; } + +/** + * Configuration options for subgraph requests with retry logic. + */ +export interface SubgraphRetryConfig { + /** Maximum number of retry attempts (default: 3) */ + maxRetries?: number; + /** Base delay between retries in milliseconds (default: 1000) */ + baseDelay?: number; +} diff --git a/packages/sdk/typescript/human-protocol-sdk/src/kvstore.ts b/packages/sdk/typescript/human-protocol-sdk/src/kvstore.ts index e2378ba70e..5cd18f8fb5 100644 --- a/packages/sdk/typescript/human-protocol-sdk/src/kvstore.ts +++ b/packages/sdk/typescript/human-protocol-sdk/src/kvstore.ts @@ -17,15 +17,14 @@ import { ErrorUnsupportedChainID, InvalidKeyError, } from './error'; -import gqlFetch from 'graphql-request'; import { NetworkData } from './types'; -import { getSubgraphUrl, isValidUrl } from './utils'; +import { getSubgraphUrl, gqlFetchWithRetry, isValidUrl } from './utils'; import { GET_KVSTORE_BY_ADDRESS_AND_KEY_QUERY, GET_KVSTORE_BY_ADDRESS_QUERY, } from './graphql/queries/kvstore'; import { KVStoreData } from './graphql'; -import { IKVStore } from './interfaces'; +import { IKVStore, SubgraphRetryConfig } from './interfaces'; /** * ## Introduction * @@ -380,7 +379,8 @@ export class KVStoreUtils { */ public static async getKVStoreData( chainId: ChainId, - address: string + address: string, + retryConfig?: SubgraphRetryConfig ): Promise { const networkData = NETWORKS[chainId]; @@ -392,10 +392,11 @@ export class KVStoreUtils { throw ErrorInvalidAddress; } - const { kvstores } = await gqlFetch<{ kvstores: KVStoreData[] }>( + const { kvstores } = await gqlFetchWithRetry<{ kvstores: KVStoreData[] }>( getSubgraphUrl(networkData), GET_KVSTORE_BY_ADDRESS_QUERY(), - { address: address.toLowerCase() } + { address: address.toLowerCase() }, + retryConfig ); const kvStoreData = kvstores.map((item) => ({ @@ -433,7 +434,8 @@ export class KVStoreUtils { public static async get( chainId: ChainId, address: string, - key: string + key: string, + retryConfig?: SubgraphRetryConfig ): Promise { if (key === '') throw ErrorKVStoreEmptyKey; if (!ethers.isAddress(address)) throw ErrorInvalidAddress; @@ -444,10 +446,11 @@ export class KVStoreUtils { throw ErrorUnsupportedChainID; } - const { kvstores } = await gqlFetch<{ kvstores: KVStoreData[] }>( + const { kvstores } = await gqlFetchWithRetry<{ kvstores: KVStoreData[] }>( getSubgraphUrl(networkData), GET_KVSTORE_BY_ADDRESS_AND_KEY_QUERY(), - { address: address.toLowerCase(), key } + { address: address.toLowerCase(), key }, + retryConfig ); if (!kvstores || kvstores.length === 0) { diff --git a/packages/sdk/typescript/human-protocol-sdk/src/staking.ts b/packages/sdk/typescript/human-protocol-sdk/src/staking.ts index 1fab0ed1fd..8a7d2465f6 100644 --- a/packages/sdk/typescript/human-protocol-sdk/src/staking.ts +++ b/packages/sdk/typescript/human-protocol-sdk/src/staking.ts @@ -7,7 +7,6 @@ import { Staking__factory, } from '@human-protocol/core/typechain-types'; import { ContractRunner, Overrides, ethers } from 'ethers'; -import gqlFetch from 'graphql-request'; import { BaseEthersClient } from './base'; import { NETWORKS } from './constants'; import { requiresSigner } from './decorators'; @@ -23,10 +22,15 @@ import { ErrorStakerNotFound, ErrorUnsupportedChainID, } from './error'; -import { IStaker, IStakersFilter, StakerInfo } from './interfaces'; +import { + IStaker, + IStakersFilter, + StakerInfo, + SubgraphRetryConfig, +} from './interfaces'; import { StakerData } from './graphql'; import { NetworkData } from './types'; -import { getSubgraphUrl, throwError } from './utils'; +import { getSubgraphUrl, gqlFetchWithRetry, throwError } from './utils'; import { GET_STAKER_BY_ADDRESS_QUERY, GET_STAKERS_QUERY, @@ -499,7 +503,8 @@ export class StakingUtils { */ public static async getStaker( chainId: ChainId, - stakerAddress: string + stakerAddress: string, + retryConfig?: SubgraphRetryConfig ): Promise { if (!ethers.isAddress(stakerAddress)) { throw ErrorInvalidStakerAddressProvided; @@ -510,10 +515,11 @@ export class StakingUtils { throw ErrorUnsupportedChainID; } - const { staker } = await gqlFetch<{ staker: StakerData }>( + const { staker } = await gqlFetchWithRetry<{ staker: StakerData }>( getSubgraphUrl(networkData), GET_STAKER_BY_ADDRESS_QUERY, - { id: stakerAddress.toLowerCase() } + { id: stakerAddress.toLowerCase() }, + retryConfig ); if (!staker) { @@ -540,7 +546,7 @@ export class StakingUtils { throw ErrorUnsupportedChainID; } - const { stakers } = await gqlFetch<{ stakers: StakerData[] }>( + const { stakers } = await gqlFetchWithRetry<{ stakers: StakerData[] }>( getSubgraphUrl(networkData), GET_STAKERS_QUERY(filter), { @@ -572,7 +578,8 @@ export class StakingUtils { orderDirection: orderDirection, first: first, skip: skip, - } + }, + filter.retryConfig ); if (!stakers) { return []; diff --git a/packages/sdk/typescript/human-protocol-sdk/src/transaction.ts b/packages/sdk/typescript/human-protocol-sdk/src/transaction.ts index fc8cd3e987..81124a6d83 100644 --- a/packages/sdk/typescript/human-protocol-sdk/src/transaction.ts +++ b/packages/sdk/typescript/human-protocol-sdk/src/transaction.ts @@ -1,6 +1,5 @@ /* eslint-disable @typescript-eslint/no-explicit-any */ import { ethers } from 'ethers'; -import gqlFetch from 'graphql-request'; import { NETWORKS } from './constants'; import { ChainId, OrderDirection } from './enums'; import { @@ -17,8 +16,9 @@ import { InternalTransaction, ITransaction, ITransactionsFilter, + SubgraphRetryConfig, } from './interfaces'; -import { getSubgraphUrl, getUnixTimestamp } from './utils'; +import { getSubgraphUrl, getUnixTimestamp, gqlFetchWithRetry } from './utils'; export class TransactionUtils { /** @@ -66,7 +66,8 @@ export class TransactionUtils { */ public static async getTransaction( chainId: ChainId, - hash: string + hash: string, + retryConfig?: SubgraphRetryConfig ): Promise { if (!ethers.isHexString(hash)) { throw ErrorInvalidHashProvided; @@ -77,11 +78,16 @@ export class TransactionUtils { throw ErrorUnsupportedChainID; } - const { transaction } = await gqlFetch<{ + const { transaction } = await gqlFetchWithRetry<{ transaction: TransactionData | null; - }>(getSubgraphUrl(networkData), GET_TRANSACTION_QUERY, { - hash: hash.toLowerCase(), - }); + }>( + getSubgraphUrl(networkData), + GET_TRANSACTION_QUERY, + { + hash: hash.toLowerCase(), + }, + retryConfig + ); if (!transaction) return null; return mapTransaction(transaction); @@ -179,24 +185,29 @@ export class TransactionUtils { throw ErrorUnsupportedChainID; } - const { transactions } = await gqlFetch<{ + const { transactions } = await gqlFetchWithRetry<{ transactions: TransactionData[]; - }>(getSubgraphUrl(networkData), GET_TRANSACTIONS_QUERY(filter), { - fromAddress: filter?.fromAddress, - toAddress: filter?.toAddress, - startDate: filter?.startDate - ? getUnixTimestamp(filter?.startDate) - : undefined, - endDate: filter.endDate ? getUnixTimestamp(filter.endDate) : undefined, - startBlock: filter.startBlock ? filter.startBlock : undefined, - endBlock: filter.endBlock ? filter.endBlock : undefined, - method: filter.method ? filter.method : undefined, - escrow: filter.escrow ? filter.escrow : undefined, - token: filter.token ? filter.token : undefined, - orderDirection: orderDirection, - first: first, - skip: skip, - }); + }>( + getSubgraphUrl(networkData), + GET_TRANSACTIONS_QUERY(filter), + { + fromAddress: filter?.fromAddress, + toAddress: filter?.toAddress, + startDate: filter?.startDate + ? getUnixTimestamp(filter?.startDate) + : undefined, + endDate: filter.endDate ? getUnixTimestamp(filter.endDate) : undefined, + startBlock: filter.startBlock ? filter.startBlock : undefined, + endBlock: filter.endBlock ? filter.endBlock : undefined, + method: filter.method ? filter.method : undefined, + escrow: filter.escrow ? filter.escrow : undefined, + token: filter.token ? filter.token : undefined, + orderDirection: orderDirection, + first: first, + skip: skip, + }, + filter.retryConfig + ); if (!transactions) { return []; diff --git a/packages/sdk/typescript/human-protocol-sdk/src/utils.ts b/packages/sdk/typescript/human-protocol-sdk/src/utils.ts index 975215da05..3e6f6ab507 100644 --- a/packages/sdk/typescript/human-protocol-sdk/src/utils.ts +++ b/packages/sdk/typescript/human-protocol-sdk/src/utils.ts @@ -1,5 +1,6 @@ /* eslint-disable @typescript-eslint/no-explicit-any */ import { ethers } from 'ethers'; +import gqlFetch from 'graphql-request'; import { isURL } from 'validator'; import { SUBGRAPH_API_KEY_PLACEHOLDER } from './constants'; @@ -15,6 +16,7 @@ import { WarnSubgraphApiKeyNotProvided, } from './error'; import { NetworkData } from './types'; +import { SubgraphRetryConfig } from './interfaces'; /** * **Handle and throw the error.* @@ -99,3 +101,61 @@ export const getSubgraphUrl = (networkData: NetworkData) => { export const getUnixTimestamp = (date: Date): number => { return Math.floor(date.getTime() / 1000); }; + +export const isIndexerError = (error: any): boolean => { + if (!error) return false; + + const errorMessage = + error.response?.errors?.[0]?.message || + error.message || + error.toString() || + ''; + return errorMessage.toLowerCase().includes('bad indexers'); +}; + +const sleep = (ms: number): Promise => { + return new Promise((resolve) => setTimeout(resolve, ms)); +}; + +/** + * Execute a GraphQL request with automatic retry logic for bad indexer errors. + * Only retries if config is provided. + */ +export const gqlFetchWithRetry = async ( + url: string, + query: any, + variables?: any, + config?: SubgraphRetryConfig +): Promise => { + if (!config) { + return await gqlFetch(url, query, variables); + } + + const maxRetries = config.maxRetries ?? 3; + const baseDelay = config.baseDelay ?? 1000; + + let lastError: any; + + for (let attempt = 0; attempt <= maxRetries; attempt++) { + try { + const result = await gqlFetch(url, query, variables); + return result; + } catch (error) { + lastError = error; + + if (attempt === maxRetries) { + throw error; + } + + if (!isIndexerError(error)) { + throw error; + } + + const delay = baseDelay * Math.pow(2, attempt); + + await sleep(delay); + } + } + + throw lastError; +}; diff --git a/packages/sdk/typescript/human-protocol-sdk/src/worker.ts b/packages/sdk/typescript/human-protocol-sdk/src/worker.ts index 60fa11ed5f..89b91320f6 100644 --- a/packages/sdk/typescript/human-protocol-sdk/src/worker.ts +++ b/packages/sdk/typescript/human-protocol-sdk/src/worker.ts @@ -1,12 +1,11 @@ import { ethers } from 'ethers'; -import gqlFetch from 'graphql-request'; import { NETWORKS } from './constants'; import { ChainId, OrderDirection } from './enums'; import { ErrorInvalidAddress, ErrorUnsupportedChainID } from './error'; import { WorkerData } from './graphql'; import { GET_WORKER_QUERY, GET_WORKERS_QUERY } from './graphql/queries/worker'; -import { IWorker, IWorkersFilter } from './interfaces'; -import { getSubgraphUrl } from './utils'; +import { IWorker, IWorkersFilter, SubgraphRetryConfig } from './interfaces'; +import { getSubgraphUrl, gqlFetchWithRetry } from './utils'; export class WorkerUtils { /** @@ -26,7 +25,8 @@ export class WorkerUtils { */ public static async getWorker( chainId: ChainId, - address: string + address: string, + retryConfig?: SubgraphRetryConfig ): Promise { const networkData = NETWORKS[chainId]; @@ -37,11 +37,16 @@ export class WorkerUtils { throw ErrorInvalidAddress; } - const { worker } = await gqlFetch<{ + const { worker } = await gqlFetchWithRetry<{ worker: WorkerData | null; - }>(getSubgraphUrl(networkData), GET_WORKER_QUERY, { - address: address.toLowerCase(), - }); + }>( + getSubgraphUrl(networkData), + GET_WORKER_QUERY, + { + address: address.toLowerCase(), + }, + retryConfig + ); if (!worker) return null; @@ -104,15 +109,20 @@ export class WorkerUtils { throw ErrorInvalidAddress; } - const { workers } = await gqlFetch<{ + const { workers } = await gqlFetchWithRetry<{ workers: WorkerData[]; - }>(getSubgraphUrl(networkData), GET_WORKERS_QUERY(filter), { - address: filter?.address?.toLowerCase(), - first: first, - skip: skip, - orderBy: orderBy, - orderDirection: orderDirection, - }); + }>( + getSubgraphUrl(networkData), + GET_WORKERS_QUERY(filter), + { + address: filter?.address?.toLowerCase(), + first: first, + skip: skip, + orderBy: orderBy, + orderDirection: orderDirection, + }, + filter.retryConfig + ); if (!workers) { return []; diff --git a/packages/sdk/typescript/human-protocol-sdk/test/utils.test.ts b/packages/sdk/typescript/human-protocol-sdk/test/utils.test.ts index 745119098c..9ee361dc7a 100644 --- a/packages/sdk/typescript/human-protocol-sdk/test/utils.test.ts +++ b/packages/sdk/typescript/human-protocol-sdk/test/utils.test.ts @@ -1,5 +1,13 @@ /* eslint-disable @typescript-eslint/no-non-null-assertion */ -import { describe, expect, test, vi } from 'vitest'; + +vi.mock('graphql-request', () => { + return { + default: vi.fn(), + }; +}); + +import * as gqlFetch from 'graphql-request'; +import { beforeEach, describe, expect, test, vi } from 'vitest'; import { ChainId } from '../src'; import { NETWORKS } from '../src/constants'; import { @@ -15,6 +23,8 @@ import { import { getSubgraphUrl, getUnixTimestamp, + gqlFetchWithRetry, + isIndexerError, isValidJson, isValidUrl, throwError, @@ -142,3 +152,186 @@ describe('throwError', () => { expect(() => throwError(errorObj)).toThrow(expectedError); }); }); + +describe('isIndexerError', () => { + test('returns false for null/undefined errors', () => { + expect(isIndexerError(null)).toBe(false); + expect(isIndexerError(undefined)).toBe(false); + expect(isIndexerError('')).toBe(false); + }); + + test('returns true for GraphQL errors with "bad indexers" message', () => { + const error = { + response: { + errors: [ + { + message: + 'bad indexers: {0xbdfb5ee5a2abf4fc7bb1bd1221067aef7f9de491: Timeout}', + }, + ], + }, + }; + expect(isIndexerError(error)).toBe(true); + }); + + test('returns false for regular GraphQL errors', () => { + const error = { + response: { + errors: [ + { + message: 'Field "unknownField" is not defined', + }, + ], + }, + }; + expect(isIndexerError(error)).toBe(false); + }); + + test('returns false for network/connection errors', () => { + const error = { + message: 'Network error: ECONNREFUSED', + }; + expect(isIndexerError(error)).toBe(false); + }); + + test('returns false for validation errors', () => { + const error = { + message: 'Invalid query syntax', + }; + expect(isIndexerError(error)).toBe(false); + }); +}); + +describe('gqlFetchWithRetry', () => { + const mockUrl = 'http://test-subgraph.com'; + const mockQuery = 'query { test }'; + const mockVariables = { id: '123' }; + + beforeEach(() => { + vi.clearAllMocks(); + vi.resetModules(); + }); + + test('calls gqlFetch directly when no config provided', async () => { + const expectedResult = { data: 'test' }; + const gqlFetchSpy = vi + .spyOn(gqlFetch, 'default') + .mockResolvedValue(expectedResult); + + const result = await gqlFetchWithRetry(mockUrl, mockQuery, mockVariables); + + expect(gqlFetchSpy).toHaveBeenCalledTimes(1); + expect(gqlFetchSpy).toHaveBeenCalledWith(mockUrl, mockQuery, mockVariables); + expect(result).toBe(expectedResult); + }); + + test('succeeds on first attempt with config', async () => { + const expectedResult = { data: 'test' }; + const gqlFetchSpy = vi + .spyOn(gqlFetch, 'default') + .mockResolvedValue(expectedResult); + + const result = await gqlFetchWithRetry(mockUrl, mockQuery, mockVariables, { + maxRetries: 3, + baseDelay: 100, + }); + + expect(gqlFetchSpy).toHaveBeenCalledTimes(1); + expect(result).toBe(expectedResult); + }); + + test('retries on bad indexers error', async () => { + const badIndexerError = { + response: { + errors: [{ message: 'bad indexers: {0x123: Timeout}' }], + }, + }; + const expectedResult = { data: 'success' }; + const gqlFetchSpy = vi + .spyOn(gqlFetch, 'default') + .mockRejectedValueOnce(badIndexerError) + .mockRejectedValueOnce(badIndexerError) + .mockResolvedValueOnce(expectedResult); + + const result = await gqlFetchWithRetry(mockUrl, mockQuery, mockVariables, { + maxRetries: 3, + baseDelay: 10, + }); + + expect(gqlFetchSpy).toHaveBeenCalledTimes(3); + expect(result).toBe(expectedResult); + }); + + test('throws immediately on non-indexer errors', async () => { + const regularError = new Error('Regular GraphQL error'); + const gqlFetchSpy = vi + .spyOn(gqlFetch, 'default') + .mockRejectedValue(regularError); + + await expect( + gqlFetchWithRetry(mockUrl, mockQuery, mockVariables, { + maxRetries: 3, + baseDelay: 10, + }) + ).rejects.toThrow('Regular GraphQL error'); + + expect(gqlFetchSpy).toHaveBeenCalledTimes(1); + }); + + test('throws after max retries exceeded', async () => { + const badIndexerError = { + response: { + errors: [{ message: 'bad indexers: {0x123: Timeout}' }], + }, + }; + const gqlFetchSpy = vi + .spyOn(gqlFetch, 'default') + .mockRejectedValue(badIndexerError); + + await expect( + gqlFetchWithRetry(mockUrl, mockQuery, mockVariables, { + maxRetries: 2, + baseDelay: 10, + }) + ).rejects.toEqual(badIndexerError); + + expect(gqlFetchSpy).toHaveBeenCalledTimes(3); + }); + + test('uses default values for missing maxRetries', async () => { + const badIndexerError = { + response: { + errors: [{ message: 'bad indexers: {0x123: Timeout}' }], + }, + }; + const gqlFetchSpy = vi + .spyOn(gqlFetch, 'default') + .mockRejectedValue(badIndexerError); + + await expect( + gqlFetchWithRetry(mockUrl, mockQuery, mockVariables, { baseDelay: 10 }) + ).rejects.toEqual(badIndexerError); + + expect(gqlFetchSpy).toHaveBeenCalledTimes(4); + }); + + test('uses custom maxRetries when provided', async () => { + const badIndexerError = { + response: { + errors: [{ message: 'bad indexers: {0x123: Timeout}' }], + }, + }; + const gqlFetchSpy = vi + .spyOn(gqlFetch, 'default') + .mockRejectedValue(badIndexerError); + + await expect( + gqlFetchWithRetry(mockUrl, mockQuery, mockVariables, { + maxRetries: 1, + baseDelay: 10, + }) + ).rejects.toEqual(badIndexerError); + + expect(gqlFetchSpy).toHaveBeenCalledTimes(2); + }); +}); From 6b5851357e0400a13582bea1e3726a7f7804b38c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Francisco=20L=C3=B3pez?= Date: Fri, 7 Nov 2025 17:01:18 +0100 Subject: [PATCH 2/7] add retry configuration to subgraph requests across both SDKs Also fixed unit tests --- .../sdk/python/human-protocol-sdk/example.py | 9 +- .../human_protocol_sdk/escrow/escrow_utils.py | 30 +++- .../kvstore/kvstore_utils.py | 26 ++- .../operator/operator_utils.py | 23 ++- .../staking/staking_utils.py | 15 +- .../statistics/statistics_client.py | 40 ++++- .../transaction/transaction_utils.py | 14 +- .../human_protocol_sdk/utils.py | 153 +++++++++++------- .../human_protocol_sdk/worker/worker_utils.py | 17 +- .../escrow/test_escrow_utils.py | 11 +- .../kvstore/test_kvstore_utils.py | 5 + .../operator/test_operator_utils.py | 14 ++ .../staking/test_staking_utils.py | 2 + .../statistics/test_statistics_client.py | 7 + .../test/human_protocol_sdk/test_utils.py | 105 +++++++++++- .../transaction/test_transaction_utils.py | 4 + .../worker/test_worker_utils.py | 4 + .../human-protocol-sdk/src/escrow.ts | 30 +++- .../human-protocol-sdk/src/interfaces.ts | 9 -- .../human-protocol-sdk/src/kvstore.ts | 15 +- .../human-protocol-sdk/src/operator.ts | 80 ++++++--- .../human-protocol-sdk/src/staking.ts | 10 +- .../human-protocol-sdk/src/statistics.ts | 136 ++++++++++------ .../human-protocol-sdk/src/transaction.ts | 7 +- .../human-protocol-sdk/src/worker.ts | 9 +- .../human-protocol-sdk/test/kvstore.test.ts | 27 ++-- .../test/statistics.test.ts | 3 +- 27 files changed, 601 insertions(+), 204 deletions(-) diff --git a/packages/sdk/python/human-protocol-sdk/example.py b/packages/sdk/python/human-protocol-sdk/example.py index ddc2c65264..62037b22ea 100644 --- a/packages/sdk/python/human-protocol-sdk/example.py +++ b/packages/sdk/python/human-protocol-sdk/example.py @@ -18,6 +18,7 @@ from human_protocol_sdk.operator import OperatorUtils, OperatorFilter from human_protocol_sdk.agreement import agreement from human_protocol_sdk.staking.staking_utils import StakingUtils +from human_protocol_sdk.utils import SubgraphRetryConfig def get_escrow_statistics(statistics_client: StatisticsClient): @@ -162,7 +163,8 @@ def get_escrows(): status=Status.Pending, date_from=datetime.datetime(2023, 5, 8), date_to=datetime.datetime(2023, 6, 8), - ) + ), + SubgraphRetryConfig(3, 1000), ) ) @@ -232,12 +234,13 @@ def get_stakers_example(): chain_id=ChainId.POLYGON_AMOY, order_by="lastDepositTimestamp", order_direction=OrderDirection.ASC, - ) + ), + SubgraphRetryConfig(3, 1000), ) print("Filtered stakers:", len(stakers)) if stakers: - staker = StakingUtils.get_staker(ChainId.LOCALHOST, stakers[0].address) + staker = StakingUtils.get_staker(ChainId.POLYGON_AMOY, stakers[0].address) print("Staker info:", staker.address) else: print("No stakers found.") diff --git a/packages/sdk/python/human-protocol-sdk/human_protocol_sdk/escrow/escrow_utils.py b/packages/sdk/python/human-protocol-sdk/human_protocol_sdk/escrow/escrow_utils.py index 3d1512a04a..ab47ead27e 100644 --- a/packages/sdk/python/human-protocol-sdk/human_protocol_sdk/escrow/escrow_utils.py +++ b/packages/sdk/python/human-protocol-sdk/human_protocol_sdk/escrow/escrow_utils.py @@ -38,6 +38,7 @@ PayoutFilter, ) from human_protocol_sdk.utils import ( + SubgraphRetryConfig, get_data_from_subgraph, ) @@ -219,10 +220,12 @@ class EscrowUtils: @staticmethod def get_escrows( filter: EscrowFilter, + retry_config: Optional[SubgraphRetryConfig] = None, ) -> List[EscrowData]: """Get an array of escrow addresses based on the specified filter parameters. :param filter: Object containing all the necessary parameters to filter + :param retry_config: Optional retry behaviour for subgraph requests :return: List of escrows @@ -283,6 +286,7 @@ def get_escrows( "skip": filter.skip, "orderDirection": filter.order_direction.value, }, + retry_config=retry_config, ) if ( @@ -334,11 +338,13 @@ def get_escrows( def get_escrow( chain_id: ChainId, escrow_address: str, + retry_config: Optional[SubgraphRetryConfig] = None, ) -> Optional[EscrowData]: """Returns the escrow for a given address. :param chain_id: Network in which the escrow has been deployed :param escrow_address: Address of the escrow + :param retry_config: Optional retry behaviour for subgraph requests :return: Escrow data @@ -373,6 +379,7 @@ def get_escrow( params={ "escrowAddress": escrow_address.lower(), }, + retry_config=retry_config, ) if ( @@ -414,11 +421,15 @@ def get_escrow( ) @staticmethod - def get_status_events(filter: StatusEventFilter) -> List[StatusEvent]: + def get_status_events( + filter: StatusEventFilter, + retry_config: Optional[SubgraphRetryConfig] = None, + ) -> List[StatusEvent]: """ Retrieve status events for specified networks and statuses within a date range. :param filter: Object containing all the necessary parameters to filter status events. + :param retry_config: Optional retry behaviour for subgraph requests :return List[StatusEvent]: List of status events matching the query parameters. @@ -447,6 +458,7 @@ def get_status_events(filter: StatusEventFilter) -> List[StatusEvent]: "skip": filter.skip, "orderDirection": filter.order_direction.value, }, + retry_config=retry_config, ) if ( @@ -472,11 +484,15 @@ def get_status_events(filter: StatusEventFilter) -> List[StatusEvent]: return events_with_chain_id @staticmethod - def get_payouts(filter: PayoutFilter) -> List[Payout]: + def get_payouts( + filter: PayoutFilter, + retry_config: Optional[SubgraphRetryConfig] = None, + ) -> List[Payout]: """ Fetch payouts from the subgraph based on the provided filter. :param filter: Object containing all the necessary parameters to filter payouts. + :param retry_config: Optional retry behaviour for subgraph requests :return List[Payout]: List of payouts matching the query parameters. @@ -508,6 +524,7 @@ def get_payouts(filter: PayoutFilter) -> List[Payout]: "skip": filter.skip, "orderDirection": filter.order_direction.value, }, + retry_config=retry_config, ) if ( @@ -536,11 +553,13 @@ def get_payouts(filter: PayoutFilter) -> List[Payout]: @staticmethod def get_cancellation_refunds( filter: CancellationRefundFilter, + retry_config: Optional[SubgraphRetryConfig] = None, ) -> List[CancellationRefund]: """ Fetch cancellation refunds from the subgraph based on the provided filter. :param filter: Object containing all the necessary parameters to filter cancellation refunds. + :param retry_config: Optional retry behaviour for subgraph requests :return List[CancellationRefund]: List of cancellation refunds matching the query parameters. @@ -572,6 +591,7 @@ def get_cancellation_refunds( "skip": filter.skip, "orderDirection": filter.order_direction.value, }, + retry_config=retry_config, ) if ( @@ -601,13 +621,16 @@ def get_cancellation_refunds( @staticmethod def get_cancellation_refund( - chain_id: ChainId, escrow_address: str + chain_id: ChainId, + escrow_address: str, + retry_config: Optional[SubgraphRetryConfig] = None, ) -> CancellationRefund: """ Returns the cancellation refund for a given escrow address. :param chain_id: Network in which the escrow has been deployed :param escrow_address: Address of the escrow + :param retry_config: Optional retry behaviour for subgraph requests :return: CancellationRefund data or None @@ -641,6 +664,7 @@ def get_cancellation_refund( { "escrowAddress": escrow_address.lower(), }, + retry_config=retry_config, ) if ( diff --git a/packages/sdk/python/human-protocol-sdk/human_protocol_sdk/kvstore/kvstore_utils.py b/packages/sdk/python/human-protocol-sdk/human_protocol_sdk/kvstore/kvstore_utils.py index 9886b144eb..013d0dfd28 100644 --- a/packages/sdk/python/human-protocol-sdk/human_protocol_sdk/kvstore/kvstore_utils.py +++ b/packages/sdk/python/human-protocol-sdk/human_protocol_sdk/kvstore/kvstore_utils.py @@ -29,7 +29,7 @@ import requests from human_protocol_sdk.constants import NETWORKS, ChainId, KVStoreKeys -from human_protocol_sdk.utils import get_data_from_subgraph +from human_protocol_sdk.utils import SubgraphRetryConfig, get_data_from_subgraph from human_protocol_sdk.kvstore.kvstore_client import KVStoreClientError @@ -57,11 +57,13 @@ class KVStoreUtils: def get_kvstore_data( chain_id: ChainId, address: str, + retry_config: Optional[SubgraphRetryConfig] = None, ) -> Optional[List[KVStoreData]]: """Returns the KVStore data for a given address. :param chain_id: Network in which the KVStore data has been deployed :param address: Address of the KVStore + :param retry_config: Optional retry behaviour for subgraph requests :return: List of KVStore data @@ -94,6 +96,7 @@ def get_kvstore_data( params={ "address": address.lower(), }, + retry_config=retry_config, ) if ( @@ -111,12 +114,18 @@ def get_kvstore_data( ] @staticmethod - def get(chain_id: ChainId, address: str, key: str) -> str: + def get( + chain_id: ChainId, + address: str, + key: str, + retry_config: Optional[SubgraphRetryConfig] = None, + ) -> str: """Gets the value of a key-value pair in the contract. :param chain_id: Network in which the KVStore data has been deployed :param address: The Ethereum address associated with the key-value pair :param key: The key of the key-value pair to get + :param retry_config: Optional retry behaviour for subgraph requests :return: The value of the key-value pair if it exists @@ -149,6 +158,7 @@ def get(chain_id: ChainId, address: str, key: str) -> str: "address": address.lower(), "key": key, }, + retry_config=retry_config, ) if ( @@ -163,13 +173,17 @@ def get(chain_id: ChainId, address: str, key: str) -> str: @staticmethod def get_file_url_and_verify_hash( - chain_id: ChainId, address: str, key: Optional[str] = "url" + chain_id: ChainId, + address: str, + key: Optional[str] = "url", + retry_config: Optional[SubgraphRetryConfig] = None, ) -> str: """Gets the URL value of the given entity, and verify its hash. :param chain_id: Network in which the KVStore data has been deployed :param address: Address from which to get the URL value. :param key: Configurable URL key. `url` by default. + :param retry_config: Optional retry behaviour for subgraph requests :return url: The URL value of the given address if exists, and the content is valid @@ -189,8 +203,10 @@ def get_file_url_and_verify_hash( if not Web3.is_address(address): raise KVStoreClientError(f"Invalid address: {address}") - url = KVStoreUtils.get(chain_id, address, key) - hash = KVStoreUtils.get(chain_id, address, key + "_hash") + url = KVStoreUtils.get(chain_id, address, key, retry_config=retry_config) + hash = KVStoreUtils.get( + chain_id, address, key + "_hash", retry_config=retry_config + ) if len(url) == 0: return url diff --git a/packages/sdk/python/human-protocol-sdk/human_protocol_sdk/operator/operator_utils.py b/packages/sdk/python/human-protocol-sdk/human_protocol_sdk/operator/operator_utils.py index 9bed4d8fc6..8d922ef890 100644 --- a/packages/sdk/python/human-protocol-sdk/human_protocol_sdk/operator/operator_utils.py +++ b/packages/sdk/python/human-protocol-sdk/human_protocol_sdk/operator/operator_utils.py @@ -25,7 +25,7 @@ from human_protocol_sdk.constants import NETWORKS, ChainId, OrderDirection from human_protocol_sdk.gql.reward import get_reward_added_events_query -from human_protocol_sdk.utils import get_data_from_subgraph +from human_protocol_sdk.utils import SubgraphRetryConfig, get_data_from_subgraph from web3 import Web3 LOG = logging.getLogger("human_protocol_sdk.operator") @@ -198,10 +198,14 @@ class OperatorUtils: """ @staticmethod - def get_operators(filter: OperatorFilter) -> List[OperatorData]: + def get_operators( + filter: OperatorFilter, + retry_config: Optional[SubgraphRetryConfig] = None, + ) -> List[OperatorData]: """Get operators data of the protocol. :param filter: Operator filter + :param retry_config: Optional retry behaviour for subgraph requests :return: List of operators data @@ -237,6 +241,7 @@ def get_operators(filter: OperatorFilter) -> List[OperatorData]: "first": filter.first, "skip": filter.skip, }, + retry_config=retry_config, ) if ( @@ -283,11 +288,13 @@ def get_operators(filter: OperatorFilter) -> List[OperatorData]: def get_operator( chain_id: ChainId, operator_address: str, + retry_config: Optional[SubgraphRetryConfig] = None, ) -> Optional[OperatorData]: """Gets the operator details. :param chain_id: Network in which the operator exists :param operator_address: Address of the operator + :param retry_config: Optional retry behaviour for subgraph requests :return: Operator data if exists, otherwise None @@ -318,6 +325,7 @@ def get_operator( network, query=get_operator_query, params={"address": operator_address.lower()}, + retry_config=retry_config, ) if ( @@ -359,12 +367,14 @@ def get_reputation_network_operators( chain_id: ChainId, address: str, role: Optional[str] = None, + retry_config: Optional[SubgraphRetryConfig] = None, ) -> List[OperatorData]: """Get the reputation network operators of the specified address. :param chain_id: Network in which the reputation network exists :param address: Address of the reputation oracle :param role: (Optional) Role of the operator + :param retry_config: Optional retry behaviour for subgraph requests :return: Returns an array of operator details @@ -395,6 +405,7 @@ def get_reputation_network_operators( network, query=get_reputation_network_query(role), params={"address": address.lower(), "role": role}, + retry_config=retry_config, ) if ( @@ -438,11 +449,16 @@ def get_reputation_network_operators( return result @staticmethod - def get_rewards_info(chain_id: ChainId, slasher: str) -> List[RewardData]: + def get_rewards_info( + chain_id: ChainId, + slasher: str, + retry_config: Optional[SubgraphRetryConfig] = None, + ) -> List[RewardData]: """Get rewards of the given slasher. :param chain_id: Network in which the slasher exists :param slasher: Address of the slasher + :param retry_config: Optional retry behaviour for subgraph requests :return: List of rewards info @@ -471,6 +487,7 @@ def get_rewards_info(chain_id: ChainId, slasher: str) -> List[RewardData]: network, query=get_reward_added_events_query, params={"slasherAddress": slasher.lower()}, + retry_config=retry_config, ) if ( diff --git a/packages/sdk/python/human-protocol-sdk/human_protocol_sdk/staking/staking_utils.py b/packages/sdk/python/human-protocol-sdk/human_protocol_sdk/staking/staking_utils.py index 2da710d030..d29ea52869 100644 --- a/packages/sdk/python/human-protocol-sdk/human_protocol_sdk/staking/staking_utils.py +++ b/packages/sdk/python/human-protocol-sdk/human_protocol_sdk/staking/staking_utils.py @@ -30,7 +30,7 @@ from typing import List, Optional from human_protocol_sdk.constants import NETWORKS, ChainId from human_protocol_sdk.filter import StakersFilter -from human_protocol_sdk.utils import get_data_from_subgraph +from human_protocol_sdk.utils import SubgraphRetryConfig, get_data_from_subgraph from human_protocol_sdk.gql.staking import get_staker_query, get_stakers_query @@ -62,7 +62,11 @@ class StakingUtilsError(Exception): class StakingUtils: @staticmethod - def get_staker(chain_id: ChainId, address: str) -> Optional[StakerData]: + def get_staker( + chain_id: ChainId, + address: str, + retry_config: Optional[SubgraphRetryConfig] = None, + ) -> Optional[StakerData]: network = NETWORKS.get(chain_id) if not network: raise StakingUtilsError("Unsupported Chain ID") @@ -71,6 +75,7 @@ def get_staker(chain_id: ChainId, address: str) -> Optional[StakerData]: network, query=get_staker_query(), params={"id": address.lower()}, + retry_config=retry_config, ) if ( not data @@ -93,7 +98,10 @@ def get_staker(chain_id: ChainId, address: str) -> Optional[StakerData]: ) @staticmethod - def get_stakers(filter: StakersFilter) -> List[StakerData]: + def get_stakers( + filter: StakersFilter, + retry_config: Optional[SubgraphRetryConfig] = None, + ) -> List[StakerData]: network_data = NETWORKS.get(filter.chain_id) if not network_data: raise StakingUtilsError("Unsupported Chain ID") @@ -115,6 +123,7 @@ def get_stakers(filter: StakersFilter) -> List[StakerData]: "first": filter.first, "skip": filter.skip, }, + retry_config=retry_config, ) if ( not data diff --git a/packages/sdk/python/human-protocol-sdk/human_protocol_sdk/statistics/statistics_client.py b/packages/sdk/python/human-protocol-sdk/human_protocol_sdk/statistics/statistics_client.py index 3a42205689..1c785e973f 100644 --- a/packages/sdk/python/human-protocol-sdk/human_protocol_sdk/statistics/statistics_client.py +++ b/packages/sdk/python/human-protocol-sdk/human_protocol_sdk/statistics/statistics_client.py @@ -22,7 +22,7 @@ from human_protocol_sdk.constants import ChainId, NETWORKS -from human_protocol_sdk.utils import get_data_from_subgraph +from human_protocol_sdk.utils import SubgraphRetryConfig, get_data_from_subgraph from human_protocol_sdk.filter import StatisticsFilter LOG = logging.getLogger("human_protocol_sdk.statistics") @@ -290,11 +290,14 @@ def __init__(self, chain_id: ChainId = ChainId.POLYGON_AMOY): raise StatisticsClientError("Empty network configuration") def get_escrow_statistics( - self, filter: StatisticsFilter = StatisticsFilter() + self, + filter: StatisticsFilter = StatisticsFilter(), + retry_config: Optional[SubgraphRetryConfig] = None, ) -> EscrowStatistics: """Get escrow statistics data for the given date range. :param filter: Object containing the date range + :param retry_config: Optional retry behaviour for subgraph requests :return: Escrow statistics data @@ -326,6 +329,7 @@ def get_escrow_statistics( escrow_statistics_data = get_data_from_subgraph( self.network, query=get_escrow_statistics_query, + retry_config=retry_config, ) escrow_statistics = escrow_statistics_data["data"]["escrowStatistics"] @@ -339,6 +343,7 @@ def get_escrow_statistics( "skip": filter.skip, "orderDirection": filter.order_direction.value, }, + retry_config=retry_config, ) event_day_datas = event_day_datas_data["data"]["eventDayDatas"] @@ -368,11 +373,14 @@ def get_escrow_statistics( ) def get_worker_statistics( - self, filter: StatisticsFilter = StatisticsFilter() + self, + filter: StatisticsFilter = StatisticsFilter(), + retry_config: Optional[SubgraphRetryConfig] = None, ) -> WorkerStatistics: """Get worker statistics data for the given date range. :param filter: Object containing the date range + :param retry_config: Optional retry behaviour for subgraph requests :return: Worker statistics data @@ -409,6 +417,7 @@ def get_worker_statistics( "skip": filter.skip, "orderDirection": filter.order_direction.value, }, + retry_config=retry_config, ) event_day_datas = event_day_datas_data["data"]["eventDayDatas"] @@ -425,11 +434,14 @@ def get_worker_statistics( ) def get_payment_statistics( - self, filter: StatisticsFilter = StatisticsFilter() + self, + filter: StatisticsFilter = StatisticsFilter(), + retry_config: Optional[SubgraphRetryConfig] = None, ) -> PaymentStatistics: """Get payment statistics data for the given date range. :param filter: Object containing the date range + :param retry_config: Optional retry behaviour for subgraph requests :return: Payment statistics data @@ -467,6 +479,7 @@ def get_payment_statistics( "skip": filter.skip, "orderDirection": filter.order_direction.value, }, + retry_config=retry_config, ) event_day_datas = event_day_datas_data["data"]["eventDayDatas"] @@ -491,9 +504,13 @@ def get_payment_statistics( ], ) - def get_hmt_statistics(self) -> HMTStatistics: + def get_hmt_statistics( + self, retry_config: Optional[SubgraphRetryConfig] = None + ) -> HMTStatistics: """Get HMT statistics data. + :param retry_config: Optional retry behaviour for subgraph requests + :return: HMT statistics data :example: @@ -514,6 +531,7 @@ def get_hmt_statistics(self) -> HMTStatistics: hmtoken_statistics_data = get_data_from_subgraph( self.network, query=get_hmtoken_statistics_query, + retry_config=retry_config, ) hmtoken_statistics = hmtoken_statistics_data["data"]["hmtokenStatistics"] @@ -528,11 +546,14 @@ def get_hmt_statistics(self) -> HMTStatistics: ) def get_hmt_holders( - self, param: HMTHoldersParam = HMTHoldersParam() + self, + param: HMTHoldersParam = HMTHoldersParam(), + retry_config: Optional[SubgraphRetryConfig] = None, ) -> List[HMTHolder]: """Get HMT holders data with optional filters and ordering. :param param: Object containing filter and order parameters + :param retry_config: Optional retry behaviour for subgraph requests :return: List of HMT holders @@ -564,6 +585,7 @@ def get_hmt_holders( "orderBy": "balance", "orderDirection": param.order_direction, }, + retry_config=retry_config, ) holders = holders_data["data"]["holders"] @@ -577,11 +599,14 @@ def get_hmt_holders( ] def get_hmt_daily_data( - self, filter: StatisticsFilter = StatisticsFilter() + self, + filter: StatisticsFilter = StatisticsFilter(), + retry_config: Optional[SubgraphRetryConfig] = None, ) -> List[DailyHMTData]: """Get HMT daily statistics data for the given date range. :param filter: Object containing the date range + :param retry_config: Optional retry behaviour for subgraph requests :return: HMT statistics data @@ -617,6 +642,7 @@ def get_hmt_daily_data( "skip": filter.skip, "orderDirection": filter.order_direction.value, }, + retry_config=retry_config, ) event_day_datas = event_day_datas_data["data"]["eventDayDatas"] diff --git a/packages/sdk/python/human-protocol-sdk/human_protocol_sdk/transaction/transaction_utils.py b/packages/sdk/python/human-protocol-sdk/human_protocol_sdk/transaction/transaction_utils.py index 1b88aa89da..500320a20d 100644 --- a/packages/sdk/python/human-protocol-sdk/human_protocol_sdk/transaction/transaction_utils.py +++ b/packages/sdk/python/human-protocol-sdk/human_protocol_sdk/transaction/transaction_utils.py @@ -30,7 +30,7 @@ from human_protocol_sdk.constants import NETWORKS, ChainId from web3 import Web3 from human_protocol_sdk.filter import TransactionFilter -from human_protocol_sdk.utils import get_data_from_subgraph +from human_protocol_sdk.utils import SubgraphRetryConfig, get_data_from_subgraph class InternalTransaction: @@ -97,11 +97,14 @@ class TransactionUtils: """ @staticmethod - def get_transaction(chain_id: ChainId, hash: str) -> Optional[TransactionData]: + def get_transaction( + chain_id: ChainId, hash: str, retry_config: Optional[SubgraphRetryConfig] = None + ) -> Optional[TransactionData]: """Returns the transaction for a given hash. :param chain_id: Network in which the transaction was executed :param hash: Hash of the transaction + :param retry_config: Optional retry behaviour for subgraph requests :return: Transaction data @@ -128,6 +131,7 @@ def get_transaction(chain_id: ChainId, hash: str) -> Optional[TransactionData]: network, query=get_transaction_query(), params={"hash": hash.lower()}, + retry_config=retry_config, ) if ( not transaction_data @@ -166,11 +170,14 @@ def get_transaction(chain_id: ChainId, hash: str) -> Optional[TransactionData]: ) @staticmethod - def get_transactions(filter: TransactionFilter) -> List[TransactionData]: + def get_transactions( + filter: TransactionFilter, retry_config: Optional[SubgraphRetryConfig] = None + ) -> List[TransactionData]: """Get an array of transactions based on the specified filter parameters. :param filter: Object containing all the necessary parameters to filter (chain_id, from_address, to_address, start_date, end_date, start_block, end_block, method, escrow, token, first, skip, order_direction) + :param retry_config: Optional retry behaviour for subgraph requests :return: List of transactions @@ -223,6 +230,7 @@ def get_transactions(filter: TransactionFilter) -> List[TransactionData]: "skip": filter.skip, "orderDirection": filter.order_direction.value, }, + retry_config=retry_config, ) if ( not data diff --git a/packages/sdk/python/human-protocol-sdk/human_protocol_sdk/utils.py b/packages/sdk/python/human-protocol-sdk/human_protocol_sdk/utils.py index c9dd1c7bf5..0d72db2e0b 100644 --- a/packages/sdk/python/human-protocol-sdk/human_protocol_sdk/utils.py +++ b/packages/sdk/python/human-protocol-sdk/human_protocol_sdk/utils.py @@ -3,7 +3,8 @@ import os import time import re -from typing import Tuple, Optional +from typing import Tuple, Optional, Dict, Any +from dataclasses import dataclass import requests from validators import url as URL @@ -32,41 +33,108 @@ pass -def with_retry(fn, retries=3, delay=5, backoff=2): - """Retry a function +@dataclass +class SubgraphRetryConfig: + """Configuration for subgraph retry logic.""" - Mainly used with handle_transaction to retry on case of failure. - Uses exponential backoff. + max_retries: int = 3 + base_delay: int = 1000 # milliseconds - :param fn: to run with retry logic. - :param retries: number of times to retry the transaction - :param delay: time to wait (exponentially) - :param backoff: defines the rate of grow for the exponential wait. - :return: False if transaction never succeeded, - otherwise the return of the function +def is_indexer_error(error: Exception) -> bool: + """ + Check if an error indicates that the indexer is down or not synced. + This function specifically checks for "bad indexers" errors from The Graph. - :note: If the partial returns a Boolean and it happens to be False, - we would not know if the tx succeeded and it will retry. + :param error: The error to check + :return: True if the error indicates indexer issues """ + if not error: + return False - wait_time = delay + response = getattr(error, "response", None) - for i in range(retries): + message = "" + if response is not None: try: - result = fn() - if result: - return result - except Exception as e: - name = getattr(fn, "__name__", "partial") - logger.warning( - f"(x{i+1}) {name} exception: {e}. Retrying after {wait_time} sec..." - ) + data = response.json() + except Exception: + data = None + + if isinstance(data, dict): + errors = data.get("errors") + if isinstance(errors, list) and errors: + first_error = errors[0] + if isinstance(first_error, dict): + message = str(first_error.get("message", "")) + + if not message: + message = getattr(error, "message", "") or str(error) or "" + + return "bad indexers" in message.lower() + + +def get_data_from_subgraph( + network: dict, + query: str, + params: dict = None, + retry_config: Optional[SubgraphRetryConfig] = None, +): + """Fetch data from the subgraph with optional retry logic for bad indexer errors. + + :param network: Network configuration dictionary + :param query: GraphQL query string + :param params: Query parameters + :param retry_config: Optional retry configuration for bad indexer errors + + :return: JSON response from the subgraph + + :raise Exception: If the subgraph query fails + """ + if not retry_config: + return _fetch_subgraph_data(network, query, params) + + max_retries = retry_config.max_retries + base_delay = retry_config.base_delay / 1000 - time.sleep(wait_time) - wait_time *= backoff + last_error = None - return False + for attempt in range(max_retries + 1): + try: + return _fetch_subgraph_data(network, query, params) + except Exception as error: + last_error = error + + if not is_indexer_error(error): + break + + delay = base_delay * (2**attempt) + time.sleep(delay) + + raise last_error + + +def _fetch_subgraph_data(network: dict, query: str, params: dict = None): + subgraph_api_key = os.getenv("SUBGRAPH_API_KEY", "") + if subgraph_api_key: + subgraph_url = network["subgraph_url_api_key"].replace( + SUBGRAPH_API_KEY_PLACEHOLDER, subgraph_api_key + ) + else: + logger.warning( + "Warning: SUBGRAPH_API_KEY is not provided. It might cause issues with the subgraph." + ) + subgraph_url = network["subgraph_url"] + + request = requests.post(subgraph_url, json={"query": query, "variables": params}) + if request.status_code == 200: + return request.json() + else: + raise Exception( + "Subgraph query failed. return code is {}. \n{}".format( + request.status_code, query + ) + ) def get_hmt_balance(wallet_addr, token_addr, w3): @@ -192,39 +260,6 @@ def get_kvstore_interface(): ) -def get_data_from_subgraph(network: dict, query: str, params: dict = None): - """Fetch data from the subgraph. - - :param network: Network configuration dictionary - :param query: GraphQL query string - :param params: Query parameters - - :return: JSON response from the subgraph - - :raise Exception: If the subgraph query fails - """ - subgraph_api_key = os.getenv("SUBGRAPH_API_KEY", "") - if subgraph_api_key: - subgraph_url = network["subgraph_url_api_key"].replace( - SUBGRAPH_API_KEY_PLACEHOLDER, subgraph_api_key - ) - else: - logger.warning( - "Warning: SUBGRAPH_API_KEY is not provided. It might cause issues with the subgraph." - ) - subgraph_url = network["subgraph_url"] - - request = requests.post(subgraph_url, json={"query": query, "variables": params}) - if request.status_code == 200: - return request.json() - else: - raise Exception( - "Subgraph query failed. return code is {}. \n{}".format( - request.status_code, query - ) - ) - - def handle_error(e, exception_class): """ Handles and translates errors raised during contract transactions. diff --git a/packages/sdk/python/human-protocol-sdk/human_protocol_sdk/worker/worker_utils.py b/packages/sdk/python/human-protocol-sdk/human_protocol_sdk/worker/worker_utils.py index c363898ce6..e7d2c7a523 100644 --- a/packages/sdk/python/human-protocol-sdk/human_protocol_sdk/worker/worker_utils.py +++ b/packages/sdk/python/human-protocol-sdk/human_protocol_sdk/worker/worker_utils.py @@ -4,7 +4,7 @@ from web3 import Web3 from human_protocol_sdk.constants import NETWORKS, ChainId -from human_protocol_sdk.utils import get_data_from_subgraph +from human_protocol_sdk.utils import SubgraphRetryConfig, get_data_from_subgraph from human_protocol_sdk.filter import WorkerFilter LOG = logging.getLogger("human_protocol_sdk.worker") @@ -47,10 +47,14 @@ class WorkerUtils: """ @staticmethod - def get_workers(filter: WorkerFilter) -> List[WorkerData]: + def get_workers( + filter: WorkerFilter, + retry_config: Optional[SubgraphRetryConfig] = None, + ) -> List[WorkerData]: """Get workers data of the protocol. :param filter: Worker filter + :param retry_config: Optional retry behaviour for subgraph requests :return: List of workers data """ @@ -72,6 +76,7 @@ def get_workers(filter: WorkerFilter) -> List[WorkerData]: "first": filter.first, "skip": filter.skip, }, + retry_config=retry_config, ) if ( @@ -97,11 +102,16 @@ def get_workers(filter: WorkerFilter) -> List[WorkerData]: return workers @staticmethod - def get_worker(chain_id: ChainId, worker_address: str) -> Optional[WorkerData]: + def get_worker( + chain_id: ChainId, + worker_address: str, + retry_config: Optional[SubgraphRetryConfig] = None, + ) -> Optional[WorkerData]: """Gets the worker details. :param chain_id: Network in which the worker exists :param worker_address: Address of the worker + :param retry_config: Optional retry behaviour for subgraph requests :return: Worker data if exists, otherwise None """ @@ -120,6 +130,7 @@ def get_worker(chain_id: ChainId, worker_address: str) -> Optional[WorkerData]: network, query=get_worker_query(), params={"address": worker_address.lower()}, + retry_config=retry_config, ) if ( diff --git a/packages/sdk/python/human-protocol-sdk/test/human_protocol_sdk/escrow/test_escrow_utils.py b/packages/sdk/python/human-protocol-sdk/test/human_protocol_sdk/escrow/test_escrow_utils.py index 88b52db16e..862e390d72 100644 --- a/packages/sdk/python/human-protocol-sdk/test/human_protocol_sdk/escrow/test_escrow_utils.py +++ b/packages/sdk/python/human-protocol-sdk/test/human_protocol_sdk/escrow/test_escrow_utils.py @@ -51,7 +51,7 @@ def test_get_escrows(self): "createdAt": "1683811973", } - def side_effect(subgraph_url, query, params): + def side_effect(subgraph_url, query, params, retry_config): if subgraph_url == NETWORKS[ChainId.POLYGON_AMOY]: return {"data": {"escrows": [mock_escrow]}} @@ -83,6 +83,7 @@ def side_effect(subgraph_url, query, params): "skip": 0, "orderDirection": "desc", }, + retry_config=None, ) self.assertEqual(len(filtered), 1) self.assertEqual(filtered[0].address, mock_escrow["address"]) @@ -154,6 +155,7 @@ def side_effect(subgraph_url, query, params): "skip": 0, "orderDirection": "desc", }, + retry_config=None, ) self.assertEqual(len(filtered), 1) self.assertEqual(filtered[0].chain_id, ChainId.POLYGON_AMOY) @@ -204,7 +206,7 @@ def test_get_escrows_with_status_array(self): "createdAt": "1672531200000", } - def side_effect(subgraph_url, query, params): + def side_effect(subgraph_url, query, params, retry_config): if subgraph_url == NETWORKS[ChainId.POLYGON_AMOY]: return {"data": {"escrows": [mock_escrow_1, mock_escrow_2]}} @@ -232,6 +234,7 @@ def side_effect(subgraph_url, query, params): "skip": 0, "orderDirection": "desc", }, + retry_config=None, ) self.assertEqual(len(filtered), 2) self.assertEqual(filtered[0].address, mock_escrow_1["address"]) @@ -284,6 +287,7 @@ def test_get_escrow(self): params={ "escrowAddress": "0x1234567890123456789012345678901234567890", }, + retry_config=None, ) self.assertEqual(escrow.chain_id, ChainId.POLYGON_AMOY) self.assertEqual(escrow.address, mock_escrow["address"]) @@ -343,6 +347,7 @@ def test_get_escrow_empty_data(self): params={ "escrowAddress": "0x1234567890123456789012345678901234567890", }, + retry_config=None, ) self.assertEqual(escrow, None) @@ -616,7 +621,7 @@ def test_get_cancellation_refunds(self): "txHash": "0xhash1", } - def side_effect(subgraph_url, query, params): + def side_effect(subgraph_url, query, params, retry_config): if subgraph_url == NETWORKS[ChainId.POLYGON_AMOY]: return {"data": {"cancellationRefundEvents": [mock_refund]}} diff --git a/packages/sdk/python/human-protocol-sdk/test/human_protocol_sdk/kvstore/test_kvstore_utils.py b/packages/sdk/python/human-protocol-sdk/test/human_protocol_sdk/kvstore/test_kvstore_utils.py index 35e590715b..95bc56ba82 100644 --- a/packages/sdk/python/human-protocol-sdk/test/human_protocol_sdk/kvstore/test_kvstore_utils.py +++ b/packages/sdk/python/human-protocol-sdk/test/human_protocol_sdk/kvstore/test_kvstore_utils.py @@ -51,6 +51,7 @@ def test_get_kvstore_data(self): params={ "address": "0x15d34aaf54267db7d7c367839aaf71a00a2c6a65", }, + retry_config=None, ) self.assertEqual(len(kvstores), 2) self.assertEqual(kvstores[0].key, "fee") @@ -76,6 +77,7 @@ def test_get_kvstore_data_empty_data(self): params={ "address": "0x15d34aaf54267db7d7c367839aaf71a00a2c6a65", }, + retry_config=None, ) self.assertEqual(kvstores, []) @@ -118,6 +120,7 @@ def test_get(self, mock_function): NETWORKS[ChainId.LOCALHOST], query=get_kvstore_by_address_and_key_query(), params={"address": address, "key": key}, + retry_config=None, ) self.assertEqual(result, "1") @@ -154,6 +157,7 @@ def test_get_empty_value(self, mock_function): NETWORKS[ChainId.LOCALHOST], query=get_kvstore_by_address_and_key_query(), params={"address": address, "key": key}, + retry_config=None, ) @patch("human_protocol_sdk.kvstore.kvstore_utils.get_data_from_subgraph") @@ -182,6 +186,7 @@ def test_get_without_account(self, mock_function): NETWORKS[ChainId.LOCALHOST], query=get_kvstore_by_address_and_key_query(), params={"address": address, "key": key}, + retry_config=None, ) self.assertEqual(result, "mock_value") diff --git a/packages/sdk/python/human-protocol-sdk/test/human_protocol_sdk/operator/test_operator_utils.py b/packages/sdk/python/human-protocol-sdk/test/human_protocol_sdk/operator/test_operator_utils.py index b77c23db86..3858d10e0c 100644 --- a/packages/sdk/python/human-protocol-sdk/test/human_protocol_sdk/operator/test_operator_utils.py +++ b/packages/sdk/python/human-protocol-sdk/test/human_protocol_sdk/operator/test_operator_utils.py @@ -67,6 +67,7 @@ def test_get_operators(self): "first": filter.first, "skip": filter.skip, }, + retry_config=None, ) self.assertEqual(len(operators), 1) @@ -143,6 +144,7 @@ def test_get_operators_when_job_types_is_none(self): "first": filter.first, "skip": filter.skip, }, + retry_config=None, ) self.assertEqual(len(operators), 1) @@ -219,6 +221,7 @@ def test_get_operators_when_job_types_is_array(self): "first": filter.first, "skip": filter.skip, }, + retry_config=None, ) self.assertEqual(len(operators), 1) @@ -271,6 +274,7 @@ def test_get_operators_empty_data(self): "first": filter.first, "skip": filter.skip, }, + retry_config=None, ) self.assertEqual(operators, []) @@ -321,6 +325,7 @@ def test_get_operator(self): NETWORKS[ChainId.POLYGON], query=get_operator_query, params={"address": staker_address}, + retry_config=None, ) self.assertNotEqual(operator, None) @@ -389,6 +394,7 @@ def test_get_operator_when_job_types_is_none(self): NETWORKS[ChainId.POLYGON], query=get_operator_query, params={"address": staker_address}, + retry_config=None, ) self.assertNotEqual(operator, None) @@ -457,6 +463,7 @@ def test_get_operator_when_job_types_is_array(self): NETWORKS[ChainId.POLYGON], query=get_operator_query, params={"address": staker_address}, + retry_config=None, ) self.assertNotEqual(operator, None) @@ -495,6 +502,7 @@ def test_get_operator_empty_data(self): NETWORKS[ChainId.POLYGON], query=get_operator_query, params={"address": staker_address}, + retry_config=None, ) self.assertEqual(operator, None) @@ -541,6 +549,7 @@ def test_get_reputation_network_operators(self): NETWORKS[ChainId.POLYGON], query=get_reputation_network_query(None), params={"address": reputation_address, "role": None}, + retry_config=None, ) self.assertNotEqual(operators, []) @@ -593,6 +602,7 @@ def test_get_reputation_network_operators_when_job_types_is_none(self): NETWORKS[ChainId.POLYGON], query=get_reputation_network_query(None), params={"address": reputation_address, "role": None}, + retry_config=None, ) self.assertNotEqual(operators, []) @@ -645,6 +655,7 @@ def test_get_reputation_network_operators_when_job_types_is_array(self): NETWORKS[ChainId.POLYGON], query=get_reputation_network_query(None), params={"address": reputation_address, "role": None}, + retry_config=None, ) self.assertNotEqual(operators, []) @@ -673,6 +684,7 @@ def test_get_reputation_network_operators_empty_data(self): NETWORKS[ChainId.POLYGON], query=get_reputation_network_query(None), params={"address": reputation_address, "role": None}, + retry_config=None, ) self.assertEqual(operators, []) @@ -704,6 +716,7 @@ def test_get_rewards_info(self): NETWORKS[ChainId.POLYGON], query=get_reward_added_events_query, params={"slasherAddress": slasher}, + retry_config=None, ) self.assertEqual(len(rewards_info), 2) @@ -726,6 +739,7 @@ def test_get_rewards_info_empty_data(self): NETWORKS[ChainId.POLYGON], query=get_reward_added_events_query, params={"slasherAddress": slasher}, + retry_config=None, ) self.assertEqual(rewards_info, []) diff --git a/packages/sdk/python/human-protocol-sdk/test/human_protocol_sdk/staking/test_staking_utils.py b/packages/sdk/python/human-protocol-sdk/test/human_protocol_sdk/staking/test_staking_utils.py index 0f9183eb8b..8f19790bf8 100644 --- a/packages/sdk/python/human-protocol-sdk/test/human_protocol_sdk/staking/test_staking_utils.py +++ b/packages/sdk/python/human-protocol-sdk/test/human_protocol_sdk/staking/test_staking_utils.py @@ -69,6 +69,7 @@ def test_get_stakers(self): "first": 2, "skip": 0, }, + retry_config=None, ) self.assertEqual(len(stakers), 2) self.assertIsInstance(stakers[0], StakerData) @@ -160,6 +161,7 @@ def test_get_staker(self): NETWORKS[ChainId.POLYGON_AMOY], query=get_staker_query(), params={"id": "0x123"}, + retry_config=None, ) self.assertIsInstance(staker, StakerData) self.assertEqual(staker.id, "1") diff --git a/packages/sdk/python/human-protocol-sdk/test/human_protocol_sdk/statistics/test_statistics_client.py b/packages/sdk/python/human-protocol-sdk/test/human_protocol_sdk/statistics/test_statistics_client.py index 666dc1029d..a45b383b7b 100644 --- a/packages/sdk/python/human-protocol-sdk/test/human_protocol_sdk/statistics/test_statistics_client.py +++ b/packages/sdk/python/human-protocol-sdk/test/human_protocol_sdk/statistics/test_statistics_client.py @@ -65,6 +65,7 @@ def test_get_escrow_statistics(self): mock_function.assert_any_call( NETWORKS[ChainId.LOCALHOST], query=get_escrow_statistics_query, + retry_config=None, ) mock_function.assert_any_call( @@ -77,6 +78,7 @@ def test_get_escrow_statistics(self): "skip": 0, "orderDirection": "asc", }, + retry_config=None, ) self.assertEqual(escrow_statistics.total_escrows, 1) @@ -128,6 +130,7 @@ def test_get_worker_statistics(self): "skip": 0, "orderDirection": "asc", }, + retry_config=None, ) self.assertEqual(len(payment_statistics.daily_workers_data), 1) @@ -173,6 +176,7 @@ def test_get_payment_statistics(self): "skip": 0, "orderDirection": "asc", }, + retry_config=None, ) self.assertEqual(len(payment_statistics.daily_payments_data), 1) @@ -211,6 +215,7 @@ def test_get_hmt_statistics(self): mock_function.assert_any_call( NETWORKS[ChainId.LOCALHOST], query=get_hmtoken_statistics_query, + retry_config=None, ) self.assertEqual(hmt_statistics.total_transfer_amount, 100) @@ -250,6 +255,7 @@ def test_get_hmt_holders(self): "orderBy": "balance", "orderDirection": param.order_direction, }, + retry_config=None, ) self.assertEqual(len(holders), 2) @@ -296,6 +302,7 @@ def test_get_hmt_daily_data(self): "skip": 0, "orderDirection": "asc", }, + retry_config=None, ) self.assertEqual(len(hmt_statistics), 1) diff --git a/packages/sdk/python/human-protocol-sdk/test/human_protocol_sdk/test_utils.py b/packages/sdk/python/human-protocol-sdk/test/human_protocol_sdk/test_utils.py index 1aea57fb90..b8c53b10c2 100644 --- a/packages/sdk/python/human-protocol-sdk/test/human_protocol_sdk/test_utils.py +++ b/packages/sdk/python/human-protocol-sdk/test/human_protocol_sdk/test_utils.py @@ -1,7 +1,21 @@ import unittest +from unittest.mock import Mock, patch from validators import ValidationError -from human_protocol_sdk.utils import validate_url +from human_protocol_sdk.utils import ( + SubgraphRetryConfig, + get_data_from_subgraph, + is_indexer_error, + validate_url, +) + + +def make_graphql_error(payload, message=""): + error = Exception(message or "GraphQL error") + response = Mock() + response.json.return_value = payload + error.response = response + return error class TestStorageClient(unittest.TestCase): @@ -12,4 +26,91 @@ def test_validate_url_with_docker_network_url(self): self.assertTrue(validate_url("http://test:8000/valid")) def test_validate_url_with_invalid_url(self): - assert isinstance(validate_url("htt://test:8000/valid"), ValidationError) + self.assertIsInstance(validate_url("htt://test:8000/valid"), ValidationError) + + +class TestIsIndexerError(unittest.TestCase): + def test_returns_true_for_graphql_response(self): + error = make_graphql_error( + {"errors": [{"message": "bad indexers: out of sync"}]} + ) + self.assertTrue(is_indexer_error(error)) + + def test_returns_true_for_message(self): + error = Exception("bad indexers: [...]") + self.assertTrue(is_indexer_error(error)) + + def test_returns_false_when_not_indexer_error(self): + error = Exception("some other issue") + self.assertFalse(is_indexer_error(error)) + + +class TestGetDataFromSubgraph(unittest.TestCase): + def setUp(self): + self.network = { + "subgraph_url": "http://subgraph", + "subgraph_url_api_key": "http://subgraph-with-key", + } + self.query = "query Test" + self.variables = {"foo": "bar"} + + def test_returns_response_without_retry_config(self): + expected = {"data": {"ok": True}} + with patch( + "human_protocol_sdk.utils._fetch_subgraph_data", + return_value=expected, + ) as mock_fetch: + result = get_data_from_subgraph(self.network, self.query, self.variables) + + self.assertEqual(result, expected) + mock_fetch.assert_called_once_with(self.network, self.query, self.variables) + + def test_retries_on_indexer_error_and_succeeds(self): + retry_config = SubgraphRetryConfig(max_retries=2, base_delay=100) + error = make_graphql_error({"errors": [{"message": "Bad indexers: syncing"}]}) + + with patch( + "human_protocol_sdk.utils._fetch_subgraph_data", + side_effect=[error, {"data": {"ok": True}}], + ) as mock_fetch, patch("human_protocol_sdk.utils.time.sleep") as mock_sleep: + result = get_data_from_subgraph( + self.network, self.query, self.variables, retry_config=retry_config + ) + + self.assertEqual(result, {"data": {"ok": True}}) + self.assertEqual(mock_fetch.call_count, 2) + mock_sleep.assert_called_once() + + def test_raises_immediately_on_non_indexer_error(self): + retry_config = SubgraphRetryConfig(max_retries=3, base_delay=50) + with patch( + "human_protocol_sdk.utils._fetch_subgraph_data", + side_effect=Exception("network failure"), + ) as mock_fetch, patch("human_protocol_sdk.utils.time.sleep") as mock_sleep: + with self.assertRaises(Exception) as ctx: + get_data_from_subgraph( + self.network, self.query, self.variables, retry_config=retry_config + ) + + self.assertIn("network failure", str(ctx.exception)) + mock_fetch.assert_called_once() + mock_sleep.assert_not_called() + + def test_raises_after_exhausting_retries(self): + retry_config = SubgraphRetryConfig(max_retries=2, base_delay=10) + errors = [ + make_graphql_error({"errors": [{"message": "bad indexers: stalled"}]}) + for _ in range(3) + ] + + with patch( + "human_protocol_sdk.utils._fetch_subgraph_data", + side_effect=errors, + ) as mock_fetch, patch("human_protocol_sdk.utils.time.sleep"): + with self.assertRaises(Exception) as ctx: + get_data_from_subgraph( + self.network, self.query, self.variables, retry_config=retry_config + ) + + self.assertTrue(is_indexer_error(ctx.exception)) + self.assertEqual(mock_fetch.call_count, 3) diff --git a/packages/sdk/python/human-protocol-sdk/test/human_protocol_sdk/transaction/test_transaction_utils.py b/packages/sdk/python/human-protocol-sdk/test/human_protocol_sdk/transaction/test_transaction_utils.py index aee4e3914f..4d93e9c9c9 100644 --- a/packages/sdk/python/human-protocol-sdk/test/human_protocol_sdk/transaction/test_transaction_utils.py +++ b/packages/sdk/python/human-protocol-sdk/test/human_protocol_sdk/transaction/test_transaction_utils.py @@ -82,6 +82,7 @@ def test_get_transactions(self): "token": None, "method": None, }, + retry_config=None, ) self.assertEqual(len(transactions), 2) self.assertEqual(transactions[0].chain_id, ChainId.POLYGON_AMOY) @@ -117,6 +118,7 @@ def test_get_transactions_empty_response(self): "token": None, "method": None, }, + retry_config=None, ) self.assertEqual(len(transactions), 0) @@ -181,6 +183,7 @@ def test_get_transaction(self): params={ "hash": "0x1234567890123456789012345678901234567890123456789012345678901234" }, + retry_config=None, ) self.assertIsNotNone(transaction) self.assertEqual(transaction.chain_id, ChainId.POLYGON_AMOY) @@ -209,6 +212,7 @@ def test_get_transaction_empty_data(self): NETWORKS[ChainId.POLYGON_AMOY], query=ANY, params={"hash": "transaction_hash"}, + retry_config=None, ) self.assertIsNone(transaction) diff --git a/packages/sdk/python/human-protocol-sdk/test/human_protocol_sdk/worker/test_worker_utils.py b/packages/sdk/python/human-protocol-sdk/test/human_protocol_sdk/worker/test_worker_utils.py index 4427af4998..6ae51ccbc7 100644 --- a/packages/sdk/python/human-protocol-sdk/test/human_protocol_sdk/worker/test_worker_utils.py +++ b/packages/sdk/python/human-protocol-sdk/test/human_protocol_sdk/worker/test_worker_utils.py @@ -47,6 +47,7 @@ def test_get_workers(self): "orderBy": "totalHMTAmountReceived", "orderDirection": "asc", }, + retry_config=None, ) self.assertEqual(len(workers), 2) self.assertEqual(workers[0].id, "worker1") @@ -75,6 +76,7 @@ def test_get_workers_empty_response(self): "orderBy": "payoutCount", "orderDirection": "desc", }, + retry_config=None, ) self.assertEqual(len(workers), 0) @@ -106,6 +108,7 @@ def test_get_worker(self): NETWORKS[ChainId.POLYGON_AMOY], query=get_worker_query(), params={"address": "0x1234567890123456789012345678901234567890"}, + retry_config=None, ) self.assertIsNotNone(worker) self.assertEqual(worker.id, "worker1") @@ -127,6 +130,7 @@ def test_get_worker_empty_data(self): NETWORKS[ChainId.POLYGON_AMOY], query=get_worker_query(), params={"address": "0x1234567890123456789012345678901234567890"}, + retry_config=None, ) self.assertIsNone(worker) diff --git a/packages/sdk/typescript/human-protocol-sdk/src/escrow.ts b/packages/sdk/typescript/human-protocol-sdk/src/escrow.ts index 93ec500127..a6ace7df2e 100644 --- a/packages/sdk/typescript/human-protocol-sdk/src/escrow.ts +++ b/packages/sdk/typescript/human-protocol-sdk/src/escrow.ts @@ -1942,6 +1942,7 @@ export class EscrowUtils { * * * @param {IEscrowsFilter} filter Filter parameters. + * @param {SubgraphRetryConfig} retryConfig Optional configuration for retrying subgraph requests. * @returns {IEscrow[]} List of escrows that match the filter. * * **Code example** @@ -1958,7 +1959,10 @@ export class EscrowUtils { * const escrows = await EscrowUtils.getEscrows(filters); * ``` */ - public static async getEscrows(filter: IEscrowsFilter): Promise { + public static async getEscrows( + filter: IEscrowsFilter, + retryConfig?: SubgraphRetryConfig + ): Promise { if (filter.launcher && !ethers.isAddress(filter.launcher)) { throw ErrorInvalidAddress; } @@ -2007,7 +2011,7 @@ export class EscrowUtils { first: first, skip: skip, }, - filter.retryConfig + retryConfig ); return (escrows || []).map((e) => mapEscrow(e, networkData.chainId)); } @@ -2065,6 +2069,7 @@ export class EscrowUtils { * * @param {ChainId} chainId Network in which the escrow has been deployed * @param {string} escrowAddress Address of the escrow + * @param {SubgraphRetryConfig} retryConfig Optional configuration for retrying subgraph requests. * @returns {Promise} - Escrow data or null if not found. * * **Code example** @@ -2137,6 +2142,7 @@ export class EscrowUtils { * ``` * * @param {IStatusEventFilter} filter Filter parameters. + * @param {SubgraphRetryConfig} retryConfig Optional configuration for retrying subgraph requests. * @returns {Promise} - Array of status events with their corresponding statuses. * * **Code example** @@ -2158,7 +2164,8 @@ export class EscrowUtils { * ``` */ public static async getStatusEvents( - filter: IStatusEventFilter + filter: IStatusEventFilter, + retryConfig?: SubgraphRetryConfig ): Promise { const { chainId, @@ -2206,7 +2213,7 @@ export class EscrowUtils { first: Math.min(first, 1000), skip, }, - filter.retryConfig + retryConfig ); if (!data || !data['escrowStatusEvents']) { @@ -2230,6 +2237,7 @@ export class EscrowUtils { * Fetch payouts from the subgraph. * * @param {IPayoutFilter} filter Filter parameters. + * @param {SubgraphRetryConfig} retryConfig Optional configuration for retrying subgraph requests. * @returns {Promise} List of payouts matching the filters. * * **Code example** @@ -2247,7 +2255,10 @@ export class EscrowUtils { * console.log(payouts); * ``` */ - public static async getPayouts(filter: IPayoutFilter): Promise { + public static async getPayouts( + filter: IPayoutFilter, + retryConfig?: SubgraphRetryConfig + ): Promise { const networkData = NETWORKS[filter.chainId]; if (!networkData) { throw ErrorUnsupportedChainID; @@ -2276,7 +2287,7 @@ export class EscrowUtils { skip, orderDirection, }, - filter.retryConfig + retryConfig ); if (!payouts) { return []; @@ -2325,6 +2336,7 @@ export class EscrowUtils { * * * @param {Object} filter Filter parameters. + * @param {SubgraphRetryConfig} retryConfig Optional configuration for retrying subgraph requests. * @returns {Promise} List of cancellation refunds matching the filters. * * **Code example** @@ -2340,7 +2352,8 @@ export class EscrowUtils { * ``` */ public static async getCancellationRefunds( - filter: ICancellationRefundFilter + filter: ICancellationRefundFilter, + retryConfig?: SubgraphRetryConfig ): Promise { const networkData = NETWORKS[filter.chainId]; if (!networkData) throw ErrorUnsupportedChainID; @@ -2370,7 +2383,7 @@ export class EscrowUtils { skip, orderDirection, }, - filter.retryConfig + retryConfig ); if (!cancellationRefundEvents || cancellationRefundEvents.length === 0) { @@ -2423,6 +2436,7 @@ export class EscrowUtils { * * @param {ChainId} chainId Network in which the escrow has been deployed * @param {string} escrowAddress Address of the escrow + * @param {SubgraphRetryConfig} retryConfig Optional configuration for retrying subgraph requests. * @returns {Promise} Cancellation refund data * * **Code example** diff --git a/packages/sdk/typescript/human-protocol-sdk/src/interfaces.ts b/packages/sdk/typescript/human-protocol-sdk/src/interfaces.ts index ccb7eb7006..3b1eddf78a 100644 --- a/packages/sdk/typescript/human-protocol-sdk/src/interfaces.ts +++ b/packages/sdk/typescript/human-protocol-sdk/src/interfaces.ts @@ -35,7 +35,6 @@ export interface IOperatorsFilter extends IPagination { roles?: string[]; minStakedAmount?: number; orderBy?: string; - retryConfig?: SubgraphRetryConfig; } export interface IReputationNetwork { @@ -82,7 +81,6 @@ export interface IEscrowsFilter extends IPagination { from?: Date; to?: Date; chainId: ChainId; - retryConfig?: SubgraphRetryConfig; } export interface IEscrowConfig { @@ -106,7 +104,6 @@ export interface IKeyPair { export interface IStatisticsFilter extends IPagination { from?: Date; to?: Date; - retryConfig?: SubgraphRetryConfig; } export interface IHMTHoldersParams extends IPagination { @@ -119,7 +116,6 @@ export interface IPayoutFilter extends IPagination { recipient?: string; from?: Date; to?: Date; - retryConfig?: SubgraphRetryConfig; } export interface IKVStore { @@ -162,7 +158,6 @@ export interface ITransactionsFilter extends IPagination { method?: string; escrow?: string; token?: string; - retryConfig?: SubgraphRetryConfig; } export interface IPagination { @@ -184,7 +179,6 @@ export interface IStatusEventFilter extends IPagination { from?: Date; to?: Date; launcher?: string; - retryConfig?: SubgraphRetryConfig; } export interface IWorker { @@ -198,7 +192,6 @@ export interface IWorkersFilter extends IPagination { chainId: ChainId; address?: string; orderBy?: string; - retryConfig?: SubgraphRetryConfig; } export interface IStaker { @@ -227,7 +220,6 @@ export interface IStakersFilter extends IPagination { | 'withdrawnAmount' | 'slashedAmount' | 'lastDepositTimestamp'; - retryConfig?: SubgraphRetryConfig; } export interface ICancellationRefundFilter extends IPagination { chainId: ChainId; @@ -235,7 +227,6 @@ export interface ICancellationRefundFilter extends IPagination { receiver?: string; from?: Date; to?: Date; - retryConfig?: SubgraphRetryConfig; } export interface IDailyEscrow { diff --git a/packages/sdk/typescript/human-protocol-sdk/src/kvstore.ts b/packages/sdk/typescript/human-protocol-sdk/src/kvstore.ts index 5cd18f8fb5..5ed525cf62 100644 --- a/packages/sdk/typescript/human-protocol-sdk/src/kvstore.ts +++ b/packages/sdk/typescript/human-protocol-sdk/src/kvstore.ts @@ -293,6 +293,7 @@ export class KVStoreClient extends BaseEthersClient { * * @param {string} address Address from which to get the key value. * @param {string} key Key to obtain the value. + * @param {SubgraphRetryConfig} retryConfig Optional configuration for retrying subgraph requests. * @returns {string} Value of the key. * * @@ -364,6 +365,7 @@ export class KVStoreUtils { * * @param {ChainId} chainId Network in which the KVStore is deployed * @param {string} address Address of the KVStore + * @param {SubgraphRetryConfig} retryConfig Optional configuration for retrying subgraph requests. * @returns {Promise} KVStore data * @throws {ErrorUnsupportedChainID} - Thrown if the network's chainId is not supported * @throws {ErrorInvalidAddress} - Thrown if the Address sent is invalid @@ -413,6 +415,7 @@ export class KVStoreUtils { * @param {ChainId} chainId Network in which the KVStore is deployed * @param {string} address Address from which to get the key value. * @param {string} key Key to obtain the value. + * @param {SubgraphRetryConfig} retryConfig Optional configuration for retrying subgraph requests. * @returns {Promise} Value of the key. * @throws {ErrorUnsupportedChainID} - Thrown if the network's chainId is not supported * @throws {ErrorInvalidAddress} - Thrown if the Address sent is invalid @@ -466,6 +469,7 @@ export class KVStoreUtils { * @param {ChainId} chainId Network in which the KVStore is deployed * @param {string} address Address from which to get the URL value. * @param {string} urlKey Configurable URL key. `url` by default. + * @param {SubgraphRetryConfig} retryConfig Optional configuration for retrying subgraph requests. * @returns {Promise} URL value for the given address if it exists, and the content is valid * * **Code example** @@ -483,7 +487,8 @@ export class KVStoreUtils { public static async getFileUrlAndVerifyHash( chainId: ChainId, address: string, - urlKey = 'url' + urlKey = 'url', + retryConfig?: SubgraphRetryConfig ): Promise { if (!ethers.isAddress(address)) throw ErrorInvalidAddress; const hashKey = urlKey + '_hash'; @@ -492,7 +497,7 @@ export class KVStoreUtils { hash = ''; try { - url = await this.get(chainId, address, urlKey); + url = await this.get(chainId, address, urlKey, retryConfig); } catch (e) { if (e instanceof Error) throw Error(`Failed to get URL: ${e.message}`); } @@ -542,12 +547,14 @@ export class KVStoreUtils { */ public static async getPublicKey( chainId: ChainId, - address: string + address: string, + retryConfig?: SubgraphRetryConfig ): Promise { const publicKeyUrl = await this.getFileUrlAndVerifyHash( chainId, address, - KVStoreKeys.publicKey + KVStoreKeys.publicKey, + retryConfig ); if (publicKeyUrl === '') { diff --git a/packages/sdk/typescript/human-protocol-sdk/src/operator.ts b/packages/sdk/typescript/human-protocol-sdk/src/operator.ts index cbca6e866a..10af374b72 100644 --- a/packages/sdk/typescript/human-protocol-sdk/src/operator.ts +++ b/packages/sdk/typescript/human-protocol-sdk/src/operator.ts @@ -1,6 +1,10 @@ /* eslint-disable @typescript-eslint/no-explicit-any */ -import gqlFetch from 'graphql-request'; -import { IOperator, IOperatorsFilter, IReward } from './interfaces'; +import { + IOperator, + IOperatorsFilter, + IReward, + SubgraphRetryConfig, +} from './interfaces'; import { GET_REWARD_ADDED_EVENTS_QUERY } from './graphql/queries/reward'; import { IOperatorSubgraph, @@ -18,7 +22,7 @@ import { ErrorInvalidStakerAddressProvided, ErrorUnsupportedChainID, } from './error'; -import { getSubgraphUrl } from './utils'; +import { getSubgraphUrl, gqlFetchWithRetry } from './utils'; import { ChainId, OrderDirection } from './enums'; import { NETWORKS } from './constants'; @@ -28,6 +32,7 @@ export class OperatorUtils { * * @param {ChainId} chainId Network in which the operator is deployed * @param {string} address Operator address. + * @param {SubgraphRetryConfig} retryConfig Optional configuration for retrying subgraph requests. * @returns {Promise} - Returns the operator details or null if not found. * * **Code example** @@ -40,7 +45,8 @@ export class OperatorUtils { */ public static async getOperator( chainId: ChainId, - address: string + address: string, + retryConfig?: SubgraphRetryConfig ): Promise { if (!ethers.isAddress(address)) { throw ErrorInvalidStakerAddressProvided; @@ -51,10 +57,11 @@ export class OperatorUtils { throw ErrorUnsupportedChainID; } - const { operator } = await gqlFetch<{ + const { operator } = await gqlFetchWithRetry<{ operator: IOperatorSubgraph; }>(getSubgraphUrl(networkData), GET_LEADER_QUERY, { address: address.toLowerCase(), + retryConfig, }); if (!operator) { @@ -68,6 +75,7 @@ export class OperatorUtils { * This function returns all the operator details of the protocol. * * @param {IOperatorsFilter} filter Filter for the operators. + * @param {SubgraphRetryConfig} retryConfig Optional configuration for retrying subgraph requests. * @returns {Promise} Returns an array with all the operator details. * * **Code example** @@ -82,7 +90,8 @@ export class OperatorUtils { * ``` */ public static async getOperators( - filter: IOperatorsFilter + filter: IOperatorsFilter, + retryConfig?: SubgraphRetryConfig ): Promise { const first = filter.first !== undefined && filter.first > 0 @@ -107,16 +116,21 @@ export class OperatorUtils { throw ErrorUnsupportedChainID; } - const { operators } = await gqlFetch<{ + const { operators } = await gqlFetchWithRetry<{ operators: IOperatorSubgraph[]; - }>(getSubgraphUrl(networkData), GET_LEADERS_QUERY(filter), { - minStakedAmount: filter?.minStakedAmount, - roles: filter?.roles, - orderBy: orderBy, - orderDirection: orderDirection, - first: first, - skip: skip, - }); + }>( + getSubgraphUrl(networkData), + GET_LEADERS_QUERY(filter), + { + minStakedAmount: filter?.minStakedAmount, + roles: filter?.roles, + orderBy: orderBy, + orderDirection: orderDirection, + first: first, + skip: skip, + }, + retryConfig + ); if (!operators) { return []; @@ -131,6 +145,7 @@ export class OperatorUtils { * @param {ChainId} chainId Network in which the reputation network is deployed * @param {string} address Address of the reputation oracle. * @param {string} [role] - (Optional) Role of the operator. + * @param {SubgraphRetryConfig} retryConfig Optional configuration for retrying subgraph requests. * @returns {Promise} - Returns an array of operator details. * * **Code example** @@ -144,19 +159,25 @@ export class OperatorUtils { public static async getReputationNetworkOperators( chainId: ChainId, address: string, - role?: string + role?: string, + retryConfig?: SubgraphRetryConfig ): Promise { const networkData = NETWORKS[chainId]; if (!networkData) { throw ErrorUnsupportedChainID; } - const { reputationNetwork } = await gqlFetch<{ + const { reputationNetwork } = await gqlFetchWithRetry<{ reputationNetwork: IReputationNetworkSubgraph; - }>(getSubgraphUrl(networkData), GET_REPUTATION_NETWORK_QUERY(role), { - address: address.toLowerCase(), - role: role, - }); + }>( + getSubgraphUrl(networkData), + GET_REPUTATION_NETWORK_QUERY(role), + { + address: address.toLowerCase(), + role: role, + }, + retryConfig + ); if (!reputationNetwork) return []; @@ -170,6 +191,7 @@ export class OperatorUtils { * * @param {ChainId} chainId Network in which the rewards are deployed * @param {string} slasherAddress Slasher address. + * @param {SubgraphRetryConfig} retryConfig Optional configuration for retrying subgraph requests. * @returns {Promise} Returns an array of Reward objects that contain the rewards earned by the user through slashing other users. * * **Code example** @@ -182,7 +204,8 @@ export class OperatorUtils { */ public static async getRewards( chainId: ChainId, - slasherAddress: string + slasherAddress: string, + retryConfig?: SubgraphRetryConfig ): Promise { if (!ethers.isAddress(slasherAddress)) { throw ErrorInvalidSlasherAddressProvided; @@ -193,11 +216,16 @@ export class OperatorUtils { throw ErrorUnsupportedChainID; } - const { rewardAddedEvents } = await gqlFetch<{ + const { rewardAddedEvents } = await gqlFetchWithRetry<{ rewardAddedEvents: RewardAddedEventData[]; - }>(getSubgraphUrl(networkData), GET_REWARD_ADDED_EVENTS_QUERY, { - slasherAddress: slasherAddress.toLowerCase(), - }); + }>( + getSubgraphUrl(networkData), + GET_REWARD_ADDED_EVENTS_QUERY, + { + slasherAddress: slasherAddress.toLowerCase(), + }, + retryConfig + ); if (!rewardAddedEvents) return []; diff --git a/packages/sdk/typescript/human-protocol-sdk/src/staking.ts b/packages/sdk/typescript/human-protocol-sdk/src/staking.ts index 8a7d2465f6..6eae68f54d 100644 --- a/packages/sdk/typescript/human-protocol-sdk/src/staking.ts +++ b/packages/sdk/typescript/human-protocol-sdk/src/staking.ts @@ -499,6 +499,7 @@ export class StakingUtils { * * @param {ChainId} chainId Network in which the staking contract is deployed * @param {string} stakerAddress Address of the staker + * @param {SubgraphRetryConfig} retryConfig Optional configuration for retrying subgraph requests. * @returns {Promise} Staker info from subgraph */ public static async getStaker( @@ -532,9 +533,14 @@ export class StakingUtils { /** * Gets all stakers from the subgraph with filters, pagination and ordering. * + * @param {IStakersFilter} filter Stakers filter with pagination and ordering + * @param {SubgraphRetryConfig} retryConfig Optional configuration for retrying subgraph requests. * @returns {Promise} Array of stakers */ - public static async getStakers(filter: IStakersFilter): Promise { + public static async getStakers( + filter: IStakersFilter, + retryConfig?: SubgraphRetryConfig + ): Promise { const first = filter.first !== undefined ? Math.min(filter.first, 1000) : 10; const skip = filter.skip || 0; @@ -579,7 +585,7 @@ export class StakingUtils { first: first, skip: skip, }, - filter.retryConfig + retryConfig ); if (!stakers) { return []; diff --git a/packages/sdk/typescript/human-protocol-sdk/src/statistics.ts b/packages/sdk/typescript/human-protocol-sdk/src/statistics.ts index 715c094621..046e8f1127 100644 --- a/packages/sdk/typescript/human-protocol-sdk/src/statistics.ts +++ b/packages/sdk/typescript/human-protocol-sdk/src/statistics.ts @@ -1,6 +1,4 @@ /* eslint-disable @typescript-eslint/no-explicit-any */ -import gqlFetch from 'graphql-request'; - import { OrderDirection } from './enums'; import { EscrowStatisticsData, @@ -21,9 +19,15 @@ import { IPaymentStatistics, IStatisticsFilter, IWorkerStatistics, + SubgraphRetryConfig, } from './interfaces'; import { NetworkData } from './types'; -import { getSubgraphUrl, getUnixTimestamp, throwError } from './utils'; +import { + getSubgraphUrl, + getUnixTimestamp, + gqlFetchWithRetry, + throwError, +} from './utils'; /** * ## Introduction @@ -103,6 +107,7 @@ export class StatisticsClient { * ``` * * @param {IStatisticsFilter} filter Statistics params with duration data + * @param {SubgraphRetryConfig} retryConfig Optional configuration for retrying subgraph requests. * @returns {Promise} Escrow statistics data. * * **Code example** @@ -120,7 +125,8 @@ export class StatisticsClient { * ``` */ async getEscrowStatistics( - filter: IStatisticsFilter = {} + filter: IStatisticsFilter = {}, + retryConfig?: SubgraphRetryConfig ): Promise { try { const first = @@ -128,19 +134,24 @@ export class StatisticsClient { const skip = filter.skip || 0; const orderDirection = filter.orderDirection || OrderDirection.ASC; - const { escrowStatistics } = await gqlFetch<{ + const { escrowStatistics } = await gqlFetchWithRetry<{ escrowStatistics: EscrowStatisticsData; - }>(this.subgraphUrl, GET_ESCROW_STATISTICS_QUERY); + }>(this.subgraphUrl, GET_ESCROW_STATISTICS_QUERY, retryConfig); - const { eventDayDatas } = await gqlFetch<{ + const { eventDayDatas } = await gqlFetchWithRetry<{ eventDayDatas: EventDayData[]; - }>(this.subgraphUrl, GET_EVENT_DAY_DATA_QUERY(filter), { - from: filter.from ? getUnixTimestamp(filter.from) : undefined, - to: filter.to ? getUnixTimestamp(filter.to) : undefined, - orderDirection: orderDirection, - first: first, - skip: skip, - }); + }>( + this.subgraphUrl, + GET_EVENT_DAY_DATA_QUERY(filter), + { + from: filter.from ? getUnixTimestamp(filter.from) : undefined, + to: filter.to ? getUnixTimestamp(filter.to) : undefined, + orderDirection: orderDirection, + first: first, + skip: skip, + }, + retryConfig + ); return { totalEscrows: escrowStatistics?.totalEscrowCount @@ -187,6 +198,7 @@ export class StatisticsClient { * ``` * * @param {IStatisticsFilter} filter Statistics params with duration data + * @param {SubgraphRetryConfig} retryConfig Optional configuration for retrying subgraph requests. * @returns {Promise} Worker statistics data. * * **Code example** @@ -204,7 +216,8 @@ export class StatisticsClient { * ``` */ async getWorkerStatistics( - filter: IStatisticsFilter = {} + filter: IStatisticsFilter = {}, + retryConfig?: SubgraphRetryConfig ): Promise { try { const first = @@ -212,15 +225,20 @@ export class StatisticsClient { const skip = filter.skip || 0; const orderDirection = filter.orderDirection || OrderDirection.ASC; - const { eventDayDatas } = await gqlFetch<{ + const { eventDayDatas } = await gqlFetchWithRetry<{ eventDayDatas: EventDayData[]; - }>(this.subgraphUrl, GET_EVENT_DAY_DATA_QUERY(filter), { - from: filter.from ? getUnixTimestamp(filter.from) : undefined, - to: filter.to ? getUnixTimestamp(filter.to) : undefined, - orderDirection: orderDirection, - first: first, - skip: skip, - }); + }>( + this.subgraphUrl, + GET_EVENT_DAY_DATA_QUERY(filter), + { + from: filter.from ? getUnixTimestamp(filter.from) : undefined, + to: filter.to ? getUnixTimestamp(filter.to) : undefined, + orderDirection: orderDirection, + first: first, + skip: skip, + }, + retryConfig + ); return { dailyWorkersData: eventDayDatas.map((eventDayData) => ({ @@ -262,6 +280,7 @@ export class StatisticsClient { * ``` * * @param {IStatisticsFilter} filter Statistics params with duration data + * @param {SubgraphRetryConfig} retryConfig Optional configuration for retrying subgraph requests. * @returns {Promise} Payment statistics data. * * **Code example** @@ -300,7 +319,8 @@ export class StatisticsClient { * ``` */ async getPaymentStatistics( - filter: IStatisticsFilter = {} + filter: IStatisticsFilter = {}, + retryConfig?: SubgraphRetryConfig ): Promise { try { const first = @@ -308,15 +328,20 @@ export class StatisticsClient { const skip = filter.skip || 0; const orderDirection = filter.orderDirection || OrderDirection.ASC; - const { eventDayDatas } = await gqlFetch<{ + const { eventDayDatas } = await gqlFetchWithRetry<{ eventDayDatas: EventDayData[]; - }>(this.subgraphUrl, GET_EVENT_DAY_DATA_QUERY(filter), { - from: filter.from ? getUnixTimestamp(filter.from) : undefined, - to: filter.to ? getUnixTimestamp(filter.to) : undefined, - orderDirection: orderDirection, - first: first, - skip: skip, - }); + }>( + this.subgraphUrl, + GET_EVENT_DAY_DATA_QUERY(filter), + { + from: filter.from ? getUnixTimestamp(filter.from) : undefined, + to: filter.to ? getUnixTimestamp(filter.to) : undefined, + orderDirection: orderDirection, + first: first, + skip: skip, + }, + retryConfig + ); return { dailyPaymentsData: eventDayDatas.map((eventDayData) => ({ @@ -346,6 +371,7 @@ export class StatisticsClient { * }; * ``` * + * @param {SubgraphRetryConfig} retryConfig Optional configuration for retrying subgraph requests. * @returns {Promise} HMToken statistics data. * * **Code example** @@ -363,11 +389,13 @@ export class StatisticsClient { * }); * ``` */ - async getHMTStatistics(): Promise { + async getHMTStatistics( + retryConfig?: SubgraphRetryConfig + ): Promise { try { - const { hmtokenStatistics } = await gqlFetch<{ + const { hmtokenStatistics } = await gqlFetchWithRetry<{ hmtokenStatistics: HMTStatisticsData; - }>(this.subgraphUrl, GET_HMTOKEN_STATISTICS_QUERY); + }>(this.subgraphUrl, GET_HMTOKEN_STATISTICS_QUERY, retryConfig); return { totalTransferAmount: BigInt(hmtokenStatistics.totalValueTransfered), @@ -385,6 +413,7 @@ export class StatisticsClient { * **Input parameters** * * @param {IHMTHoldersParams} params HMT Holders params with filters and ordering + * @param {SubgraphRetryConfig} retryConfig Optional configuration for retrying subgraph requests. * @returns {Promise} List of HMToken holders. * * **Code example** @@ -404,19 +433,23 @@ export class StatisticsClient { * }))); * ``` */ - async getHMTHolders(params: IHMTHoldersParams = {}): Promise { + async getHMTHolders( + params: IHMTHoldersParams = {}, + retryConfig?: SubgraphRetryConfig + ): Promise { try { const { address, orderDirection } = params; const query = GET_HOLDERS_QUERY(address); - const { holders } = await gqlFetch<{ holders: HMTHolderData[] }>( + const { holders } = await gqlFetchWithRetry<{ holders: HMTHolderData[] }>( this.subgraphUrl, query, { address, orderBy: 'balance', orderDirection, - } + }, + retryConfig ); return holders.map((holder) => ({ @@ -454,6 +487,7 @@ export class StatisticsClient { * ``` * * @param {IStatisticsFilter} filter Statistics params with duration data + * @param {SubgraphRetryConfig} retryConfig Optional configuration for retrying subgraph requests. * @returns {Promise} Daily HMToken statistics data. * * **Code example** @@ -475,22 +509,30 @@ export class StatisticsClient { * console.log('HMT statistics from 5/8 - 6/8:', hmtStatisticsRange); * ``` */ - async getHMTDailyData(filter: IStatisticsFilter = {}): Promise { + async getHMTDailyData( + filter: IStatisticsFilter = {}, + retryConfig?: SubgraphRetryConfig + ): Promise { try { const first = filter.first !== undefined ? Math.min(filter.first, 1000) : 10; const skip = filter.skip || 0; const orderDirection = filter.orderDirection || OrderDirection.ASC; - const { eventDayDatas } = await gqlFetch<{ + const { eventDayDatas } = await gqlFetchWithRetry<{ eventDayDatas: EventDayData[]; - }>(this.subgraphUrl, GET_EVENT_DAY_DATA_QUERY(filter), { - from: filter.from ? getUnixTimestamp(filter.from) : undefined, - to: filter.to ? getUnixTimestamp(filter.to) : undefined, - orderDirection: orderDirection, - first: first, - skip: skip, - }); + }>( + this.subgraphUrl, + GET_EVENT_DAY_DATA_QUERY(filter), + { + from: filter.from ? getUnixTimestamp(filter.from) : undefined, + to: filter.to ? getUnixTimestamp(filter.to) : undefined, + orderDirection: orderDirection, + first: first, + skip: skip, + }, + retryConfig + ); return eventDayDatas.map((eventDayData) => ({ timestamp: +eventDayData.timestamp * 1000, diff --git a/packages/sdk/typescript/human-protocol-sdk/src/transaction.ts b/packages/sdk/typescript/human-protocol-sdk/src/transaction.ts index 81124a6d83..ef128e300a 100644 --- a/packages/sdk/typescript/human-protocol-sdk/src/transaction.ts +++ b/packages/sdk/typescript/human-protocol-sdk/src/transaction.ts @@ -54,6 +54,7 @@ export class TransactionUtils { * * @param {ChainId} chainId The chain ID. * @param {string} hash The transaction hash. + * @param {SubgraphRetryConfig} retryConfig Optional configuration for retrying subgraph requests. * @returns {Promise} - Returns the transaction details or null if not found. * * **Code example** @@ -147,6 +148,7 @@ export class TransactionUtils { * ``` * * @param {ITransactionsFilter} filter Filter for the transactions. + * @param {SubgraphRetryConfig} retryConfig Optional configuration for retrying subgraph requests. * @returns {Promise} Returns an array with all the transaction details. * * **Code example** @@ -166,7 +168,8 @@ export class TransactionUtils { * ``` */ public static async getTransactions( - filter: ITransactionsFilter + filter: ITransactionsFilter, + retryConfig?: SubgraphRetryConfig ): Promise { if ( (!!filter.startDate || !!filter.endDate) && @@ -206,7 +209,7 @@ export class TransactionUtils { first: first, skip: skip, }, - filter.retryConfig + retryConfig ); if (!transactions) { diff --git a/packages/sdk/typescript/human-protocol-sdk/src/worker.ts b/packages/sdk/typescript/human-protocol-sdk/src/worker.ts index 89b91320f6..4483a792ec 100644 --- a/packages/sdk/typescript/human-protocol-sdk/src/worker.ts +++ b/packages/sdk/typescript/human-protocol-sdk/src/worker.ts @@ -13,6 +13,7 @@ export class WorkerUtils { * * @param {ChainId} chainId The chain ID. * @param {string} address The worker address. + * @param {SubgraphRetryConfig} retryConfig Optional configuration for retrying subgraph requests. * @returns {Promise} - Returns the worker details or null if not found. * * **Code example** @@ -79,6 +80,7 @@ export class WorkerUtils { * ``` * * @param {IWorkersFilter} filter Filter for the workers. + * @param {SubgraphRetryConfig} retryConfig Optional configuration for retrying subgraph requests. * @returns {Promise} Returns an array with all the worker details. * * **Code example** @@ -94,7 +96,10 @@ export class WorkerUtils { * const workers = await WorkerUtils.getWorkers(filter); * ``` */ - public static async getWorkers(filter: IWorkersFilter): Promise { + public static async getWorkers( + filter: IWorkersFilter, + retryConfig?: SubgraphRetryConfig + ): Promise { const first = filter.first !== undefined ? Math.min(filter.first, 1000) : 10; const skip = filter.skip || 0; @@ -121,7 +126,7 @@ export class WorkerUtils { orderBy: orderBy, orderDirection: orderDirection, }, - filter.retryConfig + retryConfig ); if (!workers) { diff --git a/packages/sdk/typescript/human-protocol-sdk/test/kvstore.test.ts b/packages/sdk/typescript/human-protocol-sdk/test/kvstore.test.ts index 80571d29ab..c918e03333 100644 --- a/packages/sdk/typescript/human-protocol-sdk/test/kvstore.test.ts +++ b/packages/sdk/typescript/human-protocol-sdk/test/kvstore.test.ts @@ -560,7 +560,8 @@ describe('KVStoreUtils', () => { expect(KVStoreUtils.get).toHaveBeenCalledWith( ChainId.LOCALHOST, '0x42d75a16b04a02d1abd7f2386b1c5b567bc7ef71', - 'url' + 'url', + undefined ); }); @@ -581,7 +582,8 @@ describe('KVStoreUtils', () => { expect(KVStoreUtils.get).toHaveBeenCalledWith( ChainId.LOCALHOST, '0x42d75a16b04a02d1abd7f2386b1c5b567bc7ef71', - 'url' + 'url', + undefined ); expect(KVStoreUtils.get).toHaveBeenCalledWith( ChainId.LOCALHOST, @@ -608,7 +610,8 @@ describe('KVStoreUtils', () => { expect(KVStoreUtils.get).toHaveBeenCalledWith( ChainId.LOCALHOST, '0x42d75a16b04a02d1abd7f2386b1c5b567bc7ef71', - 'linkedin_url' + 'linkedin_url', + undefined ); expect(KVStoreUtils.get).toHaveBeenCalledWith( ChainId.LOCALHOST, @@ -635,7 +638,8 @@ describe('KVStoreUtils', () => { expect(KVStoreUtils.get).toHaveBeenCalledWith( ChainId.LOCALHOST, '0x42d75a16b04a02d1abd7f2386b1c5b567bc7ef71', - 'url' + 'url', + undefined ); expect(KVStoreUtils.get).toHaveBeenCalledWith( ChainId.LOCALHOST, @@ -659,7 +663,8 @@ describe('KVStoreUtils', () => { expect(KVStoreUtils.get).toHaveBeenCalledWith( ChainId.LOCALHOST, '0x42d75a16b04a02d1abd7f2386b1c5b567bc7ef71', - 'url' + 'url', + undefined ); }); }); @@ -682,7 +687,8 @@ describe('KVStoreUtils', () => { expect(KVStoreUtils.get).toHaveBeenCalledWith( ChainId.LOCALHOST, '0x42d75a16b04a02d1abd7f2386b1c5b567bc7ef71', - 'public_key' + 'public_key', + undefined ); }); @@ -703,7 +709,8 @@ describe('KVStoreUtils', () => { expect(KVStoreUtils.get).toHaveBeenCalledWith( ChainId.LOCALHOST, '0x42d75a16b04a02d1abd7f2386b1c5b567bc7ef71', - 'public_key' + 'public_key', + undefined ); expect(KVStoreUtils.get).toHaveBeenCalledWith( ChainId.LOCALHOST, @@ -732,7 +739,8 @@ describe('KVStoreUtils', () => { expect(KVStoreUtils.get).toHaveBeenCalledWith( ChainId.LOCALHOST, '0x42d75a16b04a02d1abd7f2386b1c5b567bc7ef71', - 'public_key' + 'public_key', + undefined ); expect(KVStoreUtils.get).toHaveBeenCalledWith( ChainId.LOCALHOST, @@ -756,7 +764,8 @@ describe('KVStoreUtils', () => { expect(KVStoreUtils.get).toHaveBeenCalledWith( ChainId.LOCALHOST, '0x42d75a16b04a02d1abd7f2386b1c5b567bc7ef71', - 'public_key' + 'public_key', + undefined ); }); }); diff --git a/packages/sdk/typescript/human-protocol-sdk/test/statistics.test.ts b/packages/sdk/typescript/human-protocol-sdk/test/statistics.test.ts index 8d6a741df2..0f36f8449b 100644 --- a/packages/sdk/typescript/human-protocol-sdk/test/statistics.test.ts +++ b/packages/sdk/typescript/human-protocol-sdk/test/statistics.test.ts @@ -62,7 +62,8 @@ describe('StatisticsClient', () => { expect(gqlFetchSpy).toHaveBeenCalledWith( 'https://api.studio.thegraph.com/query/74256/polygon/version/latest', - GET_ESCROW_STATISTICS_QUERY + GET_ESCROW_STATISTICS_QUERY, + undefined ); expect(gqlFetchSpy).toHaveBeenCalledWith( 'https://api.studio.thegraph.com/query/74256/polygon/version/latest', From 6a9e6b6fe41164fe44b265819e45373e4dc62962 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Francisco=20L=C3=B3pez?= Date: Mon, 10 Nov 2025 10:57:33 +0100 Subject: [PATCH 3/7] - Replaced gqlFetchWithRetry with customGqlFetch in TypeScript and Python SDKs to handle subgraph requests with retry logic for bad indexer errors. - Updated interfaces to rename SubgraphRetryConfig to SubgraphOptions for better clarity. - Generated changeset for new changes --- .changeset/wide-sites-pull.md | 6 ++ .../human_protocol_sdk/escrow/escrow_utils.py | 14 ++-- .../kvstore/kvstore_utils.py | 6 +- .../operator/operator_utils.py | 10 +-- .../staking/staking_utils.py | 6 +- .../statistics/statistics_client.py | 16 ++--- .../transaction/transaction_utils.py | 6 +- .../human_protocol_sdk/utils.py | 2 +- .../human_protocol_sdk/worker/worker_utils.py | 6 +- .../escrow/test_escrow_utils.py | 64 +++++++++---------- .../kvstore/test_kvstore_utils.py | 34 +++++----- .../operator/test_operator_utils.py | 28 ++++---- .../staking/test_staking_utils.py | 8 +-- .../statistics/test_statistics_client.py | 12 ++-- .../test/human_protocol_sdk/test_utils.py | 10 +-- .../transaction/test_transaction_utils.py | 8 +-- .../worker/test_worker_utils.py | 8 +-- .../human-protocol-sdk/src/escrow.ts | 52 +++++++-------- .../human-protocol-sdk/src/interfaces.ts | 2 +- .../human-protocol-sdk/src/kvstore.ts | 32 +++++----- .../human-protocol-sdk/src/operator.ts | 36 +++++------ .../human-protocol-sdk/src/staking.ts | 20 +++--- .../human-protocol-sdk/src/statistics.ts | 58 ++++++++--------- .../human-protocol-sdk/src/transaction.ts | 20 +++--- .../human-protocol-sdk/src/utils.ts | 14 ++-- .../human-protocol-sdk/src/worker.ts | 20 +++--- .../human-protocol-sdk/test/utils.test.ts | 18 +++--- 27 files changed, 260 insertions(+), 256 deletions(-) create mode 100644 .changeset/wide-sites-pull.md diff --git a/.changeset/wide-sites-pull.md b/.changeset/wide-sites-pull.md new file mode 100644 index 0000000000..5c38cdad12 --- /dev/null +++ b/.changeset/wide-sites-pull.md @@ -0,0 +1,6 @@ +--- +"@human-protocol/sdk": minor +"@human-protocol/python-sdk": minor +--- + +Added new optional config for querying subgraph with retries when failuers are due to bad indexers errors in Python and Typescript SDK diff --git a/packages/sdk/python/human-protocol-sdk/human_protocol_sdk/escrow/escrow_utils.py b/packages/sdk/python/human-protocol-sdk/human_protocol_sdk/escrow/escrow_utils.py index ab47ead27e..00cd379475 100644 --- a/packages/sdk/python/human-protocol-sdk/human_protocol_sdk/escrow/escrow_utils.py +++ b/packages/sdk/python/human-protocol-sdk/human_protocol_sdk/escrow/escrow_utils.py @@ -39,7 +39,7 @@ ) from human_protocol_sdk.utils import ( SubgraphRetryConfig, - get_data_from_subgraph, + custom_gql_fetch, ) from human_protocol_sdk.escrow.escrow_client import EscrowClientError @@ -260,7 +260,7 @@ def get_escrows( else: statuses = [filter.status.name] - escrows_data = get_data_from_subgraph( + escrows_data = custom_gql_fetch( network, query=get_escrows_query(filter), params={ @@ -373,7 +373,7 @@ def get_escrow( network = NETWORKS[ChainId(chain_id)] - escrow_data = get_data_from_subgraph( + escrow_data = custom_gql_fetch( network, query=get_escrow_query(), params={ @@ -446,7 +446,7 @@ def get_status_events( status_names = [status.name for status in filter.statuses] - data = get_data_from_subgraph( + data = custom_gql_fetch( network, get_status_query(filter.date_from, filter.date_to, filter.launcher), { @@ -510,7 +510,7 @@ def get_payouts( if not network: raise EscrowClientError("Unsupported Chain ID") - data = get_data_from_subgraph( + data = custom_gql_fetch( network, get_payouts_query(filter), { @@ -577,7 +577,7 @@ def get_cancellation_refunds( if not network: raise EscrowClientError("Unsupported Chain ID") - data = get_data_from_subgraph( + data = custom_gql_fetch( network, get_cancellation_refunds_query(filter), { @@ -658,7 +658,7 @@ def get_cancellation_refund( if not network: raise EscrowClientError("Unsupported Chain ID") - data = get_data_from_subgraph( + data = custom_gql_fetch( network, get_cancellation_refund_by_escrow_query(), { diff --git a/packages/sdk/python/human-protocol-sdk/human_protocol_sdk/kvstore/kvstore_utils.py b/packages/sdk/python/human-protocol-sdk/human_protocol_sdk/kvstore/kvstore_utils.py index 013d0dfd28..b4e12001d6 100644 --- a/packages/sdk/python/human-protocol-sdk/human_protocol_sdk/kvstore/kvstore_utils.py +++ b/packages/sdk/python/human-protocol-sdk/human_protocol_sdk/kvstore/kvstore_utils.py @@ -29,7 +29,7 @@ import requests from human_protocol_sdk.constants import NETWORKS, ChainId, KVStoreKeys -from human_protocol_sdk.utils import SubgraphRetryConfig, get_data_from_subgraph +from human_protocol_sdk.utils import SubgraphRetryConfig, custom_gql_fetch from human_protocol_sdk.kvstore.kvstore_client import KVStoreClientError @@ -90,7 +90,7 @@ def get_kvstore_data( network = NETWORKS[ChainId(chain_id)] - kvstore_data = get_data_from_subgraph( + kvstore_data = custom_gql_fetch( network, query=get_kvstore_by_address_query(), params={ @@ -151,7 +151,7 @@ def get( network = NETWORKS[ChainId(chain_id)] - kvstore_data = get_data_from_subgraph( + kvstore_data = custom_gql_fetch( network, query=get_kvstore_by_address_and_key_query(), params={ diff --git a/packages/sdk/python/human-protocol-sdk/human_protocol_sdk/operator/operator_utils.py b/packages/sdk/python/human-protocol-sdk/human_protocol_sdk/operator/operator_utils.py index 8d922ef890..429625bbfe 100644 --- a/packages/sdk/python/human-protocol-sdk/human_protocol_sdk/operator/operator_utils.py +++ b/packages/sdk/python/human-protocol-sdk/human_protocol_sdk/operator/operator_utils.py @@ -25,7 +25,7 @@ from human_protocol_sdk.constants import NETWORKS, ChainId, OrderDirection from human_protocol_sdk.gql.reward import get_reward_added_events_query -from human_protocol_sdk.utils import SubgraphRetryConfig, get_data_from_subgraph +from human_protocol_sdk.utils import SubgraphRetryConfig, custom_gql_fetch from web3 import Web3 LOG = logging.getLogger("human_protocol_sdk.operator") @@ -230,7 +230,7 @@ def get_operators( if not network.get("subgraph_url"): return [] - operators_data = get_data_from_subgraph( + operators_data = custom_gql_fetch( network, query=get_operators_query(filter), params={ @@ -321,7 +321,7 @@ def get_operator( network = NETWORKS[chain_id] - operator_data = get_data_from_subgraph( + operator_data = custom_gql_fetch( network, query=get_operator_query, params={"address": operator_address.lower()}, @@ -401,7 +401,7 @@ def get_reputation_network_operators( network = NETWORKS[chain_id] - reputation_network_data = get_data_from_subgraph( + reputation_network_data = custom_gql_fetch( network, query=get_reputation_network_query(role), params={"address": address.lower(), "role": role}, @@ -483,7 +483,7 @@ def get_rewards_info( network = NETWORKS[chain_id] - reward_added_events_data = get_data_from_subgraph( + reward_added_events_data = custom_gql_fetch( network, query=get_reward_added_events_query, params={"slasherAddress": slasher.lower()}, diff --git a/packages/sdk/python/human-protocol-sdk/human_protocol_sdk/staking/staking_utils.py b/packages/sdk/python/human-protocol-sdk/human_protocol_sdk/staking/staking_utils.py index d29ea52869..09bc34c864 100644 --- a/packages/sdk/python/human-protocol-sdk/human_protocol_sdk/staking/staking_utils.py +++ b/packages/sdk/python/human-protocol-sdk/human_protocol_sdk/staking/staking_utils.py @@ -30,7 +30,7 @@ from typing import List, Optional from human_protocol_sdk.constants import NETWORKS, ChainId from human_protocol_sdk.filter import StakersFilter -from human_protocol_sdk.utils import SubgraphRetryConfig, get_data_from_subgraph +from human_protocol_sdk.utils import SubgraphRetryConfig, custom_gql_fetch from human_protocol_sdk.gql.staking import get_staker_query, get_stakers_query @@ -71,7 +71,7 @@ def get_staker( if not network: raise StakingUtilsError("Unsupported Chain ID") - data = get_data_from_subgraph( + data = custom_gql_fetch( network, query=get_staker_query(), params={"id": address.lower()}, @@ -106,7 +106,7 @@ def get_stakers( if not network_data: raise StakingUtilsError("Unsupported Chain ID") - data = get_data_from_subgraph( + data = custom_gql_fetch( network_data, query=get_stakers_query(filter), params={ diff --git a/packages/sdk/python/human-protocol-sdk/human_protocol_sdk/statistics/statistics_client.py b/packages/sdk/python/human-protocol-sdk/human_protocol_sdk/statistics/statistics_client.py index 1c785e973f..e251120303 100644 --- a/packages/sdk/python/human-protocol-sdk/human_protocol_sdk/statistics/statistics_client.py +++ b/packages/sdk/python/human-protocol-sdk/human_protocol_sdk/statistics/statistics_client.py @@ -22,7 +22,7 @@ from human_protocol_sdk.constants import ChainId, NETWORKS -from human_protocol_sdk.utils import SubgraphRetryConfig, get_data_from_subgraph +from human_protocol_sdk.utils import SubgraphRetryConfig, custom_gql_fetch from human_protocol_sdk.filter import StatisticsFilter LOG = logging.getLogger("human_protocol_sdk.statistics") @@ -326,14 +326,14 @@ def get_escrow_statistics( get_escrow_statistics_query, ) - escrow_statistics_data = get_data_from_subgraph( + escrow_statistics_data = custom_gql_fetch( self.network, query=get_escrow_statistics_query, retry_config=retry_config, ) escrow_statistics = escrow_statistics_data["data"]["escrowStatistics"] - event_day_datas_data = get_data_from_subgraph( + event_day_datas_data = custom_gql_fetch( self.network, query=get_event_day_data_query(filter), params={ @@ -407,7 +407,7 @@ def get_worker_statistics( get_event_day_data_query, ) - event_day_datas_data = get_data_from_subgraph( + event_day_datas_data = custom_gql_fetch( self.network, query=get_event_day_data_query(filter), params={ @@ -469,7 +469,7 @@ def get_payment_statistics( get_event_day_data_query, ) - event_day_datas_data = get_data_from_subgraph( + event_day_datas_data = custom_gql_fetch( self.network, query=get_event_day_data_query(filter), params={ @@ -528,7 +528,7 @@ def get_hmt_statistics( get_hmtoken_statistics_query, ) - hmtoken_statistics_data = get_data_from_subgraph( + hmtoken_statistics_data = custom_gql_fetch( self.network, query=get_hmtoken_statistics_query, retry_config=retry_config, @@ -577,7 +577,7 @@ def get_hmt_holders( """ from human_protocol_sdk.gql.hmtoken import get_holders_query - holders_data = get_data_from_subgraph( + holders_data = custom_gql_fetch( self.network, query=get_holders_query(address=param.address), params={ @@ -632,7 +632,7 @@ def get_hmt_daily_data( get_event_day_data_query, ) - event_day_datas_data = get_data_from_subgraph( + event_day_datas_data = custom_gql_fetch( self.network, query=get_event_day_data_query(filter), params={ diff --git a/packages/sdk/python/human-protocol-sdk/human_protocol_sdk/transaction/transaction_utils.py b/packages/sdk/python/human-protocol-sdk/human_protocol_sdk/transaction/transaction_utils.py index 500320a20d..14eb644f74 100644 --- a/packages/sdk/python/human-protocol-sdk/human_protocol_sdk/transaction/transaction_utils.py +++ b/packages/sdk/python/human-protocol-sdk/human_protocol_sdk/transaction/transaction_utils.py @@ -30,7 +30,7 @@ from human_protocol_sdk.constants import NETWORKS, ChainId from web3 import Web3 from human_protocol_sdk.filter import TransactionFilter -from human_protocol_sdk.utils import SubgraphRetryConfig, get_data_from_subgraph +from human_protocol_sdk.utils import SubgraphRetryConfig, custom_gql_fetch class InternalTransaction: @@ -127,7 +127,7 @@ def get_transaction( from human_protocol_sdk.gql.transaction import get_transaction_query - transaction_data = get_data_from_subgraph( + transaction_data = custom_gql_fetch( network, query=get_transaction_query(), params={"hash": hash.lower()}, @@ -207,7 +207,7 @@ def get_transactions( if not network_data: raise TransactionUtilsError("Unsupported Chain ID") - data = get_data_from_subgraph( + data = custom_gql_fetch( network_data, query=get_transactions_query(filter), params={ diff --git a/packages/sdk/python/human-protocol-sdk/human_protocol_sdk/utils.py b/packages/sdk/python/human-protocol-sdk/human_protocol_sdk/utils.py index 0d72db2e0b..cd0ebaaae7 100644 --- a/packages/sdk/python/human-protocol-sdk/human_protocol_sdk/utils.py +++ b/packages/sdk/python/human-protocol-sdk/human_protocol_sdk/utils.py @@ -74,7 +74,7 @@ def is_indexer_error(error: Exception) -> bool: return "bad indexers" in message.lower() -def get_data_from_subgraph( +def custom_gql_fetch( network: dict, query: str, params: dict = None, diff --git a/packages/sdk/python/human-protocol-sdk/human_protocol_sdk/worker/worker_utils.py b/packages/sdk/python/human-protocol-sdk/human_protocol_sdk/worker/worker_utils.py index e7d2c7a523..4eaaf9507c 100644 --- a/packages/sdk/python/human-protocol-sdk/human_protocol_sdk/worker/worker_utils.py +++ b/packages/sdk/python/human-protocol-sdk/human_protocol_sdk/worker/worker_utils.py @@ -4,7 +4,7 @@ from web3 import Web3 from human_protocol_sdk.constants import NETWORKS, ChainId -from human_protocol_sdk.utils import SubgraphRetryConfig, get_data_from_subgraph +from human_protocol_sdk.utils import SubgraphRetryConfig, custom_gql_fetch from human_protocol_sdk.filter import WorkerFilter LOG = logging.getLogger("human_protocol_sdk.worker") @@ -66,7 +66,7 @@ def get_workers( if not network: raise WorkerUtilsError("Unsupported Chain ID") - workers_data = get_data_from_subgraph( + workers_data = custom_gql_fetch( network, query=get_workers_query(filter), params={ @@ -126,7 +126,7 @@ def get_worker( raise WorkerUtilsError(f"Invalid operator address: {worker_address}") network = NETWORKS[chain_id] - worker_data = get_data_from_subgraph( + worker_data = custom_gql_fetch( network, query=get_worker_query(), params={"address": worker_address.lower()}, diff --git a/packages/sdk/python/human-protocol-sdk/test/human_protocol_sdk/escrow/test_escrow_utils.py b/packages/sdk/python/human-protocol-sdk/test/human_protocol_sdk/escrow/test_escrow_utils.py index 862e390d72..2098b8fdf8 100644 --- a/packages/sdk/python/human-protocol-sdk/test/human_protocol_sdk/escrow/test_escrow_utils.py +++ b/packages/sdk/python/human-protocol-sdk/test/human_protocol_sdk/escrow/test_escrow_utils.py @@ -23,7 +23,7 @@ class TestEscrowUtils(unittest.TestCase): def test_get_escrows(self): with patch( - "human_protocol_sdk.escrow.escrow_utils.get_data_from_subgraph" + "human_protocol_sdk.escrow.escrow_utils.custom_gql_fetch" ) as mock_function: mock_escrow = { "id": "0x1234567890123456789012345678901234567891", @@ -163,7 +163,7 @@ def side_effect(subgraph_url, query, params, retry_config): def test_get_escrows_with_status_array(self): """Test get_escrows with an array of statuses, similar to the TypeScript test.""" with patch( - "human_protocol_sdk.escrow.escrow_utils.get_data_from_subgraph" + "human_protocol_sdk.escrow.escrow_utils.custom_gql_fetch" ) as mock_function: mock_escrow_1 = { "id": "0x1234567890123456789012345678901234567891", @@ -242,7 +242,7 @@ def side_effect(subgraph_url, query, params, retry_config): def test_get_escrow(self): with patch( - "human_protocol_sdk.escrow.escrow_utils.get_data_from_subgraph" + "human_protocol_sdk.escrow.escrow_utils.custom_gql_fetch" ) as mock_function: mock_escrow = { "id": "0x1234567890123456789012345678901234567891", @@ -329,7 +329,7 @@ def test_get_escrow(self): def test_get_escrow_empty_data(self): with patch( - "human_protocol_sdk.escrow.escrow_utils.get_data_from_subgraph" + "human_protocol_sdk.escrow.escrow_utils.custom_gql_fetch" ) as mock_function: mock_function.return_value = { "data": { @@ -372,9 +372,9 @@ def test_get_status_events_invalid_launcher(self): def test_get_status_events(self): with patch( - "human_protocol_sdk.escrow.escrow_utils.get_data_from_subgraph" - ) as mock_get_data_from_subgraph: - mock_get_data_from_subgraph.return_value = { + "human_protocol_sdk.escrow.escrow_utils.custom_gql_fetch" + ) as mock_custom_gql_fetch: + mock_custom_gql_fetch.return_value = { "data": { "escrowStatusEvents": [ { @@ -399,9 +399,9 @@ def test_get_status_events(self): def test_get_status_events_with_date_range(self): with patch( - "human_protocol_sdk.escrow.escrow_utils.get_data_from_subgraph" - ) as mock_get_data_from_subgraph: - mock_get_data_from_subgraph.return_value = { + "human_protocol_sdk.escrow.escrow_utils.custom_gql_fetch" + ) as mock_custom_gql_fetch: + mock_custom_gql_fetch.return_value = { "data": { "escrowStatusEvents": [ { @@ -432,9 +432,9 @@ def test_get_status_events_with_date_range(self): def test_get_status_events_no_data(self): with patch( - "human_protocol_sdk.escrow.escrow_utils.get_data_from_subgraph" - ) as mock_get_data_from_subgraph: - mock_get_data_from_subgraph.return_value = {"data": {}} + "human_protocol_sdk.escrow.escrow_utils.custom_gql_fetch" + ) as mock_custom_gql_fetch: + mock_custom_gql_fetch.return_value = {"data": {}} filter = StatusEventFilter( chain_id=ChainId.POLYGON_AMOY, statuses=[Status.Pending] @@ -445,9 +445,9 @@ def test_get_status_events_no_data(self): def test_get_status_events_with_launcher(self): with patch( - "human_protocol_sdk.escrow.escrow_utils.get_data_from_subgraph" - ) as mock_get_data_from_subgraph: - mock_get_data_from_subgraph.return_value = { + "human_protocol_sdk.escrow.escrow_utils.custom_gql_fetch" + ) as mock_custom_gql_fetch: + mock_custom_gql_fetch.return_value = { "data": { "escrowStatusEvents": [ { @@ -492,9 +492,9 @@ def test_get_payouts_invalid_recipient(self): def test_get_payouts(self): with patch( - "human_protocol_sdk.escrow.escrow_utils.get_data_from_subgraph" - ) as mock_get_data_from_subgraph: - mock_get_data_from_subgraph.return_value = { + "human_protocol_sdk.escrow.escrow_utils.custom_gql_fetch" + ) as mock_custom_gql_fetch: + mock_custom_gql_fetch.return_value = { "data": { "payouts": [ { @@ -524,9 +524,9 @@ def test_get_payouts(self): def test_get_payouts_with_filters(self): with patch( - "human_protocol_sdk.escrow.escrow_utils.get_data_from_subgraph" - ) as mock_get_data_from_subgraph: - mock_get_data_from_subgraph.return_value = { + "human_protocol_sdk.escrow.escrow_utils.custom_gql_fetch" + ) as mock_custom_gql_fetch: + mock_custom_gql_fetch.return_value = { "data": { "payouts": [ { @@ -562,9 +562,9 @@ def test_get_payouts_with_filters(self): def test_get_payouts_no_data(self): with patch( - "human_protocol_sdk.escrow.escrow_utils.get_data_from_subgraph" - ) as mock_get_data_from_subgraph: - mock_get_data_from_subgraph.return_value = {"data": {"payouts": []}} + "human_protocol_sdk.escrow.escrow_utils.custom_gql_fetch" + ) as mock_custom_gql_fetch: + mock_custom_gql_fetch.return_value = {"data": {"payouts": []}} filter = PayoutFilter(chain_id=ChainId.POLYGON_AMOY) result = EscrowUtils.get_payouts(filter) @@ -573,9 +573,9 @@ def test_get_payouts_no_data(self): def test_get_payouts_with_pagination(self): with patch( - "human_protocol_sdk.escrow.escrow_utils.get_data_from_subgraph" - ) as mock_get_data_from_subgraph: - mock_get_data_from_subgraph.return_value = { + "human_protocol_sdk.escrow.escrow_utils.custom_gql_fetch" + ) as mock_custom_gql_fetch: + mock_custom_gql_fetch.return_value = { "data": { "payouts": [ { @@ -609,7 +609,7 @@ def test_get_cancellation_refunds(self): from human_protocol_sdk.escrow.escrow_utils import CancellationRefundFilter with patch( - "human_protocol_sdk.escrow.escrow_utils.get_data_from_subgraph" + "human_protocol_sdk.escrow.escrow_utils.custom_gql_fetch" ) as mock_function: mock_refund = { "id": "1", @@ -679,7 +679,7 @@ def test_get_cancellation_refunds_no_data(self): from human_protocol_sdk.escrow.escrow_utils import CancellationRefundFilter with patch( - "human_protocol_sdk.escrow.escrow_utils.get_data_from_subgraph" + "human_protocol_sdk.escrow.escrow_utils.custom_gql_fetch" ) as mock_function: mock_function.return_value = {"data": {"cancellationRefundEvents": []}} @@ -690,7 +690,7 @@ def test_get_cancellation_refunds_no_data(self): def test_get_cancellation_refund(self): with patch( - "human_protocol_sdk.escrow.escrow_utils.get_data_from_subgraph" + "human_protocol_sdk.escrow.escrow_utils.custom_gql_fetch" ) as mock_function: mock_refund = { "id": "1", @@ -725,7 +725,7 @@ def test_get_cancellation_refund(self): def test_get_cancellation_refund_no_data(self): with patch( - "human_protocol_sdk.escrow.escrow_utils.get_data_from_subgraph" + "human_protocol_sdk.escrow.escrow_utils.custom_gql_fetch" ) as mock_function: mock_function.return_value = {"data": {"cancellationRefundEvents": []}} diff --git a/packages/sdk/python/human-protocol-sdk/test/human_protocol_sdk/kvstore/test_kvstore_utils.py b/packages/sdk/python/human-protocol-sdk/test/human_protocol_sdk/kvstore/test_kvstore_utils.py index 95bc56ba82..0a7a5a14d8 100644 --- a/packages/sdk/python/human-protocol-sdk/test/human_protocol_sdk/kvstore/test_kvstore_utils.py +++ b/packages/sdk/python/human-protocol-sdk/test/human_protocol_sdk/kvstore/test_kvstore_utils.py @@ -13,7 +13,7 @@ class TestKVStoreUtils(unittest.TestCase): def test_get_kvstore_data(self): with patch( - "human_protocol_sdk.kvstore.kvstore_utils.get_data_from_subgraph" + "human_protocol_sdk.kvstore.kvstore_utils.custom_gql_fetch" ) as mock_function: mock_kvstore_data = [ { @@ -59,7 +59,7 @@ def test_get_kvstore_data(self): def test_get_kvstore_data_empty_data(self): with patch( - "human_protocol_sdk.kvstore.kvstore_utils.get_data_from_subgraph" + "human_protocol_sdk.kvstore.kvstore_utils.custom_gql_fetch" ) as mock_function: mock_function.return_value = { "data": { @@ -93,7 +93,7 @@ def test_get_kvstore_data_invalid_address(self): KVStoreUtils.get_kvstore_data(ChainId.LOCALHOST, "invalid_address") self.assertEqual("Invalid KVStore address: invalid_address", str(cm.exception)) - @patch("human_protocol_sdk.kvstore.kvstore_utils.get_data_from_subgraph") + @patch("human_protocol_sdk.kvstore.kvstore_utils.custom_gql_fetch") def test_get(self, mock_function): address = Web3.to_checksum_address("0x1234567890123456789012345678901234567890") key = "key1" @@ -124,7 +124,7 @@ def test_get(self, mock_function): ) self.assertEqual(result, "1") - @patch("human_protocol_sdk.kvstore.kvstore_utils.get_data_from_subgraph") + @patch("human_protocol_sdk.kvstore.kvstore_utils.custom_gql_fetch") def test_get_empty_key(self, mock_function): address = Web3.to_checksum_address("0x1234567890123456789012345678901234567890") key = "" @@ -132,7 +132,7 @@ def test_get_empty_key(self, mock_function): KVStoreUtils.get(ChainId.LOCALHOST, address, key) self.assertEqual("Key cannot be empty", str(cm.exception)) - @patch("human_protocol_sdk.kvstore.kvstore_utils.get_data_from_subgraph") + @patch("human_protocol_sdk.kvstore.kvstore_utils.custom_gql_fetch") def test_get_invalid_address(self, mock_function): address = "invalid_address" key = "key" @@ -140,7 +140,7 @@ def test_get_invalid_address(self, mock_function): KVStoreUtils.get(ChainId.LOCALHOST, address, key) self.assertEqual(f"Invalid address: {address}", str(cm.exception)) - @patch("human_protocol_sdk.kvstore.kvstore_utils.get_data_from_subgraph") + @patch("human_protocol_sdk.kvstore.kvstore_utils.custom_gql_fetch") def test_get_empty_value(self, mock_function): mock_function.return_value = {"data": {"kvstores": []}} @@ -160,7 +160,7 @@ def test_get_empty_value(self, mock_function): retry_config=None, ) - @patch("human_protocol_sdk.kvstore.kvstore_utils.get_data_from_subgraph") + @patch("human_protocol_sdk.kvstore.kvstore_utils.custom_gql_fetch") def test_get_without_account(self, mock_function): mock_function.return_value = { "data": { @@ -190,7 +190,7 @@ def test_get_without_account(self, mock_function): ) self.assertEqual(result, "mock_value") - @patch("human_protocol_sdk.kvstore.kvstore_utils.get_data_from_subgraph") + @patch("human_protocol_sdk.kvstore.kvstore_utils.custom_gql_fetch") def test_get_file_url_and_verify_hash(self, mock_function): mock_function.side_effect = [ {"data": {"kvstores": [{"value": "https://example.com"}]}}, @@ -209,7 +209,7 @@ def test_get_file_url_and_verify_hash(self, mock_function): self.assertEqual(result, "https://example.com") - @patch("human_protocol_sdk.kvstore.kvstore_utils.get_data_from_subgraph") + @patch("human_protocol_sdk.kvstore.kvstore_utils.custom_gql_fetch") def test_get_file_url_and_verify_hash_with_key(self, mock_function): mock_function.side_effect = [ {"data": {"kvstores": [{"value": "https://example.com"}]}}, @@ -228,14 +228,14 @@ def test_get_file_url_and_verify_hash_with_key(self, mock_function): self.assertEqual(result, "https://example.com") - @patch("human_protocol_sdk.kvstore.kvstore_utils.get_data_from_subgraph") + @patch("human_protocol_sdk.kvstore.kvstore_utils.custom_gql_fetch") def test_get_file_url_and_verify_hash_invalid_address(self, mock_function): address = "invalid_address" with self.assertRaises(KVStoreClientError) as cm: KVStoreUtils.get_file_url_and_verify_hash(ChainId.LOCALHOST, address) self.assertEqual(f"Invalid address: {address}", str(cm.exception)) - @patch("human_protocol_sdk.kvstore.kvstore_utils.get_data_from_subgraph") + @patch("human_protocol_sdk.kvstore.kvstore_utils.custom_gql_fetch") def test_get_file_url_and_verify_hash_empty_value(self, mock_function): mock_function.return_value = {"data": {"kvstores": []}} @@ -248,7 +248,7 @@ def test_get_file_url_and_verify_hash_empty_value(self, mock_function): f"Key '{key}' not found for address {address}", str(cm.exception) ) - @patch("human_protocol_sdk.kvstore.kvstore_utils.get_data_from_subgraph") + @patch("human_protocol_sdk.kvstore.kvstore_utils.custom_gql_fetch") def test_get_file_url_and_verify_hash_without_account(self, mock_function): mock_function.side_effect = [ {"data": {"kvstores": [{"value": "https://example.com"}]}}, @@ -267,7 +267,7 @@ def test_get_file_url_and_verify_hash_without_account(self, mock_function): self.assertEqual(result, "https://example.com") - @patch("human_protocol_sdk.kvstore.kvstore_utils.get_data_from_subgraph") + @patch("human_protocol_sdk.kvstore.kvstore_utils.custom_gql_fetch") def test_get_file_url_and_verify_hash_invalid_hash(self, mock_function): mock_function.side_effect = [ {"data": {"kvstores": [{"value": "https://example.com"}]}}, @@ -284,7 +284,7 @@ def test_get_file_url_and_verify_hash_invalid_hash(self, mock_function): KVStoreUtils.get_file_url_and_verify_hash(ChainId.LOCALHOST, address) self.assertEqual("Invalid hash", str(cm.exception)) - @patch("human_protocol_sdk.kvstore.kvstore_utils.get_data_from_subgraph") + @patch("human_protocol_sdk.kvstore.kvstore_utils.custom_gql_fetch") def test_get_public_key(self, mock_function): mock_function.side_effect = [ {"data": {"kvstores": [{"value": "PUBLIC_KEY_URL"}]}}, @@ -307,7 +307,7 @@ def test_get_public_key_invalid_address(self): KVStoreUtils.get_public_key(ChainId.LOCALHOST, address) self.assertEqual(f"Invalid address: {address}", str(cm.exception)) - @patch("human_protocol_sdk.kvstore.kvstore_utils.get_data_from_subgraph") + @patch("human_protocol_sdk.kvstore.kvstore_utils.custom_gql_fetch") def test_get_public_key_empty_value(self, mock_function): mock_function.return_value = {"data": {"kvstores": []}} @@ -321,7 +321,7 @@ def test_get_public_key_empty_value(self, mock_function): f"Key '{key}' not found for address {address}", str(cm.exception) ) - @patch("human_protocol_sdk.kvstore.kvstore_utils.get_data_from_subgraph") + @patch("human_protocol_sdk.kvstore.kvstore_utils.custom_gql_fetch") def test_get_public_key_invalid_hash(self, mock_function): mock_function.side_effect = [ {"data": {"kvstores": [{"value": "PUBLIC_KEY_URL"}]}}, @@ -338,7 +338,7 @@ def test_get_public_key_invalid_hash(self, mock_function): KVStoreUtils.get_public_key(ChainId.LOCALHOST, address) self.assertEqual("Invalid hash", str(cm.exception)) - @patch("human_protocol_sdk.kvstore.kvstore_utils.get_data_from_subgraph") + @patch("human_protocol_sdk.kvstore.kvstore_utils.custom_gql_fetch") def test_get_public_key_without_account(self, mock_function): mock_function.side_effect = [ {"data": {"kvstores": [{"value": "PUBLIC_KEY_URL"}]}}, diff --git a/packages/sdk/python/human-protocol-sdk/test/human_protocol_sdk/operator/test_operator_utils.py b/packages/sdk/python/human-protocol-sdk/test/human_protocol_sdk/operator/test_operator_utils.py index 3858d10e0c..af76e1c4e2 100644 --- a/packages/sdk/python/human-protocol-sdk/test/human_protocol_sdk/operator/test_operator_utils.py +++ b/packages/sdk/python/human-protocol-sdk/test/human_protocol_sdk/operator/test_operator_utils.py @@ -18,7 +18,7 @@ def test_get_operators(self): mock_function = MagicMock() with patch( - "human_protocol_sdk.operator.operator_utils.get_data_from_subgraph" + "human_protocol_sdk.operator.operator_utils.custom_gql_fetch" ) as mock_function: mock_function.side_effect = [ { @@ -97,7 +97,7 @@ def test_get_operators_when_job_types_is_none(self): mock_function = MagicMock() with patch( - "human_protocol_sdk.operator.operator_utils.get_data_from_subgraph" + "human_protocol_sdk.operator.operator_utils.custom_gql_fetch" ) as mock_function: mock_function.side_effect = [ { @@ -174,7 +174,7 @@ def test_get_operators_when_job_types_is_array(self): mock_function = MagicMock() with patch( - "human_protocol_sdk.operator.operator_utils.get_data_from_subgraph" + "human_protocol_sdk.operator.operator_utils.custom_gql_fetch" ) as mock_function: mock_function.side_effect = [ { @@ -251,7 +251,7 @@ def test_get_operators_empty_data(self): mock_function = MagicMock() with patch( - "human_protocol_sdk.operator.operator_utils.get_data_from_subgraph" + "human_protocol_sdk.operator.operator_utils.custom_gql_fetch" ) as mock_function: mock_function.return_value = [ { @@ -285,7 +285,7 @@ def test_get_operator(self): mock_function = MagicMock() with patch( - "human_protocol_sdk.operator.operator_utils.get_data_from_subgraph" + "human_protocol_sdk.operator.operator_utils.custom_gql_fetch" ) as mock_function: mock_function.side_effect = [ { @@ -356,7 +356,7 @@ def test_get_operator_when_job_types_is_none(self): mock_function = MagicMock() with patch( - "human_protocol_sdk.operator.operator_utils.get_data_from_subgraph" + "human_protocol_sdk.operator.operator_utils.custom_gql_fetch" ) as mock_function: mock_function.side_effect = [ { @@ -425,7 +425,7 @@ def test_get_operator_when_job_types_is_array(self): mock_function = MagicMock() with patch( - "human_protocol_sdk.operator.operator_utils.get_data_from_subgraph" + "human_protocol_sdk.operator.operator_utils.custom_gql_fetch" ) as mock_function: mock_function.side_effect = [ { @@ -492,7 +492,7 @@ def test_get_operator_empty_data(self): mock_function = MagicMock() with patch( - "human_protocol_sdk.operator.operator_utils.get_data_from_subgraph" + "human_protocol_sdk.operator.operator_utils.custom_gql_fetch" ) as mock_function: mock_function.return_value = [{"data": {"operator": None}}] @@ -517,7 +517,7 @@ def test_get_reputation_network_operators(self): mock_function = MagicMock() with patch( - "human_protocol_sdk.operator.operator_utils.get_data_from_subgraph" + "human_protocol_sdk.operator.operator_utils.custom_gql_fetch" ) as mock_function: mock_function.side_effect = [ { @@ -570,7 +570,7 @@ def test_get_reputation_network_operators_when_job_types_is_none(self): mock_function = MagicMock() with patch( - "human_protocol_sdk.operator.operator_utils.get_data_from_subgraph" + "human_protocol_sdk.operator.operator_utils.custom_gql_fetch" ) as mock_function: mock_function.side_effect = [ { @@ -623,7 +623,7 @@ def test_get_reputation_network_operators_when_job_types_is_array(self): mock_function = MagicMock() with patch( - "human_protocol_sdk.operator.operator_utils.get_data_from_subgraph" + "human_protocol_sdk.operator.operator_utils.custom_gql_fetch" ) as mock_function: mock_function.side_effect = [ { @@ -672,7 +672,7 @@ def test_get_reputation_network_operators_empty_data(self): mock_function = MagicMock() with patch( - "human_protocol_sdk.operator.operator_utils.get_data_from_subgraph" + "human_protocol_sdk.operator.operator_utils.custom_gql_fetch" ) as mock_function: mock_function.return_value = [{"data": {"reputationNetwork": None}}] @@ -694,7 +694,7 @@ def test_get_rewards_info(self): mock_function = MagicMock() with patch( - "human_protocol_sdk.operator.operator_utils.get_data_from_subgraph" + "human_protocol_sdk.operator.operator_utils.custom_gql_fetch" ) as mock_function: mock_function.return_value = { "data": { @@ -730,7 +730,7 @@ def test_get_rewards_info_empty_data(self): mock_function = MagicMock() with patch( - "human_protocol_sdk.operator.operator_utils.get_data_from_subgraph" + "human_protocol_sdk.operator.operator_utils.custom_gql_fetch" ) as mock_function: mock_function.return_value = {"data": {"rewardAddedEvents": None}} rewards_info = OperatorUtils.get_rewards_info(ChainId.POLYGON, slasher) diff --git a/packages/sdk/python/human-protocol-sdk/test/human_protocol_sdk/staking/test_staking_utils.py b/packages/sdk/python/human-protocol-sdk/test/human_protocol_sdk/staking/test_staking_utils.py index 8f19790bf8..0babf02017 100644 --- a/packages/sdk/python/human-protocol-sdk/test/human_protocol_sdk/staking/test_staking_utils.py +++ b/packages/sdk/python/human-protocol-sdk/test/human_protocol_sdk/staking/test_staking_utils.py @@ -14,7 +14,7 @@ class TestStakingUtils(unittest.TestCase): def test_get_stakers(self): with patch( - "human_protocol_sdk.staking.staking_utils.get_data_from_subgraph" + "human_protocol_sdk.staking.staking_utils.custom_gql_fetch" ) as mock_function: mock_staker_1 = { "id": "1", @@ -121,7 +121,7 @@ def test_get_stakers(self): def test_get_stakers_empty_response(self): with patch( - "human_protocol_sdk.staking.staking_utils.get_data_from_subgraph" + "human_protocol_sdk.staking.staking_utils.custom_gql_fetch" ) as mock_function: mock_function.return_value = {"data": {"stakers": []}} @@ -140,7 +140,7 @@ def test_get_stakers_invalid_network(self): def test_get_staker(self): with patch( - "human_protocol_sdk.staking.staking_utils.get_data_from_subgraph" + "human_protocol_sdk.staking.staking_utils.custom_gql_fetch" ) as mock_function: mock_staker = { "id": "1", @@ -183,7 +183,7 @@ def test_get_staker(self): def test_get_staker_empty_data(self): with patch( - "human_protocol_sdk.staking.staking_utils.get_data_from_subgraph" + "human_protocol_sdk.staking.staking_utils.custom_gql_fetch" ) as mock_function: mock_function.return_value = {"data": {"staker": None}} diff --git a/packages/sdk/python/human-protocol-sdk/test/human_protocol_sdk/statistics/test_statistics_client.py b/packages/sdk/python/human-protocol-sdk/test/human_protocol_sdk/statistics/test_statistics_client.py index a45b383b7b..ea3e404f6e 100644 --- a/packages/sdk/python/human-protocol-sdk/test/human_protocol_sdk/statistics/test_statistics_client.py +++ b/packages/sdk/python/human-protocol-sdk/test/human_protocol_sdk/statistics/test_statistics_client.py @@ -33,7 +33,7 @@ def test_get_escrow_statistics(self): mock_function = MagicMock() with patch( - "human_protocol_sdk.statistics.statistics_client.get_data_from_subgraph" + "human_protocol_sdk.statistics.statistics_client.custom_gql_fetch" ) as mock_function: mock_function.side_effect = [ { @@ -103,7 +103,7 @@ def test_get_worker_statistics(self): mock_function = MagicMock() with patch( - "human_protocol_sdk.statistics.statistics_client.get_data_from_subgraph" + "human_protocol_sdk.statistics.statistics_client.custom_gql_fetch" ) as mock_function: mock_function.side_effect = [ { @@ -147,7 +147,7 @@ def test_get_payment_statistics(self): mock_function = MagicMock() with patch( - "human_protocol_sdk.statistics.statistics_client.get_data_from_subgraph" + "human_protocol_sdk.statistics.statistics_client.custom_gql_fetch" ) as mock_function: mock_function.side_effect = [ { @@ -196,7 +196,7 @@ def test_get_hmt_statistics(self): mock_function = MagicMock() with patch( - "human_protocol_sdk.statistics.statistics_client.get_data_from_subgraph" + "human_protocol_sdk.statistics.statistics_client.custom_gql_fetch" ) as mock_function: mock_function.side_effect = [ { @@ -230,7 +230,7 @@ def test_get_hmt_holders(self): mock_function = MagicMock() with patch( - "human_protocol_sdk.statistics.statistics_client.get_data_from_subgraph" + "human_protocol_sdk.statistics.statistics_client.custom_gql_fetch" ) as mock_function: mock_function.side_effect = [ { @@ -272,7 +272,7 @@ def test_get_hmt_daily_data(self): mock_function = MagicMock() with patch( - "human_protocol_sdk.statistics.statistics_client.get_data_from_subgraph" + "human_protocol_sdk.statistics.statistics_client.custom_gql_fetch" ) as mock_function: mock_function.side_effect = [ { diff --git a/packages/sdk/python/human-protocol-sdk/test/human_protocol_sdk/test_utils.py b/packages/sdk/python/human-protocol-sdk/test/human_protocol_sdk/test_utils.py index b8c53b10c2..3fdeb4f973 100644 --- a/packages/sdk/python/human-protocol-sdk/test/human_protocol_sdk/test_utils.py +++ b/packages/sdk/python/human-protocol-sdk/test/human_protocol_sdk/test_utils.py @@ -4,7 +4,7 @@ from human_protocol_sdk.utils import ( SubgraphRetryConfig, - get_data_from_subgraph, + custom_gql_fetch, is_indexer_error, validate_url, ) @@ -60,7 +60,7 @@ def test_returns_response_without_retry_config(self): "human_protocol_sdk.utils._fetch_subgraph_data", return_value=expected, ) as mock_fetch: - result = get_data_from_subgraph(self.network, self.query, self.variables) + result = custom_gql_fetch(self.network, self.query, self.variables) self.assertEqual(result, expected) mock_fetch.assert_called_once_with(self.network, self.query, self.variables) @@ -73,7 +73,7 @@ def test_retries_on_indexer_error_and_succeeds(self): "human_protocol_sdk.utils._fetch_subgraph_data", side_effect=[error, {"data": {"ok": True}}], ) as mock_fetch, patch("human_protocol_sdk.utils.time.sleep") as mock_sleep: - result = get_data_from_subgraph( + result = custom_gql_fetch( self.network, self.query, self.variables, retry_config=retry_config ) @@ -88,7 +88,7 @@ def test_raises_immediately_on_non_indexer_error(self): side_effect=Exception("network failure"), ) as mock_fetch, patch("human_protocol_sdk.utils.time.sleep") as mock_sleep: with self.assertRaises(Exception) as ctx: - get_data_from_subgraph( + custom_gql_fetch( self.network, self.query, self.variables, retry_config=retry_config ) @@ -108,7 +108,7 @@ def test_raises_after_exhausting_retries(self): side_effect=errors, ) as mock_fetch, patch("human_protocol_sdk.utils.time.sleep"): with self.assertRaises(Exception) as ctx: - get_data_from_subgraph( + custom_gql_fetch( self.network, self.query, self.variables, retry_config=retry_config ) diff --git a/packages/sdk/python/human-protocol-sdk/test/human_protocol_sdk/transaction/test_transaction_utils.py b/packages/sdk/python/human-protocol-sdk/test/human_protocol_sdk/transaction/test_transaction_utils.py index 4d93e9c9c9..278b63f776 100644 --- a/packages/sdk/python/human-protocol-sdk/test/human_protocol_sdk/transaction/test_transaction_utils.py +++ b/packages/sdk/python/human-protocol-sdk/test/human_protocol_sdk/transaction/test_transaction_utils.py @@ -16,7 +16,7 @@ class TestTransactionUtils(unittest.TestCase): def test_get_transactions(self): with patch( - "human_protocol_sdk.transaction.transaction_utils.get_data_from_subgraph" + "human_protocol_sdk.transaction.transaction_utils.custom_gql_fetch" ) as mock_function: mock_transaction_1 = { "block": "123", @@ -90,7 +90,7 @@ def test_get_transactions(self): def test_get_transactions_empty_response(self): with patch( - "human_protocol_sdk.transaction.transaction_utils.get_data_from_subgraph" + "human_protocol_sdk.transaction.transaction_utils.custom_gql_fetch" ) as mock_function: mock_function.return_value = {"data": {"transactions": []}} @@ -150,7 +150,7 @@ def test_get_transactions_invalid_to_address(self): def test_get_transaction(self): with patch( - "human_protocol_sdk.transaction.transaction_utils.get_data_from_subgraph" + "human_protocol_sdk.transaction.transaction_utils.custom_gql_fetch" ) as mock_function: mock_transaction = { "block": "123", @@ -199,7 +199,7 @@ def test_get_transaction(self): def test_get_transaction_empty_data(self): with patch( - "human_protocol_sdk.transaction.transaction_utils.get_data_from_subgraph" + "human_protocol_sdk.transaction.transaction_utils.custom_gql_fetch" ) as mock_function: mock_function.return_value = {"data": {"transaction": None}} diff --git a/packages/sdk/python/human-protocol-sdk/test/human_protocol_sdk/worker/test_worker_utils.py b/packages/sdk/python/human-protocol-sdk/test/human_protocol_sdk/worker/test_worker_utils.py index 6ae51ccbc7..3c49aac753 100644 --- a/packages/sdk/python/human-protocol-sdk/test/human_protocol_sdk/worker/test_worker_utils.py +++ b/packages/sdk/python/human-protocol-sdk/test/human_protocol_sdk/worker/test_worker_utils.py @@ -10,7 +10,7 @@ class TestWorkerUtils(unittest.TestCase): def test_get_workers(self): with patch( - "human_protocol_sdk.worker.worker_utils.get_data_from_subgraph" + "human_protocol_sdk.worker.worker_utils.custom_gql_fetch" ) as mock_function: mock_worker_1 = { "id": "worker1", @@ -55,7 +55,7 @@ def test_get_workers(self): def test_get_workers_empty_response(self): with patch( - "human_protocol_sdk.worker.worker_utils.get_data_from_subgraph" + "human_protocol_sdk.worker.worker_utils.custom_gql_fetch" ) as mock_function: mock_function.return_value = {"data": {"workers": []}} @@ -88,7 +88,7 @@ def test_get_workers_invalid_network(self): def test_get_worker(self): with patch( - "human_protocol_sdk.worker.worker_utils.get_data_from_subgraph" + "human_protocol_sdk.worker.worker_utils.custom_gql_fetch" ) as mock_function: mock_worker = { "id": "worker1", @@ -118,7 +118,7 @@ def test_get_worker(self): def test_get_worker_empty_data(self): with patch( - "human_protocol_sdk.worker.worker_utils.get_data_from_subgraph" + "human_protocol_sdk.worker.worker_utils.custom_gql_fetch" ) as mock_function: mock_function.return_value = {"data": {"worker": None}} diff --git a/packages/sdk/typescript/human-protocol-sdk/src/escrow.ts b/packages/sdk/typescript/human-protocol-sdk/src/escrow.ts index a6ace7df2e..77661805d8 100644 --- a/packages/sdk/typescript/human-protocol-sdk/src/escrow.ts +++ b/packages/sdk/typescript/human-protocol-sdk/src/escrow.ts @@ -64,13 +64,13 @@ import { ICancellationRefundFilter, IPayout, IEscrowWithdraw, - SubgraphRetryConfig, + SubgraphOptions, } from './interfaces'; import { EscrowStatus, NetworkData, TransactionLikeWithNonce } from './types'; import { getSubgraphUrl, getUnixTimestamp, - gqlFetchWithRetry, + customGqlFetch, isValidJson, isValidUrl, throwError, @@ -1942,7 +1942,7 @@ export class EscrowUtils { * * * @param {IEscrowsFilter} filter Filter parameters. - * @param {SubgraphRetryConfig} retryConfig Optional configuration for retrying subgraph requests. + * @param {SubgraphOptions} options Optional configuration for subgraph requests. * @returns {IEscrow[]} List of escrows that match the filter. * * **Code example** @@ -1961,7 +1961,7 @@ export class EscrowUtils { */ public static async getEscrows( filter: IEscrowsFilter, - retryConfig?: SubgraphRetryConfig + options?: SubgraphOptions ): Promise { if (filter.launcher && !ethers.isAddress(filter.launcher)) { throw ErrorInvalidAddress; @@ -1995,7 +1995,7 @@ export class EscrowUtils { statuses = Array.isArray(filter.status) ? filter.status : [filter.status]; statuses = statuses.map((status) => EscrowStatus[status]); } - const { escrows } = await gqlFetchWithRetry<{ escrows: EscrowData[] }>( + const { escrows } = await customGqlFetch<{ escrows: EscrowData[] }>( getSubgraphUrl(networkData), GET_ESCROWS_QUERY(filter), { @@ -2011,7 +2011,7 @@ export class EscrowUtils { first: first, skip: skip, }, - retryConfig + options ); return (escrows || []).map((e) => mapEscrow(e, networkData.chainId)); } @@ -2069,7 +2069,7 @@ export class EscrowUtils { * * @param {ChainId} chainId Network in which the escrow has been deployed * @param {string} escrowAddress Address of the escrow - * @param {SubgraphRetryConfig} retryConfig Optional configuration for retrying subgraph requests. + * @param {SubgraphOptions} options Optional configuration for subgraph requests. * @returns {Promise} - Escrow data or null if not found. * * **Code example** @@ -2083,7 +2083,7 @@ export class EscrowUtils { public static async getEscrow( chainId: ChainId, escrowAddress: string, - retryConfig?: SubgraphRetryConfig + options?: SubgraphOptions ): Promise { const networkData = NETWORKS[chainId]; @@ -2095,11 +2095,11 @@ export class EscrowUtils { throw ErrorInvalidAddress; } - const { escrow } = await gqlFetchWithRetry<{ escrow: EscrowData | null }>( + const { escrow } = await customGqlFetch<{ escrow: EscrowData | null }>( getSubgraphUrl(networkData), GET_ESCROW_BY_ADDRESS_QUERY(), { escrowAddress: escrowAddress.toLowerCase() }, - retryConfig + options ); if (!escrow) return null; @@ -2142,7 +2142,7 @@ export class EscrowUtils { * ``` * * @param {IStatusEventFilter} filter Filter parameters. - * @param {SubgraphRetryConfig} retryConfig Optional configuration for retrying subgraph requests. + * @param {SubgraphOptions} options Optional configuration for subgraph requests. * @returns {Promise} - Array of status events with their corresponding statuses. * * **Code example** @@ -2165,7 +2165,7 @@ export class EscrowUtils { */ public static async getStatusEvents( filter: IStatusEventFilter, - retryConfig?: SubgraphRetryConfig + options?: SubgraphOptions ): Promise { const { chainId, @@ -2199,7 +2199,7 @@ export class EscrowUtils { const statusNames = effectiveStatuses.map((status) => EscrowStatus[status]); - const data = await gqlFetchWithRetry<{ + const data = await customGqlFetch<{ escrowStatusEvents: StatusEvent[]; }>( getSubgraphUrl(networkData), @@ -2213,7 +2213,7 @@ export class EscrowUtils { first: Math.min(first, 1000), skip, }, - retryConfig + options ); if (!data || !data['escrowStatusEvents']) { @@ -2237,7 +2237,7 @@ export class EscrowUtils { * Fetch payouts from the subgraph. * * @param {IPayoutFilter} filter Filter parameters. - * @param {SubgraphRetryConfig} retryConfig Optional configuration for retrying subgraph requests. + * @param {SubgraphOptions} options Optional configuration for subgraph requests. * @returns {Promise} List of payouts matching the filters. * * **Code example** @@ -2257,7 +2257,7 @@ export class EscrowUtils { */ public static async getPayouts( filter: IPayoutFilter, - retryConfig?: SubgraphRetryConfig + options?: SubgraphOptions ): Promise { const networkData = NETWORKS[filter.chainId]; if (!networkData) { @@ -2275,7 +2275,7 @@ export class EscrowUtils { const skip = filter.skip || 0; const orderDirection = filter.orderDirection || OrderDirection.DESC; - const { payouts } = await gqlFetchWithRetry<{ payouts: PayoutData[] }>( + const { payouts } = await customGqlFetch<{ payouts: PayoutData[] }>( getSubgraphUrl(networkData), GET_PAYOUTS_QUERY(filter), { @@ -2287,7 +2287,7 @@ export class EscrowUtils { skip, orderDirection, }, - retryConfig + options ); if (!payouts) { return []; @@ -2336,7 +2336,7 @@ export class EscrowUtils { * * * @param {Object} filter Filter parameters. - * @param {SubgraphRetryConfig} retryConfig Optional configuration for retrying subgraph requests. + * @param {SubgraphOptions} options Optional configuration for subgraph requests. * @returns {Promise} List of cancellation refunds matching the filters. * * **Code example** @@ -2353,7 +2353,7 @@ export class EscrowUtils { */ public static async getCancellationRefunds( filter: ICancellationRefundFilter, - retryConfig?: SubgraphRetryConfig + options?: SubgraphOptions ): Promise { const networkData = NETWORKS[filter.chainId]; if (!networkData) throw ErrorUnsupportedChainID; @@ -2369,7 +2369,7 @@ export class EscrowUtils { const skip = filter.skip || 0; const orderDirection = filter.orderDirection || OrderDirection.DESC; - const { cancellationRefundEvents } = await gqlFetchWithRetry<{ + const { cancellationRefundEvents } = await customGqlFetch<{ cancellationRefundEvents: CancellationRefundData[]; }>( getSubgraphUrl(networkData), @@ -2383,7 +2383,7 @@ export class EscrowUtils { skip, orderDirection, }, - retryConfig + options ); if (!cancellationRefundEvents || cancellationRefundEvents.length === 0) { @@ -2436,7 +2436,7 @@ export class EscrowUtils { * * @param {ChainId} chainId Network in which the escrow has been deployed * @param {string} escrowAddress Address of the escrow - * @param {SubgraphRetryConfig} retryConfig Optional configuration for retrying subgraph requests. + * @param {SubgraphOptions} options Optional configuration for subgraph requests. * @returns {Promise} Cancellation refund data * * **Code example** @@ -2450,7 +2450,7 @@ export class EscrowUtils { public static async getCancellationRefund( chainId: ChainId, escrowAddress: string, - retryConfig?: SubgraphRetryConfig + options?: SubgraphOptions ): Promise { const networkData = NETWORKS[chainId]; if (!networkData) throw ErrorUnsupportedChainID; @@ -2459,13 +2459,13 @@ export class EscrowUtils { throw ErrorInvalidEscrowAddressProvided; } - const { cancellationRefundEvents } = await gqlFetchWithRetry<{ + const { cancellationRefundEvents } = await customGqlFetch<{ cancellationRefundEvents: CancellationRefundData[]; }>( getSubgraphUrl(networkData), GET_CANCELLATION_REFUND_BY_ADDRESS_QUERY(), { escrowAddress: escrowAddress.toLowerCase() }, - retryConfig + options ); if (!cancellationRefundEvents || cancellationRefundEvents.length === 0) { diff --git a/packages/sdk/typescript/human-protocol-sdk/src/interfaces.ts b/packages/sdk/typescript/human-protocol-sdk/src/interfaces.ts index 3b1eddf78a..716c6e2d64 100644 --- a/packages/sdk/typescript/human-protocol-sdk/src/interfaces.ts +++ b/packages/sdk/typescript/human-protocol-sdk/src/interfaces.ts @@ -316,7 +316,7 @@ export interface IEscrowWithdraw { /** * Configuration options for subgraph requests with retry logic. */ -export interface SubgraphRetryConfig { +export interface SubgraphOptions { /** Maximum number of retry attempts (default: 3) */ maxRetries?: number; /** Base delay between retries in milliseconds (default: 1000) */ diff --git a/packages/sdk/typescript/human-protocol-sdk/src/kvstore.ts b/packages/sdk/typescript/human-protocol-sdk/src/kvstore.ts index 5ed525cf62..6a8bbaa212 100644 --- a/packages/sdk/typescript/human-protocol-sdk/src/kvstore.ts +++ b/packages/sdk/typescript/human-protocol-sdk/src/kvstore.ts @@ -18,13 +18,13 @@ import { InvalidKeyError, } from './error'; import { NetworkData } from './types'; -import { getSubgraphUrl, gqlFetchWithRetry, isValidUrl } from './utils'; +import { getSubgraphUrl, customGqlFetch, isValidUrl } from './utils'; import { GET_KVSTORE_BY_ADDRESS_AND_KEY_QUERY, GET_KVSTORE_BY_ADDRESS_QUERY, } from './graphql/queries/kvstore'; import { KVStoreData } from './graphql'; -import { IKVStore, SubgraphRetryConfig } from './interfaces'; +import { IKVStore, SubgraphOptions } from './interfaces'; /** * ## Introduction * @@ -293,7 +293,7 @@ export class KVStoreClient extends BaseEthersClient { * * @param {string} address Address from which to get the key value. * @param {string} key Key to obtain the value. - * @param {SubgraphRetryConfig} retryConfig Optional configuration for retrying subgraph requests. + * @param {SubgraphOptions} options Optional configuration for subgraph requests. * @returns {string} Value of the key. * * @@ -365,7 +365,7 @@ export class KVStoreUtils { * * @param {ChainId} chainId Network in which the KVStore is deployed * @param {string} address Address of the KVStore - * @param {SubgraphRetryConfig} retryConfig Optional configuration for retrying subgraph requests. + * @param {SubgraphOptions} options Optional configuration for subgraph requests. * @returns {Promise} KVStore data * @throws {ErrorUnsupportedChainID} - Thrown if the network's chainId is not supported * @throws {ErrorInvalidAddress} - Thrown if the Address sent is invalid @@ -382,7 +382,7 @@ export class KVStoreUtils { public static async getKVStoreData( chainId: ChainId, address: string, - retryConfig?: SubgraphRetryConfig + options?: SubgraphOptions ): Promise { const networkData = NETWORKS[chainId]; @@ -394,11 +394,11 @@ export class KVStoreUtils { throw ErrorInvalidAddress; } - const { kvstores } = await gqlFetchWithRetry<{ kvstores: KVStoreData[] }>( + const { kvstores } = await customGqlFetch<{ kvstores: KVStoreData[] }>( getSubgraphUrl(networkData), GET_KVSTORE_BY_ADDRESS_QUERY(), { address: address.toLowerCase() }, - retryConfig + options ); const kvStoreData = kvstores.map((item) => ({ @@ -415,7 +415,7 @@ export class KVStoreUtils { * @param {ChainId} chainId Network in which the KVStore is deployed * @param {string} address Address from which to get the key value. * @param {string} key Key to obtain the value. - * @param {SubgraphRetryConfig} retryConfig Optional configuration for retrying subgraph requests. + * @param {SubgraphOptions} options Optional configuration for subgraph requests. * @returns {Promise} Value of the key. * @throws {ErrorUnsupportedChainID} - Thrown if the network's chainId is not supported * @throws {ErrorInvalidAddress} - Thrown if the Address sent is invalid @@ -438,7 +438,7 @@ export class KVStoreUtils { chainId: ChainId, address: string, key: string, - retryConfig?: SubgraphRetryConfig + options?: SubgraphOptions ): Promise { if (key === '') throw ErrorKVStoreEmptyKey; if (!ethers.isAddress(address)) throw ErrorInvalidAddress; @@ -449,11 +449,11 @@ export class KVStoreUtils { throw ErrorUnsupportedChainID; } - const { kvstores } = await gqlFetchWithRetry<{ kvstores: KVStoreData[] }>( + const { kvstores } = await customGqlFetch<{ kvstores: KVStoreData[] }>( getSubgraphUrl(networkData), GET_KVSTORE_BY_ADDRESS_AND_KEY_QUERY(), { address: address.toLowerCase(), key }, - retryConfig + options ); if (!kvstores || kvstores.length === 0) { @@ -469,7 +469,7 @@ export class KVStoreUtils { * @param {ChainId} chainId Network in which the KVStore is deployed * @param {string} address Address from which to get the URL value. * @param {string} urlKey Configurable URL key. `url` by default. - * @param {SubgraphRetryConfig} retryConfig Optional configuration for retrying subgraph requests. + * @param {SubgraphOptions} options Optional configuration for subgraph requests. * @returns {Promise} URL value for the given address if it exists, and the content is valid * * **Code example** @@ -488,7 +488,7 @@ export class KVStoreUtils { chainId: ChainId, address: string, urlKey = 'url', - retryConfig?: SubgraphRetryConfig + options?: SubgraphOptions ): Promise { if (!ethers.isAddress(address)) throw ErrorInvalidAddress; const hashKey = urlKey + '_hash'; @@ -497,7 +497,7 @@ export class KVStoreUtils { hash = ''; try { - url = await this.get(chainId, address, urlKey, retryConfig); + url = await this.get(chainId, address, urlKey, options); } catch (e) { if (e instanceof Error) throw Error(`Failed to get URL: ${e.message}`); } @@ -548,13 +548,13 @@ export class KVStoreUtils { public static async getPublicKey( chainId: ChainId, address: string, - retryConfig?: SubgraphRetryConfig + options?: SubgraphOptions ): Promise { const publicKeyUrl = await this.getFileUrlAndVerifyHash( chainId, address, KVStoreKeys.publicKey, - retryConfig + options ); if (publicKeyUrl === '') { diff --git a/packages/sdk/typescript/human-protocol-sdk/src/operator.ts b/packages/sdk/typescript/human-protocol-sdk/src/operator.ts index 10af374b72..2428c9d24e 100644 --- a/packages/sdk/typescript/human-protocol-sdk/src/operator.ts +++ b/packages/sdk/typescript/human-protocol-sdk/src/operator.ts @@ -3,7 +3,7 @@ import { IOperator, IOperatorsFilter, IReward, - SubgraphRetryConfig, + SubgraphOptions, } from './interfaces'; import { GET_REWARD_ADDED_EVENTS_QUERY } from './graphql/queries/reward'; import { @@ -22,7 +22,7 @@ import { ErrorInvalidStakerAddressProvided, ErrorUnsupportedChainID, } from './error'; -import { getSubgraphUrl, gqlFetchWithRetry } from './utils'; +import { getSubgraphUrl, customGqlFetch } from './utils'; import { ChainId, OrderDirection } from './enums'; import { NETWORKS } from './constants'; @@ -32,7 +32,7 @@ export class OperatorUtils { * * @param {ChainId} chainId Network in which the operator is deployed * @param {string} address Operator address. - * @param {SubgraphRetryConfig} retryConfig Optional configuration for retrying subgraph requests. + * @param {SubgraphOptions} options Optional configuration for subgraph requests. * @returns {Promise} - Returns the operator details or null if not found. * * **Code example** @@ -46,7 +46,7 @@ export class OperatorUtils { public static async getOperator( chainId: ChainId, address: string, - retryConfig?: SubgraphRetryConfig + options?: SubgraphOptions ): Promise { if (!ethers.isAddress(address)) { throw ErrorInvalidStakerAddressProvided; @@ -57,11 +57,11 @@ export class OperatorUtils { throw ErrorUnsupportedChainID; } - const { operator } = await gqlFetchWithRetry<{ + const { operator } = await customGqlFetch<{ operator: IOperatorSubgraph; }>(getSubgraphUrl(networkData), GET_LEADER_QUERY, { address: address.toLowerCase(), - retryConfig, + options, }); if (!operator) { @@ -75,7 +75,7 @@ export class OperatorUtils { * This function returns all the operator details of the protocol. * * @param {IOperatorsFilter} filter Filter for the operators. - * @param {SubgraphRetryConfig} retryConfig Optional configuration for retrying subgraph requests. + * @param {SubgraphOptions} options Optional configuration for subgraph requests. * @returns {Promise} Returns an array with all the operator details. * * **Code example** @@ -91,7 +91,7 @@ export class OperatorUtils { */ public static async getOperators( filter: IOperatorsFilter, - retryConfig?: SubgraphRetryConfig + options?: SubgraphOptions ): Promise { const first = filter.first !== undefined && filter.first > 0 @@ -116,7 +116,7 @@ export class OperatorUtils { throw ErrorUnsupportedChainID; } - const { operators } = await gqlFetchWithRetry<{ + const { operators } = await customGqlFetch<{ operators: IOperatorSubgraph[]; }>( getSubgraphUrl(networkData), @@ -129,7 +129,7 @@ export class OperatorUtils { first: first, skip: skip, }, - retryConfig + options ); if (!operators) { @@ -145,7 +145,7 @@ export class OperatorUtils { * @param {ChainId} chainId Network in which the reputation network is deployed * @param {string} address Address of the reputation oracle. * @param {string} [role] - (Optional) Role of the operator. - * @param {SubgraphRetryConfig} retryConfig Optional configuration for retrying subgraph requests. + * @param {SubgraphOptions} options Optional configuration for subgraph requests. * @returns {Promise} - Returns an array of operator details. * * **Code example** @@ -160,14 +160,14 @@ export class OperatorUtils { chainId: ChainId, address: string, role?: string, - retryConfig?: SubgraphRetryConfig + options?: SubgraphOptions ): Promise { const networkData = NETWORKS[chainId]; if (!networkData) { throw ErrorUnsupportedChainID; } - const { reputationNetwork } = await gqlFetchWithRetry<{ + const { reputationNetwork } = await customGqlFetch<{ reputationNetwork: IReputationNetworkSubgraph; }>( getSubgraphUrl(networkData), @@ -176,7 +176,7 @@ export class OperatorUtils { address: address.toLowerCase(), role: role, }, - retryConfig + options ); if (!reputationNetwork) return []; @@ -191,7 +191,7 @@ export class OperatorUtils { * * @param {ChainId} chainId Network in which the rewards are deployed * @param {string} slasherAddress Slasher address. - * @param {SubgraphRetryConfig} retryConfig Optional configuration for retrying subgraph requests. + * @param {SubgraphOptions} options Optional configuration for subgraph requests. * @returns {Promise} Returns an array of Reward objects that contain the rewards earned by the user through slashing other users. * * **Code example** @@ -205,7 +205,7 @@ export class OperatorUtils { public static async getRewards( chainId: ChainId, slasherAddress: string, - retryConfig?: SubgraphRetryConfig + options?: SubgraphOptions ): Promise { if (!ethers.isAddress(slasherAddress)) { throw ErrorInvalidSlasherAddressProvided; @@ -216,7 +216,7 @@ export class OperatorUtils { throw ErrorUnsupportedChainID; } - const { rewardAddedEvents } = await gqlFetchWithRetry<{ + const { rewardAddedEvents } = await customGqlFetch<{ rewardAddedEvents: RewardAddedEventData[]; }>( getSubgraphUrl(networkData), @@ -224,7 +224,7 @@ export class OperatorUtils { { slasherAddress: slasherAddress.toLowerCase(), }, - retryConfig + options ); if (!rewardAddedEvents) return []; diff --git a/packages/sdk/typescript/human-protocol-sdk/src/staking.ts b/packages/sdk/typescript/human-protocol-sdk/src/staking.ts index 6eae68f54d..4e5afb61e3 100644 --- a/packages/sdk/typescript/human-protocol-sdk/src/staking.ts +++ b/packages/sdk/typescript/human-protocol-sdk/src/staking.ts @@ -26,11 +26,11 @@ import { IStaker, IStakersFilter, StakerInfo, - SubgraphRetryConfig, + SubgraphOptions, } from './interfaces'; import { StakerData } from './graphql'; import { NetworkData } from './types'; -import { getSubgraphUrl, gqlFetchWithRetry, throwError } from './utils'; +import { getSubgraphUrl, customGqlFetch, throwError } from './utils'; import { GET_STAKER_BY_ADDRESS_QUERY, GET_STAKERS_QUERY, @@ -499,13 +499,13 @@ export class StakingUtils { * * @param {ChainId} chainId Network in which the staking contract is deployed * @param {string} stakerAddress Address of the staker - * @param {SubgraphRetryConfig} retryConfig Optional configuration for retrying subgraph requests. + * @param {SubgraphOptions} options Optional configuration for subgraph requests. * @returns {Promise} Staker info from subgraph */ public static async getStaker( chainId: ChainId, stakerAddress: string, - retryConfig?: SubgraphRetryConfig + options?: SubgraphOptions ): Promise { if (!ethers.isAddress(stakerAddress)) { throw ErrorInvalidStakerAddressProvided; @@ -516,11 +516,11 @@ export class StakingUtils { throw ErrorUnsupportedChainID; } - const { staker } = await gqlFetchWithRetry<{ staker: StakerData }>( + const { staker } = await customGqlFetch<{ staker: StakerData }>( getSubgraphUrl(networkData), GET_STAKER_BY_ADDRESS_QUERY, { id: stakerAddress.toLowerCase() }, - retryConfig + options ); if (!staker) { @@ -534,12 +534,12 @@ export class StakingUtils { * Gets all stakers from the subgraph with filters, pagination and ordering. * * @param {IStakersFilter} filter Stakers filter with pagination and ordering - * @param {SubgraphRetryConfig} retryConfig Optional configuration for retrying subgraph requests. + * @param {SubgraphOptions} options Optional configuration for subgraph requests. * @returns {Promise} Array of stakers */ public static async getStakers( filter: IStakersFilter, - retryConfig?: SubgraphRetryConfig + options?: SubgraphOptions ): Promise { const first = filter.first !== undefined ? Math.min(filter.first, 1000) : 10; @@ -552,7 +552,7 @@ export class StakingUtils { throw ErrorUnsupportedChainID; } - const { stakers } = await gqlFetchWithRetry<{ stakers: StakerData[] }>( + const { stakers } = await customGqlFetch<{ stakers: StakerData[] }>( getSubgraphUrl(networkData), GET_STAKERS_QUERY(filter), { @@ -585,7 +585,7 @@ export class StakingUtils { first: first, skip: skip, }, - retryConfig + options ); if (!stakers) { return []; diff --git a/packages/sdk/typescript/human-protocol-sdk/src/statistics.ts b/packages/sdk/typescript/human-protocol-sdk/src/statistics.ts index 046e8f1127..36095e7258 100644 --- a/packages/sdk/typescript/human-protocol-sdk/src/statistics.ts +++ b/packages/sdk/typescript/human-protocol-sdk/src/statistics.ts @@ -19,13 +19,13 @@ import { IPaymentStatistics, IStatisticsFilter, IWorkerStatistics, - SubgraphRetryConfig, + SubgraphOptions, } from './interfaces'; import { NetworkData } from './types'; import { getSubgraphUrl, getUnixTimestamp, - gqlFetchWithRetry, + customGqlFetch, throwError, } from './utils'; @@ -107,7 +107,7 @@ export class StatisticsClient { * ``` * * @param {IStatisticsFilter} filter Statistics params with duration data - * @param {SubgraphRetryConfig} retryConfig Optional configuration for retrying subgraph requests. + * @param {SubgraphOptions} options Optional configuration for subgraph requests. * @returns {Promise} Escrow statistics data. * * **Code example** @@ -126,7 +126,7 @@ export class StatisticsClient { */ async getEscrowStatistics( filter: IStatisticsFilter = {}, - retryConfig?: SubgraphRetryConfig + options?: SubgraphOptions ): Promise { try { const first = @@ -134,11 +134,11 @@ export class StatisticsClient { const skip = filter.skip || 0; const orderDirection = filter.orderDirection || OrderDirection.ASC; - const { escrowStatistics } = await gqlFetchWithRetry<{ + const { escrowStatistics } = await customGqlFetch<{ escrowStatistics: EscrowStatisticsData; - }>(this.subgraphUrl, GET_ESCROW_STATISTICS_QUERY, retryConfig); + }>(this.subgraphUrl, GET_ESCROW_STATISTICS_QUERY, options); - const { eventDayDatas } = await gqlFetchWithRetry<{ + const { eventDayDatas } = await customGqlFetch<{ eventDayDatas: EventDayData[]; }>( this.subgraphUrl, @@ -150,7 +150,7 @@ export class StatisticsClient { first: first, skip: skip, }, - retryConfig + options ); return { @@ -198,7 +198,7 @@ export class StatisticsClient { * ``` * * @param {IStatisticsFilter} filter Statistics params with duration data - * @param {SubgraphRetryConfig} retryConfig Optional configuration for retrying subgraph requests. + * @param {SubgraphOptions} options Optional configuration for subgraph requests. * @returns {Promise} Worker statistics data. * * **Code example** @@ -217,7 +217,7 @@ export class StatisticsClient { */ async getWorkerStatistics( filter: IStatisticsFilter = {}, - retryConfig?: SubgraphRetryConfig + options?: SubgraphOptions ): Promise { try { const first = @@ -225,7 +225,7 @@ export class StatisticsClient { const skip = filter.skip || 0; const orderDirection = filter.orderDirection || OrderDirection.ASC; - const { eventDayDatas } = await gqlFetchWithRetry<{ + const { eventDayDatas } = await customGqlFetch<{ eventDayDatas: EventDayData[]; }>( this.subgraphUrl, @@ -237,7 +237,7 @@ export class StatisticsClient { first: first, skip: skip, }, - retryConfig + options ); return { @@ -280,7 +280,7 @@ export class StatisticsClient { * ``` * * @param {IStatisticsFilter} filter Statistics params with duration data - * @param {SubgraphRetryConfig} retryConfig Optional configuration for retrying subgraph requests. + * @param {SubgraphOptions} options Optional configuration for subgraph requests. * @returns {Promise} Payment statistics data. * * **Code example** @@ -320,7 +320,7 @@ export class StatisticsClient { */ async getPaymentStatistics( filter: IStatisticsFilter = {}, - retryConfig?: SubgraphRetryConfig + options?: SubgraphOptions ): Promise { try { const first = @@ -328,7 +328,7 @@ export class StatisticsClient { const skip = filter.skip || 0; const orderDirection = filter.orderDirection || OrderDirection.ASC; - const { eventDayDatas } = await gqlFetchWithRetry<{ + const { eventDayDatas } = await customGqlFetch<{ eventDayDatas: EventDayData[]; }>( this.subgraphUrl, @@ -340,7 +340,7 @@ export class StatisticsClient { first: first, skip: skip, }, - retryConfig + options ); return { @@ -371,7 +371,7 @@ export class StatisticsClient { * }; * ``` * - * @param {SubgraphRetryConfig} retryConfig Optional configuration for retrying subgraph requests. + * @param {SubgraphOptions} options Optional configuration for subgraph requests. * @returns {Promise} HMToken statistics data. * * **Code example** @@ -389,13 +389,11 @@ export class StatisticsClient { * }); * ``` */ - async getHMTStatistics( - retryConfig?: SubgraphRetryConfig - ): Promise { + async getHMTStatistics(options?: SubgraphOptions): Promise { try { - const { hmtokenStatistics } = await gqlFetchWithRetry<{ + const { hmtokenStatistics } = await customGqlFetch<{ hmtokenStatistics: HMTStatisticsData; - }>(this.subgraphUrl, GET_HMTOKEN_STATISTICS_QUERY, retryConfig); + }>(this.subgraphUrl, GET_HMTOKEN_STATISTICS_QUERY, options); return { totalTransferAmount: BigInt(hmtokenStatistics.totalValueTransfered), @@ -413,7 +411,7 @@ export class StatisticsClient { * **Input parameters** * * @param {IHMTHoldersParams} params HMT Holders params with filters and ordering - * @param {SubgraphRetryConfig} retryConfig Optional configuration for retrying subgraph requests. + * @param {SubgraphOptions} options Optional configuration for subgraph requests. * @returns {Promise} List of HMToken holders. * * **Code example** @@ -435,13 +433,13 @@ export class StatisticsClient { */ async getHMTHolders( params: IHMTHoldersParams = {}, - retryConfig?: SubgraphRetryConfig + options?: SubgraphOptions ): Promise { try { const { address, orderDirection } = params; const query = GET_HOLDERS_QUERY(address); - const { holders } = await gqlFetchWithRetry<{ holders: HMTHolderData[] }>( + const { holders } = await customGqlFetch<{ holders: HMTHolderData[] }>( this.subgraphUrl, query, { @@ -449,7 +447,7 @@ export class StatisticsClient { orderBy: 'balance', orderDirection, }, - retryConfig + options ); return holders.map((holder) => ({ @@ -487,7 +485,7 @@ export class StatisticsClient { * ``` * * @param {IStatisticsFilter} filter Statistics params with duration data - * @param {SubgraphRetryConfig} retryConfig Optional configuration for retrying subgraph requests. + * @param {SubgraphOptions} options Optional configuration for subgraph requests. * @returns {Promise} Daily HMToken statistics data. * * **Code example** @@ -511,7 +509,7 @@ export class StatisticsClient { */ async getHMTDailyData( filter: IStatisticsFilter = {}, - retryConfig?: SubgraphRetryConfig + options?: SubgraphOptions ): Promise { try { const first = @@ -519,7 +517,7 @@ export class StatisticsClient { const skip = filter.skip || 0; const orderDirection = filter.orderDirection || OrderDirection.ASC; - const { eventDayDatas } = await gqlFetchWithRetry<{ + const { eventDayDatas } = await customGqlFetch<{ eventDayDatas: EventDayData[]; }>( this.subgraphUrl, @@ -531,7 +529,7 @@ export class StatisticsClient { first: first, skip: skip, }, - retryConfig + options ); return eventDayDatas.map((eventDayData) => ({ diff --git a/packages/sdk/typescript/human-protocol-sdk/src/transaction.ts b/packages/sdk/typescript/human-protocol-sdk/src/transaction.ts index ef128e300a..0b2014b495 100644 --- a/packages/sdk/typescript/human-protocol-sdk/src/transaction.ts +++ b/packages/sdk/typescript/human-protocol-sdk/src/transaction.ts @@ -16,9 +16,9 @@ import { InternalTransaction, ITransaction, ITransactionsFilter, - SubgraphRetryConfig, + SubgraphOptions, } from './interfaces'; -import { getSubgraphUrl, getUnixTimestamp, gqlFetchWithRetry } from './utils'; +import { getSubgraphUrl, getUnixTimestamp, customGqlFetch } from './utils'; export class TransactionUtils { /** @@ -54,7 +54,7 @@ export class TransactionUtils { * * @param {ChainId} chainId The chain ID. * @param {string} hash The transaction hash. - * @param {SubgraphRetryConfig} retryConfig Optional configuration for retrying subgraph requests. + * @param {SubgraphOptions} options Optional configuration for subgraph requests. * @returns {Promise} - Returns the transaction details or null if not found. * * **Code example** @@ -68,7 +68,7 @@ export class TransactionUtils { public static async getTransaction( chainId: ChainId, hash: string, - retryConfig?: SubgraphRetryConfig + options?: SubgraphOptions ): Promise { if (!ethers.isHexString(hash)) { throw ErrorInvalidHashProvided; @@ -79,7 +79,7 @@ export class TransactionUtils { throw ErrorUnsupportedChainID; } - const { transaction } = await gqlFetchWithRetry<{ + const { transaction } = await customGqlFetch<{ transaction: TransactionData | null; }>( getSubgraphUrl(networkData), @@ -87,7 +87,7 @@ export class TransactionUtils { { hash: hash.toLowerCase(), }, - retryConfig + options ); if (!transaction) return null; @@ -148,7 +148,7 @@ export class TransactionUtils { * ``` * * @param {ITransactionsFilter} filter Filter for the transactions. - * @param {SubgraphRetryConfig} retryConfig Optional configuration for retrying subgraph requests. + * @param {SubgraphOptions} options Optional configuration for subgraph requests. * @returns {Promise} Returns an array with all the transaction details. * * **Code example** @@ -169,7 +169,7 @@ export class TransactionUtils { */ public static async getTransactions( filter: ITransactionsFilter, - retryConfig?: SubgraphRetryConfig + options?: SubgraphOptions ): Promise { if ( (!!filter.startDate || !!filter.endDate) && @@ -188,7 +188,7 @@ export class TransactionUtils { throw ErrorUnsupportedChainID; } - const { transactions } = await gqlFetchWithRetry<{ + const { transactions } = await customGqlFetch<{ transactions: TransactionData[]; }>( getSubgraphUrl(networkData), @@ -209,7 +209,7 @@ export class TransactionUtils { first: first, skip: skip, }, - retryConfig + options ); if (!transactions) { diff --git a/packages/sdk/typescript/human-protocol-sdk/src/utils.ts b/packages/sdk/typescript/human-protocol-sdk/src/utils.ts index 3e6f6ab507..a7156c7c04 100644 --- a/packages/sdk/typescript/human-protocol-sdk/src/utils.ts +++ b/packages/sdk/typescript/human-protocol-sdk/src/utils.ts @@ -16,7 +16,7 @@ import { WarnSubgraphApiKeyNotProvided, } from './error'; import { NetworkData } from './types'; -import { SubgraphRetryConfig } from './interfaces'; +import { SubgraphOptions } from './interfaces'; /** * **Handle and throw the error.* @@ -119,20 +119,20 @@ const sleep = (ms: number): Promise => { /** * Execute a GraphQL request with automatic retry logic for bad indexer errors. - * Only retries if config is provided. + * Only retries if options is provided. */ -export const gqlFetchWithRetry = async ( +export const customGqlFetch = async ( url: string, query: any, variables?: any, - config?: SubgraphRetryConfig + options?: SubgraphOptions ): Promise => { - if (!config) { + if (!options) { return await gqlFetch(url, query, variables); } - const maxRetries = config.maxRetries ?? 3; - const baseDelay = config.baseDelay ?? 1000; + const maxRetries = options.maxRetries ?? 3; + const baseDelay = options.baseDelay ?? 1000; let lastError: any; diff --git a/packages/sdk/typescript/human-protocol-sdk/src/worker.ts b/packages/sdk/typescript/human-protocol-sdk/src/worker.ts index 4483a792ec..6d37ec10bc 100644 --- a/packages/sdk/typescript/human-protocol-sdk/src/worker.ts +++ b/packages/sdk/typescript/human-protocol-sdk/src/worker.ts @@ -4,8 +4,8 @@ import { ChainId, OrderDirection } from './enums'; import { ErrorInvalidAddress, ErrorUnsupportedChainID } from './error'; import { WorkerData } from './graphql'; import { GET_WORKER_QUERY, GET_WORKERS_QUERY } from './graphql/queries/worker'; -import { IWorker, IWorkersFilter, SubgraphRetryConfig } from './interfaces'; -import { getSubgraphUrl, gqlFetchWithRetry } from './utils'; +import { IWorker, IWorkersFilter, SubgraphOptions } from './interfaces'; +import { getSubgraphUrl, customGqlFetch } from './utils'; export class WorkerUtils { /** @@ -13,7 +13,7 @@ export class WorkerUtils { * * @param {ChainId} chainId The chain ID. * @param {string} address The worker address. - * @param {SubgraphRetryConfig} retryConfig Optional configuration for retrying subgraph requests. + * @param {SubgraphOptions} options Optional configuration for subgraph requests. * @returns {Promise} - Returns the worker details or null if not found. * * **Code example** @@ -27,7 +27,7 @@ export class WorkerUtils { public static async getWorker( chainId: ChainId, address: string, - retryConfig?: SubgraphRetryConfig + options?: SubgraphOptions ): Promise { const networkData = NETWORKS[chainId]; @@ -38,7 +38,7 @@ export class WorkerUtils { throw ErrorInvalidAddress; } - const { worker } = await gqlFetchWithRetry<{ + const { worker } = await customGqlFetch<{ worker: WorkerData | null; }>( getSubgraphUrl(networkData), @@ -46,7 +46,7 @@ export class WorkerUtils { { address: address.toLowerCase(), }, - retryConfig + options ); if (!worker) return null; @@ -80,7 +80,7 @@ export class WorkerUtils { * ``` * * @param {IWorkersFilter} filter Filter for the workers. - * @param {SubgraphRetryConfig} retryConfig Optional configuration for retrying subgraph requests. + * @param {SubgraphOptions} options Optional configuration for subgraph requests. * @returns {Promise} Returns an array with all the worker details. * * **Code example** @@ -98,7 +98,7 @@ export class WorkerUtils { */ public static async getWorkers( filter: IWorkersFilter, - retryConfig?: SubgraphRetryConfig + options?: SubgraphOptions ): Promise { const first = filter.first !== undefined ? Math.min(filter.first, 1000) : 10; @@ -114,7 +114,7 @@ export class WorkerUtils { throw ErrorInvalidAddress; } - const { workers } = await gqlFetchWithRetry<{ + const { workers } = await customGqlFetch<{ workers: WorkerData[]; }>( getSubgraphUrl(networkData), @@ -126,7 +126,7 @@ export class WorkerUtils { orderBy: orderBy, orderDirection: orderDirection, }, - retryConfig + options ); if (!workers) { diff --git a/packages/sdk/typescript/human-protocol-sdk/test/utils.test.ts b/packages/sdk/typescript/human-protocol-sdk/test/utils.test.ts index 9ee361dc7a..c419422ae8 100644 --- a/packages/sdk/typescript/human-protocol-sdk/test/utils.test.ts +++ b/packages/sdk/typescript/human-protocol-sdk/test/utils.test.ts @@ -23,7 +23,7 @@ import { import { getSubgraphUrl, getUnixTimestamp, - gqlFetchWithRetry, + customGqlFetch, isIndexerError, isValidJson, isValidUrl, @@ -202,7 +202,7 @@ describe('isIndexerError', () => { }); }); -describe('gqlFetchWithRetry', () => { +describe('customGqlFetch', () => { const mockUrl = 'http://test-subgraph.com'; const mockQuery = 'query { test }'; const mockVariables = { id: '123' }; @@ -218,7 +218,7 @@ describe('gqlFetchWithRetry', () => { .spyOn(gqlFetch, 'default') .mockResolvedValue(expectedResult); - const result = await gqlFetchWithRetry(mockUrl, mockQuery, mockVariables); + const result = await customGqlFetch(mockUrl, mockQuery, mockVariables); expect(gqlFetchSpy).toHaveBeenCalledTimes(1); expect(gqlFetchSpy).toHaveBeenCalledWith(mockUrl, mockQuery, mockVariables); @@ -231,7 +231,7 @@ describe('gqlFetchWithRetry', () => { .spyOn(gqlFetch, 'default') .mockResolvedValue(expectedResult); - const result = await gqlFetchWithRetry(mockUrl, mockQuery, mockVariables, { + const result = await customGqlFetch(mockUrl, mockQuery, mockVariables, { maxRetries: 3, baseDelay: 100, }); @@ -253,7 +253,7 @@ describe('gqlFetchWithRetry', () => { .mockRejectedValueOnce(badIndexerError) .mockResolvedValueOnce(expectedResult); - const result = await gqlFetchWithRetry(mockUrl, mockQuery, mockVariables, { + const result = await customGqlFetch(mockUrl, mockQuery, mockVariables, { maxRetries: 3, baseDelay: 10, }); @@ -269,7 +269,7 @@ describe('gqlFetchWithRetry', () => { .mockRejectedValue(regularError); await expect( - gqlFetchWithRetry(mockUrl, mockQuery, mockVariables, { + customGqlFetch(mockUrl, mockQuery, mockVariables, { maxRetries: 3, baseDelay: 10, }) @@ -289,7 +289,7 @@ describe('gqlFetchWithRetry', () => { .mockRejectedValue(badIndexerError); await expect( - gqlFetchWithRetry(mockUrl, mockQuery, mockVariables, { + customGqlFetch(mockUrl, mockQuery, mockVariables, { maxRetries: 2, baseDelay: 10, }) @@ -309,7 +309,7 @@ describe('gqlFetchWithRetry', () => { .mockRejectedValue(badIndexerError); await expect( - gqlFetchWithRetry(mockUrl, mockQuery, mockVariables, { baseDelay: 10 }) + customGqlFetch(mockUrl, mockQuery, mockVariables, { baseDelay: 10 }) ).rejects.toEqual(badIndexerError); expect(gqlFetchSpy).toHaveBeenCalledTimes(4); @@ -326,7 +326,7 @@ describe('gqlFetchWithRetry', () => { .mockRejectedValue(badIndexerError); await expect( - gqlFetchWithRetry(mockUrl, mockQuery, mockVariables, { + customGqlFetch(mockUrl, mockQuery, mockVariables, { maxRetries: 1, baseDelay: 10, }) From e5d66d9cc749d926dd6fd3179d4db0a635041505 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Francisco=20L=C3=B3pez?= Date: Mon, 10 Nov 2025 11:53:23 +0100 Subject: [PATCH 4/7] rename SubgraphRetryConfig to SubgraphOptions and update related usages across python SDK --- .../sdk/python/human-protocol-sdk/example.py | 6 +-- .../human_protocol_sdk/escrow/escrow_utils.py | 38 +++++++++--------- .../kvstore/kvstore_utils.py | 24 +++++------ .../operator/operator_utils.py | 26 ++++++------ .../staking/staking_utils.py | 10 ++--- .../statistics/statistics_client.py | 40 +++++++++---------- .../transaction/transaction_utils.py | 14 +++---- .../human_protocol_sdk/utils.py | 16 ++++---- .../human_protocol_sdk/worker/worker_utils.py | 14 +++---- 9 files changed, 93 insertions(+), 95 deletions(-) diff --git a/packages/sdk/python/human-protocol-sdk/example.py b/packages/sdk/python/human-protocol-sdk/example.py index 62037b22ea..43a874447c 100644 --- a/packages/sdk/python/human-protocol-sdk/example.py +++ b/packages/sdk/python/human-protocol-sdk/example.py @@ -18,7 +18,7 @@ from human_protocol_sdk.operator import OperatorUtils, OperatorFilter from human_protocol_sdk.agreement import agreement from human_protocol_sdk.staking.staking_utils import StakingUtils -from human_protocol_sdk.utils import SubgraphRetryConfig +from human_protocol_sdk.utils import SubgraphOptions def get_escrow_statistics(statistics_client: StatisticsClient): @@ -164,7 +164,7 @@ def get_escrows(): date_from=datetime.datetime(2023, 5, 8), date_to=datetime.datetime(2023, 6, 8), ), - SubgraphRetryConfig(3, 1000), + SubgraphOptions(3, 1000), ) ) @@ -235,7 +235,7 @@ def get_stakers_example(): order_by="lastDepositTimestamp", order_direction=OrderDirection.ASC, ), - SubgraphRetryConfig(3, 1000), + SubgraphOptions(3, 1000), ) print("Filtered stakers:", len(stakers)) diff --git a/packages/sdk/python/human-protocol-sdk/human_protocol_sdk/escrow/escrow_utils.py b/packages/sdk/python/human-protocol-sdk/human_protocol_sdk/escrow/escrow_utils.py index 00cd379475..5a03bd606d 100644 --- a/packages/sdk/python/human-protocol-sdk/human_protocol_sdk/escrow/escrow_utils.py +++ b/packages/sdk/python/human-protocol-sdk/human_protocol_sdk/escrow/escrow_utils.py @@ -38,7 +38,7 @@ PayoutFilter, ) from human_protocol_sdk.utils import ( - SubgraphRetryConfig, + SubgraphOptions, custom_gql_fetch, ) @@ -220,12 +220,12 @@ class EscrowUtils: @staticmethod def get_escrows( filter: EscrowFilter, - retry_config: Optional[SubgraphRetryConfig] = None, + options: Optional[SubgraphOptions] = None, ) -> List[EscrowData]: """Get an array of escrow addresses based on the specified filter parameters. :param filter: Object containing all the necessary parameters to filter - :param retry_config: Optional retry behaviour for subgraph requests + :param options: Optional config for subgraph requests :return: List of escrows @@ -286,7 +286,7 @@ def get_escrows( "skip": filter.skip, "orderDirection": filter.order_direction.value, }, - retry_config=retry_config, + options=options, ) if ( @@ -338,13 +338,13 @@ def get_escrows( def get_escrow( chain_id: ChainId, escrow_address: str, - retry_config: Optional[SubgraphRetryConfig] = None, + options: Optional[SubgraphOptions] = None, ) -> Optional[EscrowData]: """Returns the escrow for a given address. :param chain_id: Network in which the escrow has been deployed :param escrow_address: Address of the escrow - :param retry_config: Optional retry behaviour for subgraph requests + :param options: Optional config for subgraph requests :return: Escrow data @@ -379,7 +379,7 @@ def get_escrow( params={ "escrowAddress": escrow_address.lower(), }, - retry_config=retry_config, + options=options, ) if ( @@ -423,13 +423,13 @@ def get_escrow( @staticmethod def get_status_events( filter: StatusEventFilter, - retry_config: Optional[SubgraphRetryConfig] = None, + options: Optional[SubgraphOptions] = None, ) -> List[StatusEvent]: """ Retrieve status events for specified networks and statuses within a date range. :param filter: Object containing all the necessary parameters to filter status events. - :param retry_config: Optional retry behaviour for subgraph requests + :param options: Optional config for subgraph requests :return List[StatusEvent]: List of status events matching the query parameters. @@ -458,7 +458,7 @@ def get_status_events( "skip": filter.skip, "orderDirection": filter.order_direction.value, }, - retry_config=retry_config, + options=options, ) if ( @@ -486,13 +486,13 @@ def get_status_events( @staticmethod def get_payouts( filter: PayoutFilter, - retry_config: Optional[SubgraphRetryConfig] = None, + options: Optional[SubgraphOptions] = None, ) -> List[Payout]: """ Fetch payouts from the subgraph based on the provided filter. :param filter: Object containing all the necessary parameters to filter payouts. - :param retry_config: Optional retry behaviour for subgraph requests + :param options: Optional config for subgraph requests :return List[Payout]: List of payouts matching the query parameters. @@ -524,7 +524,7 @@ def get_payouts( "skip": filter.skip, "orderDirection": filter.order_direction.value, }, - retry_config=retry_config, + options=options, ) if ( @@ -553,13 +553,13 @@ def get_payouts( @staticmethod def get_cancellation_refunds( filter: CancellationRefundFilter, - retry_config: Optional[SubgraphRetryConfig] = None, + options: Optional[SubgraphOptions] = None, ) -> List[CancellationRefund]: """ Fetch cancellation refunds from the subgraph based on the provided filter. :param filter: Object containing all the necessary parameters to filter cancellation refunds. - :param retry_config: Optional retry behaviour for subgraph requests + :param options: Optional config for subgraph requests :return List[CancellationRefund]: List of cancellation refunds matching the query parameters. @@ -591,7 +591,7 @@ def get_cancellation_refunds( "skip": filter.skip, "orderDirection": filter.order_direction.value, }, - retry_config=retry_config, + options=options, ) if ( @@ -623,14 +623,14 @@ def get_cancellation_refunds( def get_cancellation_refund( chain_id: ChainId, escrow_address: str, - retry_config: Optional[SubgraphRetryConfig] = None, + options: Optional[SubgraphOptions] = None, ) -> CancellationRefund: """ Returns the cancellation refund for a given escrow address. :param chain_id: Network in which the escrow has been deployed :param escrow_address: Address of the escrow - :param retry_config: Optional retry behaviour for subgraph requests + :param options: Optional config for subgraph requests :return: CancellationRefund data or None @@ -664,7 +664,7 @@ def get_cancellation_refund( { "escrowAddress": escrow_address.lower(), }, - retry_config=retry_config, + options=options, ) if ( diff --git a/packages/sdk/python/human-protocol-sdk/human_protocol_sdk/kvstore/kvstore_utils.py b/packages/sdk/python/human-protocol-sdk/human_protocol_sdk/kvstore/kvstore_utils.py index b4e12001d6..fdb371ddf2 100644 --- a/packages/sdk/python/human-protocol-sdk/human_protocol_sdk/kvstore/kvstore_utils.py +++ b/packages/sdk/python/human-protocol-sdk/human_protocol_sdk/kvstore/kvstore_utils.py @@ -29,7 +29,7 @@ import requests from human_protocol_sdk.constants import NETWORKS, ChainId, KVStoreKeys -from human_protocol_sdk.utils import SubgraphRetryConfig, custom_gql_fetch +from human_protocol_sdk.utils import SubgraphOptions, custom_gql_fetch from human_protocol_sdk.kvstore.kvstore_client import KVStoreClientError @@ -57,13 +57,13 @@ class KVStoreUtils: def get_kvstore_data( chain_id: ChainId, address: str, - retry_config: Optional[SubgraphRetryConfig] = None, + options: Optional[SubgraphOptions] = None, ) -> Optional[List[KVStoreData]]: """Returns the KVStore data for a given address. :param chain_id: Network in which the KVStore data has been deployed :param address: Address of the KVStore - :param retry_config: Optional retry behaviour for subgraph requests + :param options: Optional config for subgraph requests :return: List of KVStore data @@ -96,7 +96,7 @@ def get_kvstore_data( params={ "address": address.lower(), }, - retry_config=retry_config, + options=options, ) if ( @@ -118,14 +118,14 @@ def get( chain_id: ChainId, address: str, key: str, - retry_config: Optional[SubgraphRetryConfig] = None, + options: Optional[SubgraphOptions] = None, ) -> str: """Gets the value of a key-value pair in the contract. :param chain_id: Network in which the KVStore data has been deployed :param address: The Ethereum address associated with the key-value pair :param key: The key of the key-value pair to get - :param retry_config: Optional retry behaviour for subgraph requests + :param options: Optional config for subgraph requests :return: The value of the key-value pair if it exists @@ -158,7 +158,7 @@ def get( "address": address.lower(), "key": key, }, - retry_config=retry_config, + options=options, ) if ( @@ -176,14 +176,14 @@ def get_file_url_and_verify_hash( chain_id: ChainId, address: str, key: Optional[str] = "url", - retry_config: Optional[SubgraphRetryConfig] = None, + options: Optional[SubgraphOptions] = None, ) -> str: """Gets the URL value of the given entity, and verify its hash. :param chain_id: Network in which the KVStore data has been deployed :param address: Address from which to get the URL value. :param key: Configurable URL key. `url` by default. - :param retry_config: Optional retry behaviour for subgraph requests + :param options: Optional config for subgraph requests :return url: The URL value of the given address if exists, and the content is valid @@ -203,10 +203,8 @@ def get_file_url_and_verify_hash( if not Web3.is_address(address): raise KVStoreClientError(f"Invalid address: {address}") - url = KVStoreUtils.get(chain_id, address, key, retry_config=retry_config) - hash = KVStoreUtils.get( - chain_id, address, key + "_hash", retry_config=retry_config - ) + url = KVStoreUtils.get(chain_id, address, key, options=options) + hash = KVStoreUtils.get(chain_id, address, key + "_hash", options=options) if len(url) == 0: return url diff --git a/packages/sdk/python/human-protocol-sdk/human_protocol_sdk/operator/operator_utils.py b/packages/sdk/python/human-protocol-sdk/human_protocol_sdk/operator/operator_utils.py index 429625bbfe..c4332ea4fb 100644 --- a/packages/sdk/python/human-protocol-sdk/human_protocol_sdk/operator/operator_utils.py +++ b/packages/sdk/python/human-protocol-sdk/human_protocol_sdk/operator/operator_utils.py @@ -25,7 +25,7 @@ from human_protocol_sdk.constants import NETWORKS, ChainId, OrderDirection from human_protocol_sdk.gql.reward import get_reward_added_events_query -from human_protocol_sdk.utils import SubgraphRetryConfig, custom_gql_fetch +from human_protocol_sdk.utils import SubgraphOptions, custom_gql_fetch from web3 import Web3 LOG = logging.getLogger("human_protocol_sdk.operator") @@ -200,12 +200,12 @@ class OperatorUtils: @staticmethod def get_operators( filter: OperatorFilter, - retry_config: Optional[SubgraphRetryConfig] = None, + options: Optional[SubgraphOptions] = None, ) -> List[OperatorData]: """Get operators data of the protocol. :param filter: Operator filter - :param retry_config: Optional retry behaviour for subgraph requests + :param options: Optional config for subgraph requests :return: List of operators data @@ -241,7 +241,7 @@ def get_operators( "first": filter.first, "skip": filter.skip, }, - retry_config=retry_config, + options=options, ) if ( @@ -288,13 +288,13 @@ def get_operators( def get_operator( chain_id: ChainId, operator_address: str, - retry_config: Optional[SubgraphRetryConfig] = None, + options: Optional[SubgraphOptions] = None, ) -> Optional[OperatorData]: """Gets the operator details. :param chain_id: Network in which the operator exists :param operator_address: Address of the operator - :param retry_config: Optional retry behaviour for subgraph requests + :param options: Optional config for subgraph requests :return: Operator data if exists, otherwise None @@ -325,7 +325,7 @@ def get_operator( network, query=get_operator_query, params={"address": operator_address.lower()}, - retry_config=retry_config, + options=options, ) if ( @@ -367,14 +367,14 @@ def get_reputation_network_operators( chain_id: ChainId, address: str, role: Optional[str] = None, - retry_config: Optional[SubgraphRetryConfig] = None, + options: Optional[SubgraphOptions] = None, ) -> List[OperatorData]: """Get the reputation network operators of the specified address. :param chain_id: Network in which the reputation network exists :param address: Address of the reputation oracle :param role: (Optional) Role of the operator - :param retry_config: Optional retry behaviour for subgraph requests + :param options: Optional config for subgraph requests :return: Returns an array of operator details @@ -405,7 +405,7 @@ def get_reputation_network_operators( network, query=get_reputation_network_query(role), params={"address": address.lower(), "role": role}, - retry_config=retry_config, + options=options, ) if ( @@ -452,13 +452,13 @@ def get_reputation_network_operators( def get_rewards_info( chain_id: ChainId, slasher: str, - retry_config: Optional[SubgraphRetryConfig] = None, + options: Optional[SubgraphOptions] = None, ) -> List[RewardData]: """Get rewards of the given slasher. :param chain_id: Network in which the slasher exists :param slasher: Address of the slasher - :param retry_config: Optional retry behaviour for subgraph requests + :param options: Optional config for subgraph requests :return: List of rewards info @@ -487,7 +487,7 @@ def get_rewards_info( network, query=get_reward_added_events_query, params={"slasherAddress": slasher.lower()}, - retry_config=retry_config, + options=options, ) if ( diff --git a/packages/sdk/python/human-protocol-sdk/human_protocol_sdk/staking/staking_utils.py b/packages/sdk/python/human-protocol-sdk/human_protocol_sdk/staking/staking_utils.py index 09bc34c864..74915e278b 100644 --- a/packages/sdk/python/human-protocol-sdk/human_protocol_sdk/staking/staking_utils.py +++ b/packages/sdk/python/human-protocol-sdk/human_protocol_sdk/staking/staking_utils.py @@ -30,7 +30,7 @@ from typing import List, Optional from human_protocol_sdk.constants import NETWORKS, ChainId from human_protocol_sdk.filter import StakersFilter -from human_protocol_sdk.utils import SubgraphRetryConfig, custom_gql_fetch +from human_protocol_sdk.utils import SubgraphOptions, custom_gql_fetch from human_protocol_sdk.gql.staking import get_staker_query, get_stakers_query @@ -65,7 +65,7 @@ class StakingUtils: def get_staker( chain_id: ChainId, address: str, - retry_config: Optional[SubgraphRetryConfig] = None, + options: Optional[SubgraphOptions] = None, ) -> Optional[StakerData]: network = NETWORKS.get(chain_id) if not network: @@ -75,7 +75,7 @@ def get_staker( network, query=get_staker_query(), params={"id": address.lower()}, - retry_config=retry_config, + options=options, ) if ( not data @@ -100,7 +100,7 @@ def get_staker( @staticmethod def get_stakers( filter: StakersFilter, - retry_config: Optional[SubgraphRetryConfig] = None, + options: Optional[SubgraphOptions] = None, ) -> List[StakerData]: network_data = NETWORKS.get(filter.chain_id) if not network_data: @@ -123,7 +123,7 @@ def get_stakers( "first": filter.first, "skip": filter.skip, }, - retry_config=retry_config, + options=options, ) if ( not data diff --git a/packages/sdk/python/human-protocol-sdk/human_protocol_sdk/statistics/statistics_client.py b/packages/sdk/python/human-protocol-sdk/human_protocol_sdk/statistics/statistics_client.py index e251120303..b78f927418 100644 --- a/packages/sdk/python/human-protocol-sdk/human_protocol_sdk/statistics/statistics_client.py +++ b/packages/sdk/python/human-protocol-sdk/human_protocol_sdk/statistics/statistics_client.py @@ -22,7 +22,7 @@ from human_protocol_sdk.constants import ChainId, NETWORKS -from human_protocol_sdk.utils import SubgraphRetryConfig, custom_gql_fetch +from human_protocol_sdk.utils import SubgraphOptions, custom_gql_fetch from human_protocol_sdk.filter import StatisticsFilter LOG = logging.getLogger("human_protocol_sdk.statistics") @@ -292,12 +292,12 @@ def __init__(self, chain_id: ChainId = ChainId.POLYGON_AMOY): def get_escrow_statistics( self, filter: StatisticsFilter = StatisticsFilter(), - retry_config: Optional[SubgraphRetryConfig] = None, + options: Optional[SubgraphOptions] = None, ) -> EscrowStatistics: """Get escrow statistics data for the given date range. :param filter: Object containing the date range - :param retry_config: Optional retry behaviour for subgraph requests + :param options: Optional config for subgraph requests :return: Escrow statistics data @@ -329,7 +329,7 @@ def get_escrow_statistics( escrow_statistics_data = custom_gql_fetch( self.network, query=get_escrow_statistics_query, - retry_config=retry_config, + options=options, ) escrow_statistics = escrow_statistics_data["data"]["escrowStatistics"] @@ -343,7 +343,7 @@ def get_escrow_statistics( "skip": filter.skip, "orderDirection": filter.order_direction.value, }, - retry_config=retry_config, + options=options, ) event_day_datas = event_day_datas_data["data"]["eventDayDatas"] @@ -375,12 +375,12 @@ def get_escrow_statistics( def get_worker_statistics( self, filter: StatisticsFilter = StatisticsFilter(), - retry_config: Optional[SubgraphRetryConfig] = None, + options: Optional[SubgraphOptions] = None, ) -> WorkerStatistics: """Get worker statistics data for the given date range. :param filter: Object containing the date range - :param retry_config: Optional retry behaviour for subgraph requests + :param options: Optional config for subgraph requests :return: Worker statistics data @@ -417,7 +417,7 @@ def get_worker_statistics( "skip": filter.skip, "orderDirection": filter.order_direction.value, }, - retry_config=retry_config, + options=options, ) event_day_datas = event_day_datas_data["data"]["eventDayDatas"] @@ -436,12 +436,12 @@ def get_worker_statistics( def get_payment_statistics( self, filter: StatisticsFilter = StatisticsFilter(), - retry_config: Optional[SubgraphRetryConfig] = None, + options: Optional[SubgraphOptions] = None, ) -> PaymentStatistics: """Get payment statistics data for the given date range. :param filter: Object containing the date range - :param retry_config: Optional retry behaviour for subgraph requests + :param options: Optional config for subgraph requests :return: Payment statistics data @@ -479,7 +479,7 @@ def get_payment_statistics( "skip": filter.skip, "orderDirection": filter.order_direction.value, }, - retry_config=retry_config, + options=options, ) event_day_datas = event_day_datas_data["data"]["eventDayDatas"] @@ -505,11 +505,11 @@ def get_payment_statistics( ) def get_hmt_statistics( - self, retry_config: Optional[SubgraphRetryConfig] = None + self, options: Optional[SubgraphOptions] = None ) -> HMTStatistics: """Get HMT statistics data. - :param retry_config: Optional retry behaviour for subgraph requests + :param options: Optional config for subgraph requests :return: HMT statistics data @@ -531,7 +531,7 @@ def get_hmt_statistics( hmtoken_statistics_data = custom_gql_fetch( self.network, query=get_hmtoken_statistics_query, - retry_config=retry_config, + options=options, ) hmtoken_statistics = hmtoken_statistics_data["data"]["hmtokenStatistics"] @@ -548,12 +548,12 @@ def get_hmt_statistics( def get_hmt_holders( self, param: HMTHoldersParam = HMTHoldersParam(), - retry_config: Optional[SubgraphRetryConfig] = None, + options: Optional[SubgraphOptions] = None, ) -> List[HMTHolder]: """Get HMT holders data with optional filters and ordering. :param param: Object containing filter and order parameters - :param retry_config: Optional retry behaviour for subgraph requests + :param options: Optional config for subgraph requests :return: List of HMT holders @@ -585,7 +585,7 @@ def get_hmt_holders( "orderBy": "balance", "orderDirection": param.order_direction, }, - retry_config=retry_config, + options=options, ) holders = holders_data["data"]["holders"] @@ -601,12 +601,12 @@ def get_hmt_holders( def get_hmt_daily_data( self, filter: StatisticsFilter = StatisticsFilter(), - retry_config: Optional[SubgraphRetryConfig] = None, + options: Optional[SubgraphOptions] = None, ) -> List[DailyHMTData]: """Get HMT daily statistics data for the given date range. :param filter: Object containing the date range - :param retry_config: Optional retry behaviour for subgraph requests + :param options: Optional config for subgraph requests :return: HMT statistics data @@ -642,7 +642,7 @@ def get_hmt_daily_data( "skip": filter.skip, "orderDirection": filter.order_direction.value, }, - retry_config=retry_config, + options=options, ) event_day_datas = event_day_datas_data["data"]["eventDayDatas"] diff --git a/packages/sdk/python/human-protocol-sdk/human_protocol_sdk/transaction/transaction_utils.py b/packages/sdk/python/human-protocol-sdk/human_protocol_sdk/transaction/transaction_utils.py index 14eb644f74..ee725ff1b0 100644 --- a/packages/sdk/python/human-protocol-sdk/human_protocol_sdk/transaction/transaction_utils.py +++ b/packages/sdk/python/human-protocol-sdk/human_protocol_sdk/transaction/transaction_utils.py @@ -30,7 +30,7 @@ from human_protocol_sdk.constants import NETWORKS, ChainId from web3 import Web3 from human_protocol_sdk.filter import TransactionFilter -from human_protocol_sdk.utils import SubgraphRetryConfig, custom_gql_fetch +from human_protocol_sdk.utils import SubgraphOptions, custom_gql_fetch class InternalTransaction: @@ -98,13 +98,13 @@ class TransactionUtils: @staticmethod def get_transaction( - chain_id: ChainId, hash: str, retry_config: Optional[SubgraphRetryConfig] = None + chain_id: ChainId, hash: str, options: Optional[SubgraphOptions] = None ) -> Optional[TransactionData]: """Returns the transaction for a given hash. :param chain_id: Network in which the transaction was executed :param hash: Hash of the transaction - :param retry_config: Optional retry behaviour for subgraph requests + :param options: Optional config for subgraph requests :return: Transaction data @@ -131,7 +131,7 @@ def get_transaction( network, query=get_transaction_query(), params={"hash": hash.lower()}, - retry_config=retry_config, + options=options, ) if ( not transaction_data @@ -171,13 +171,13 @@ def get_transaction( @staticmethod def get_transactions( - filter: TransactionFilter, retry_config: Optional[SubgraphRetryConfig] = None + filter: TransactionFilter, options: Optional[SubgraphOptions] = None ) -> List[TransactionData]: """Get an array of transactions based on the specified filter parameters. :param filter: Object containing all the necessary parameters to filter (chain_id, from_address, to_address, start_date, end_date, start_block, end_block, method, escrow, token, first, skip, order_direction) - :param retry_config: Optional retry behaviour for subgraph requests + :param options: Optional config for subgraph requests :return: List of transactions @@ -230,7 +230,7 @@ def get_transactions( "skip": filter.skip, "orderDirection": filter.order_direction.value, }, - retry_config=retry_config, + options=options, ) if ( not data diff --git a/packages/sdk/python/human-protocol-sdk/human_protocol_sdk/utils.py b/packages/sdk/python/human-protocol-sdk/human_protocol_sdk/utils.py index cd0ebaaae7..5df6ab4733 100644 --- a/packages/sdk/python/human-protocol-sdk/human_protocol_sdk/utils.py +++ b/packages/sdk/python/human-protocol-sdk/human_protocol_sdk/utils.py @@ -34,8 +34,8 @@ @dataclass -class SubgraphRetryConfig: - """Configuration for subgraph retry logic.""" +class SubgraphOptions: + """Configuration for subgraph logic.""" max_retries: int = 3 base_delay: int = 1000 # milliseconds @@ -78,24 +78,24 @@ def custom_gql_fetch( network: dict, query: str, params: dict = None, - retry_config: Optional[SubgraphRetryConfig] = None, + options: Optional[SubgraphOptions] = None, ): - """Fetch data from the subgraph with optional retry logic for bad indexer errors. + """Fetch data from the subgraph with optional logic. :param network: Network configuration dictionary :param query: GraphQL query string :param params: Query parameters - :param retry_config: Optional retry configuration for bad indexer errors + :param options: Optional subgraph configuration :return: JSON response from the subgraph :raise Exception: If the subgraph query fails """ - if not retry_config: + if not options: return _fetch_subgraph_data(network, query, params) - max_retries = retry_config.max_retries - base_delay = retry_config.base_delay / 1000 + max_retries = options.max_retries + base_delay = options.base_delay / 1000 last_error = None diff --git a/packages/sdk/python/human-protocol-sdk/human_protocol_sdk/worker/worker_utils.py b/packages/sdk/python/human-protocol-sdk/human_protocol_sdk/worker/worker_utils.py index 4eaaf9507c..1bdab5ca14 100644 --- a/packages/sdk/python/human-protocol-sdk/human_protocol_sdk/worker/worker_utils.py +++ b/packages/sdk/python/human-protocol-sdk/human_protocol_sdk/worker/worker_utils.py @@ -4,7 +4,7 @@ from web3 import Web3 from human_protocol_sdk.constants import NETWORKS, ChainId -from human_protocol_sdk.utils import SubgraphRetryConfig, custom_gql_fetch +from human_protocol_sdk.utils import SubgraphOptions, custom_gql_fetch from human_protocol_sdk.filter import WorkerFilter LOG = logging.getLogger("human_protocol_sdk.worker") @@ -49,12 +49,12 @@ class WorkerUtils: @staticmethod def get_workers( filter: WorkerFilter, - retry_config: Optional[SubgraphRetryConfig] = None, + options: Optional[SubgraphOptions] = None, ) -> List[WorkerData]: """Get workers data of the protocol. :param filter: Worker filter - :param retry_config: Optional retry behaviour for subgraph requests + :param options: Optional config for subgraph requests :return: List of workers data """ @@ -76,7 +76,7 @@ def get_workers( "first": filter.first, "skip": filter.skip, }, - retry_config=retry_config, + options=options, ) if ( @@ -105,13 +105,13 @@ def get_workers( def get_worker( chain_id: ChainId, worker_address: str, - retry_config: Optional[SubgraphRetryConfig] = None, + options: Optional[SubgraphOptions] = None, ) -> Optional[WorkerData]: """Gets the worker details. :param chain_id: Network in which the worker exists :param worker_address: Address of the worker - :param retry_config: Optional retry behaviour for subgraph requests + :param options: Optional config for subgraph requests :return: Worker data if exists, otherwise None """ @@ -130,7 +130,7 @@ def get_worker( network, query=get_worker_query(), params={"address": worker_address.lower()}, - retry_config=retry_config, + options=options, ) if ( From 27d698e696f915f1b04ab85ec66d78ea7166fc52 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Francisco=20L=C3=B3pez?= Date: Mon, 10 Nov 2025 12:58:12 +0100 Subject: [PATCH 5/7] Refactor retry configuration to options in test files for consistency --- .../escrow/test_escrow_utils.py | 16 +++++------ .../kvstore/test_kvstore_utils.py | 10 +++---- .../operator/test_operator_utils.py | 28 +++++++++---------- .../staking/test_staking_utils.py | 4 +-- .../statistics/test_statistics_client.py | 14 +++++----- .../test/human_protocol_sdk/test_utils.py | 16 +++++------ .../transaction/test_transaction_utils.py | 8 +++--- .../worker/test_worker_utils.py | 8 +++--- 8 files changed, 52 insertions(+), 52 deletions(-) diff --git a/packages/sdk/python/human-protocol-sdk/test/human_protocol_sdk/escrow/test_escrow_utils.py b/packages/sdk/python/human-protocol-sdk/test/human_protocol_sdk/escrow/test_escrow_utils.py index 2098b8fdf8..3a61638d6b 100644 --- a/packages/sdk/python/human-protocol-sdk/test/human_protocol_sdk/escrow/test_escrow_utils.py +++ b/packages/sdk/python/human-protocol-sdk/test/human_protocol_sdk/escrow/test_escrow_utils.py @@ -51,7 +51,7 @@ def test_get_escrows(self): "createdAt": "1683811973", } - def side_effect(subgraph_url, query, params, retry_config): + def side_effect(subgraph_url, query, params, options): if subgraph_url == NETWORKS[ChainId.POLYGON_AMOY]: return {"data": {"escrows": [mock_escrow]}} @@ -83,7 +83,7 @@ def side_effect(subgraph_url, query, params, retry_config): "skip": 0, "orderDirection": "desc", }, - retry_config=None, + options=None, ) self.assertEqual(len(filtered), 1) self.assertEqual(filtered[0].address, mock_escrow["address"]) @@ -155,7 +155,7 @@ def side_effect(subgraph_url, query, params, retry_config): "skip": 0, "orderDirection": "desc", }, - retry_config=None, + options=None, ) self.assertEqual(len(filtered), 1) self.assertEqual(filtered[0].chain_id, ChainId.POLYGON_AMOY) @@ -206,7 +206,7 @@ def test_get_escrows_with_status_array(self): "createdAt": "1672531200000", } - def side_effect(subgraph_url, query, params, retry_config): + def side_effect(subgraph_url, query, params, options): if subgraph_url == NETWORKS[ChainId.POLYGON_AMOY]: return {"data": {"escrows": [mock_escrow_1, mock_escrow_2]}} @@ -234,7 +234,7 @@ def side_effect(subgraph_url, query, params, retry_config): "skip": 0, "orderDirection": "desc", }, - retry_config=None, + options=None, ) self.assertEqual(len(filtered), 2) self.assertEqual(filtered[0].address, mock_escrow_1["address"]) @@ -287,7 +287,7 @@ def test_get_escrow(self): params={ "escrowAddress": "0x1234567890123456789012345678901234567890", }, - retry_config=None, + options=None, ) self.assertEqual(escrow.chain_id, ChainId.POLYGON_AMOY) self.assertEqual(escrow.address, mock_escrow["address"]) @@ -347,7 +347,7 @@ def test_get_escrow_empty_data(self): params={ "escrowAddress": "0x1234567890123456789012345678901234567890", }, - retry_config=None, + options=None, ) self.assertEqual(escrow, None) @@ -621,7 +621,7 @@ def test_get_cancellation_refunds(self): "txHash": "0xhash1", } - def side_effect(subgraph_url, query, params, retry_config): + def side_effect(subgraph_url, query, params, options): if subgraph_url == NETWORKS[ChainId.POLYGON_AMOY]: return {"data": {"cancellationRefundEvents": [mock_refund]}} diff --git a/packages/sdk/python/human-protocol-sdk/test/human_protocol_sdk/kvstore/test_kvstore_utils.py b/packages/sdk/python/human-protocol-sdk/test/human_protocol_sdk/kvstore/test_kvstore_utils.py index 0a7a5a14d8..2b39eec467 100644 --- a/packages/sdk/python/human-protocol-sdk/test/human_protocol_sdk/kvstore/test_kvstore_utils.py +++ b/packages/sdk/python/human-protocol-sdk/test/human_protocol_sdk/kvstore/test_kvstore_utils.py @@ -51,7 +51,7 @@ def test_get_kvstore_data(self): params={ "address": "0x15d34aaf54267db7d7c367839aaf71a00a2c6a65", }, - retry_config=None, + options=None, ) self.assertEqual(len(kvstores), 2) self.assertEqual(kvstores[0].key, "fee") @@ -77,7 +77,7 @@ def test_get_kvstore_data_empty_data(self): params={ "address": "0x15d34aaf54267db7d7c367839aaf71a00a2c6a65", }, - retry_config=None, + options=None, ) self.assertEqual(kvstores, []) @@ -120,7 +120,7 @@ def test_get(self, mock_function): NETWORKS[ChainId.LOCALHOST], query=get_kvstore_by_address_and_key_query(), params={"address": address, "key": key}, - retry_config=None, + options=None, ) self.assertEqual(result, "1") @@ -157,7 +157,7 @@ def test_get_empty_value(self, mock_function): NETWORKS[ChainId.LOCALHOST], query=get_kvstore_by_address_and_key_query(), params={"address": address, "key": key}, - retry_config=None, + options=None, ) @patch("human_protocol_sdk.kvstore.kvstore_utils.custom_gql_fetch") @@ -186,7 +186,7 @@ def test_get_without_account(self, mock_function): NETWORKS[ChainId.LOCALHOST], query=get_kvstore_by_address_and_key_query(), params={"address": address, "key": key}, - retry_config=None, + options=None, ) self.assertEqual(result, "mock_value") diff --git a/packages/sdk/python/human-protocol-sdk/test/human_protocol_sdk/operator/test_operator_utils.py b/packages/sdk/python/human-protocol-sdk/test/human_protocol_sdk/operator/test_operator_utils.py index af76e1c4e2..126ddda3e6 100644 --- a/packages/sdk/python/human-protocol-sdk/test/human_protocol_sdk/operator/test_operator_utils.py +++ b/packages/sdk/python/human-protocol-sdk/test/human_protocol_sdk/operator/test_operator_utils.py @@ -67,7 +67,7 @@ def test_get_operators(self): "first": filter.first, "skip": filter.skip, }, - retry_config=None, + options=None, ) self.assertEqual(len(operators), 1) @@ -144,7 +144,7 @@ def test_get_operators_when_job_types_is_none(self): "first": filter.first, "skip": filter.skip, }, - retry_config=None, + options=None, ) self.assertEqual(len(operators), 1) @@ -221,7 +221,7 @@ def test_get_operators_when_job_types_is_array(self): "first": filter.first, "skip": filter.skip, }, - retry_config=None, + options=None, ) self.assertEqual(len(operators), 1) @@ -274,7 +274,7 @@ def test_get_operators_empty_data(self): "first": filter.first, "skip": filter.skip, }, - retry_config=None, + options=None, ) self.assertEqual(operators, []) @@ -325,7 +325,7 @@ def test_get_operator(self): NETWORKS[ChainId.POLYGON], query=get_operator_query, params={"address": staker_address}, - retry_config=None, + options=None, ) self.assertNotEqual(operator, None) @@ -394,7 +394,7 @@ def test_get_operator_when_job_types_is_none(self): NETWORKS[ChainId.POLYGON], query=get_operator_query, params={"address": staker_address}, - retry_config=None, + options=None, ) self.assertNotEqual(operator, None) @@ -463,7 +463,7 @@ def test_get_operator_when_job_types_is_array(self): NETWORKS[ChainId.POLYGON], query=get_operator_query, params={"address": staker_address}, - retry_config=None, + options=None, ) self.assertNotEqual(operator, None) @@ -502,7 +502,7 @@ def test_get_operator_empty_data(self): NETWORKS[ChainId.POLYGON], query=get_operator_query, params={"address": staker_address}, - retry_config=None, + options=None, ) self.assertEqual(operator, None) @@ -549,7 +549,7 @@ def test_get_reputation_network_operators(self): NETWORKS[ChainId.POLYGON], query=get_reputation_network_query(None), params={"address": reputation_address, "role": None}, - retry_config=None, + options=None, ) self.assertNotEqual(operators, []) @@ -602,7 +602,7 @@ def test_get_reputation_network_operators_when_job_types_is_none(self): NETWORKS[ChainId.POLYGON], query=get_reputation_network_query(None), params={"address": reputation_address, "role": None}, - retry_config=None, + options=None, ) self.assertNotEqual(operators, []) @@ -655,7 +655,7 @@ def test_get_reputation_network_operators_when_job_types_is_array(self): NETWORKS[ChainId.POLYGON], query=get_reputation_network_query(None), params={"address": reputation_address, "role": None}, - retry_config=None, + options=None, ) self.assertNotEqual(operators, []) @@ -684,7 +684,7 @@ def test_get_reputation_network_operators_empty_data(self): NETWORKS[ChainId.POLYGON], query=get_reputation_network_query(None), params={"address": reputation_address, "role": None}, - retry_config=None, + options=None, ) self.assertEqual(operators, []) @@ -716,7 +716,7 @@ def test_get_rewards_info(self): NETWORKS[ChainId.POLYGON], query=get_reward_added_events_query, params={"slasherAddress": slasher}, - retry_config=None, + options=None, ) self.assertEqual(len(rewards_info), 2) @@ -739,7 +739,7 @@ def test_get_rewards_info_empty_data(self): NETWORKS[ChainId.POLYGON], query=get_reward_added_events_query, params={"slasherAddress": slasher}, - retry_config=None, + options=None, ) self.assertEqual(rewards_info, []) diff --git a/packages/sdk/python/human-protocol-sdk/test/human_protocol_sdk/staking/test_staking_utils.py b/packages/sdk/python/human-protocol-sdk/test/human_protocol_sdk/staking/test_staking_utils.py index 0babf02017..18d8aa0b40 100644 --- a/packages/sdk/python/human-protocol-sdk/test/human_protocol_sdk/staking/test_staking_utils.py +++ b/packages/sdk/python/human-protocol-sdk/test/human_protocol_sdk/staking/test_staking_utils.py @@ -69,7 +69,7 @@ def test_get_stakers(self): "first": 2, "skip": 0, }, - retry_config=None, + options=None, ) self.assertEqual(len(stakers), 2) self.assertIsInstance(stakers[0], StakerData) @@ -161,7 +161,7 @@ def test_get_staker(self): NETWORKS[ChainId.POLYGON_AMOY], query=get_staker_query(), params={"id": "0x123"}, - retry_config=None, + options=None, ) self.assertIsInstance(staker, StakerData) self.assertEqual(staker.id, "1") diff --git a/packages/sdk/python/human-protocol-sdk/test/human_protocol_sdk/statistics/test_statistics_client.py b/packages/sdk/python/human-protocol-sdk/test/human_protocol_sdk/statistics/test_statistics_client.py index ea3e404f6e..2299d296cf 100644 --- a/packages/sdk/python/human-protocol-sdk/test/human_protocol_sdk/statistics/test_statistics_client.py +++ b/packages/sdk/python/human-protocol-sdk/test/human_protocol_sdk/statistics/test_statistics_client.py @@ -65,7 +65,7 @@ def test_get_escrow_statistics(self): mock_function.assert_any_call( NETWORKS[ChainId.LOCALHOST], query=get_escrow_statistics_query, - retry_config=None, + options=None, ) mock_function.assert_any_call( @@ -78,7 +78,7 @@ def test_get_escrow_statistics(self): "skip": 0, "orderDirection": "asc", }, - retry_config=None, + options=None, ) self.assertEqual(escrow_statistics.total_escrows, 1) @@ -130,7 +130,7 @@ def test_get_worker_statistics(self): "skip": 0, "orderDirection": "asc", }, - retry_config=None, + options=None, ) self.assertEqual(len(payment_statistics.daily_workers_data), 1) @@ -176,7 +176,7 @@ def test_get_payment_statistics(self): "skip": 0, "orderDirection": "asc", }, - retry_config=None, + options=None, ) self.assertEqual(len(payment_statistics.daily_payments_data), 1) @@ -215,7 +215,7 @@ def test_get_hmt_statistics(self): mock_function.assert_any_call( NETWORKS[ChainId.LOCALHOST], query=get_hmtoken_statistics_query, - retry_config=None, + options=None, ) self.assertEqual(hmt_statistics.total_transfer_amount, 100) @@ -255,7 +255,7 @@ def test_get_hmt_holders(self): "orderBy": "balance", "orderDirection": param.order_direction, }, - retry_config=None, + options=None, ) self.assertEqual(len(holders), 2) @@ -302,7 +302,7 @@ def test_get_hmt_daily_data(self): "skip": 0, "orderDirection": "asc", }, - retry_config=None, + options=None, ) self.assertEqual(len(hmt_statistics), 1) diff --git a/packages/sdk/python/human-protocol-sdk/test/human_protocol_sdk/test_utils.py b/packages/sdk/python/human-protocol-sdk/test/human_protocol_sdk/test_utils.py index 3fdeb4f973..4bfc829669 100644 --- a/packages/sdk/python/human-protocol-sdk/test/human_protocol_sdk/test_utils.py +++ b/packages/sdk/python/human-protocol-sdk/test/human_protocol_sdk/test_utils.py @@ -3,7 +3,7 @@ from validators import ValidationError from human_protocol_sdk.utils import ( - SubgraphRetryConfig, + SubgraphOptions, custom_gql_fetch, is_indexer_error, validate_url, @@ -54,7 +54,7 @@ def setUp(self): self.query = "query Test" self.variables = {"foo": "bar"} - def test_returns_response_without_retry_config(self): + def test_returns_response_without_options(self): expected = {"data": {"ok": True}} with patch( "human_protocol_sdk.utils._fetch_subgraph_data", @@ -66,7 +66,7 @@ def test_returns_response_without_retry_config(self): mock_fetch.assert_called_once_with(self.network, self.query, self.variables) def test_retries_on_indexer_error_and_succeeds(self): - retry_config = SubgraphRetryConfig(max_retries=2, base_delay=100) + options = SubgraphOptions(max_retries=2, base_delay=100) error = make_graphql_error({"errors": [{"message": "Bad indexers: syncing"}]}) with patch( @@ -74,7 +74,7 @@ def test_retries_on_indexer_error_and_succeeds(self): side_effect=[error, {"data": {"ok": True}}], ) as mock_fetch, patch("human_protocol_sdk.utils.time.sleep") as mock_sleep: result = custom_gql_fetch( - self.network, self.query, self.variables, retry_config=retry_config + self.network, self.query, self.variables, options=options ) self.assertEqual(result, {"data": {"ok": True}}) @@ -82,14 +82,14 @@ def test_retries_on_indexer_error_and_succeeds(self): mock_sleep.assert_called_once() def test_raises_immediately_on_non_indexer_error(self): - retry_config = SubgraphRetryConfig(max_retries=3, base_delay=50) + options = SubgraphOptions(max_retries=3, base_delay=50) with patch( "human_protocol_sdk.utils._fetch_subgraph_data", side_effect=Exception("network failure"), ) as mock_fetch, patch("human_protocol_sdk.utils.time.sleep") as mock_sleep: with self.assertRaises(Exception) as ctx: custom_gql_fetch( - self.network, self.query, self.variables, retry_config=retry_config + self.network, self.query, self.variables, options=options ) self.assertIn("network failure", str(ctx.exception)) @@ -97,7 +97,7 @@ def test_raises_immediately_on_non_indexer_error(self): mock_sleep.assert_not_called() def test_raises_after_exhausting_retries(self): - retry_config = SubgraphRetryConfig(max_retries=2, base_delay=10) + options = SubgraphOptions(max_retries=2, base_delay=10) errors = [ make_graphql_error({"errors": [{"message": "bad indexers: stalled"}]}) for _ in range(3) @@ -109,7 +109,7 @@ def test_raises_after_exhausting_retries(self): ) as mock_fetch, patch("human_protocol_sdk.utils.time.sleep"): with self.assertRaises(Exception) as ctx: custom_gql_fetch( - self.network, self.query, self.variables, retry_config=retry_config + self.network, self.query, self.variables, options=options ) self.assertTrue(is_indexer_error(ctx.exception)) diff --git a/packages/sdk/python/human-protocol-sdk/test/human_protocol_sdk/transaction/test_transaction_utils.py b/packages/sdk/python/human-protocol-sdk/test/human_protocol_sdk/transaction/test_transaction_utils.py index 278b63f776..d475b63ae1 100644 --- a/packages/sdk/python/human-protocol-sdk/test/human_protocol_sdk/transaction/test_transaction_utils.py +++ b/packages/sdk/python/human-protocol-sdk/test/human_protocol_sdk/transaction/test_transaction_utils.py @@ -82,7 +82,7 @@ def test_get_transactions(self): "token": None, "method": None, }, - retry_config=None, + options=None, ) self.assertEqual(len(transactions), 2) self.assertEqual(transactions[0].chain_id, ChainId.POLYGON_AMOY) @@ -118,7 +118,7 @@ def test_get_transactions_empty_response(self): "token": None, "method": None, }, - retry_config=None, + options=None, ) self.assertEqual(len(transactions), 0) @@ -183,7 +183,7 @@ def test_get_transaction(self): params={ "hash": "0x1234567890123456789012345678901234567890123456789012345678901234" }, - retry_config=None, + options=None, ) self.assertIsNotNone(transaction) self.assertEqual(transaction.chain_id, ChainId.POLYGON_AMOY) @@ -212,7 +212,7 @@ def test_get_transaction_empty_data(self): NETWORKS[ChainId.POLYGON_AMOY], query=ANY, params={"hash": "transaction_hash"}, - retry_config=None, + options=None, ) self.assertIsNone(transaction) diff --git a/packages/sdk/python/human-protocol-sdk/test/human_protocol_sdk/worker/test_worker_utils.py b/packages/sdk/python/human-protocol-sdk/test/human_protocol_sdk/worker/test_worker_utils.py index 3c49aac753..ea44241c19 100644 --- a/packages/sdk/python/human-protocol-sdk/test/human_protocol_sdk/worker/test_worker_utils.py +++ b/packages/sdk/python/human-protocol-sdk/test/human_protocol_sdk/worker/test_worker_utils.py @@ -47,7 +47,7 @@ def test_get_workers(self): "orderBy": "totalHMTAmountReceived", "orderDirection": "asc", }, - retry_config=None, + options=None, ) self.assertEqual(len(workers), 2) self.assertEqual(workers[0].id, "worker1") @@ -76,7 +76,7 @@ def test_get_workers_empty_response(self): "orderBy": "payoutCount", "orderDirection": "desc", }, - retry_config=None, + options=None, ) self.assertEqual(len(workers), 0) @@ -108,7 +108,7 @@ def test_get_worker(self): NETWORKS[ChainId.POLYGON_AMOY], query=get_worker_query(), params={"address": "0x1234567890123456789012345678901234567890"}, - retry_config=None, + options=None, ) self.assertIsNotNone(worker) self.assertEqual(worker.id, "worker1") @@ -130,7 +130,7 @@ def test_get_worker_empty_data(self): NETWORKS[ChainId.POLYGON_AMOY], query=get_worker_query(), params={"address": "0x1234567890123456789012345678901234567890"}, - retry_config=None, + options=None, ) self.assertIsNone(worker) From 4e9e51a198079805cab608c76f11cbf29b4d2404 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Francisco=20L=C3=B3pez?= Date: Tue, 11 Nov 2025 10:34:35 +0100 Subject: [PATCH 6/7] Reduce retry delay calculation in custom GQL fetch --- .../sdk/python/human-protocol-sdk/human_protocol_sdk/utils.py | 2 +- packages/sdk/typescript/human-protocol-sdk/src/utils.ts | 3 +-- 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/packages/sdk/python/human-protocol-sdk/human_protocol_sdk/utils.py b/packages/sdk/python/human-protocol-sdk/human_protocol_sdk/utils.py index 5df6ab4733..012580420e 100644 --- a/packages/sdk/python/human-protocol-sdk/human_protocol_sdk/utils.py +++ b/packages/sdk/python/human-protocol-sdk/human_protocol_sdk/utils.py @@ -108,7 +108,7 @@ def custom_gql_fetch( if not is_indexer_error(error): break - delay = base_delay * (2**attempt) + delay = base_delay * attempt time.sleep(delay) raise last_error diff --git a/packages/sdk/typescript/human-protocol-sdk/src/utils.ts b/packages/sdk/typescript/human-protocol-sdk/src/utils.ts index a7156c7c04..d4cf870634 100644 --- a/packages/sdk/typescript/human-protocol-sdk/src/utils.ts +++ b/packages/sdk/typescript/human-protocol-sdk/src/utils.ts @@ -151,8 +151,7 @@ export const customGqlFetch = async ( throw error; } - const delay = baseDelay * Math.pow(2, attempt); - + const delay = baseDelay * attempt; await sleep(delay); } } From 7dedb21c3a8505bb5c964482055b41ac2aa67e0f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Francisco=20L=C3=B3pez?= Date: Wed, 12 Nov 2025 14:11:03 +0100 Subject: [PATCH 7/7] Refactor subgraph options to enforce complete retry configuration and update related tests --- .../human_protocol_sdk/escrow/escrow_utils.py | 1 - .../human_protocol_sdk/utils.py | 18 ++++++++++--- .../test/human_protocol_sdk/test_utils.py | 12 +++++++++ .../human-protocol-sdk/src/error.ts | 7 +++++ .../human-protocol-sdk/src/interfaces.ts | 4 +-- .../human-protocol-sdk/src/utils.ts | 15 +++++++---- .../human-protocol-sdk/test/utils.test.ts | 26 +++++++++++-------- 7 files changed, 60 insertions(+), 23 deletions(-) diff --git a/packages/sdk/python/human-protocol-sdk/human_protocol_sdk/escrow/escrow_utils.py b/packages/sdk/python/human-protocol-sdk/human_protocol_sdk/escrow/escrow_utils.py index 5a03bd606d..5a5531e591 100644 --- a/packages/sdk/python/human-protocol-sdk/human_protocol_sdk/escrow/escrow_utils.py +++ b/packages/sdk/python/human-protocol-sdk/human_protocol_sdk/escrow/escrow_utils.py @@ -24,7 +24,6 @@ ------ """ -from datetime import datetime import logging from typing import List, Optional diff --git a/packages/sdk/python/human-protocol-sdk/human_protocol_sdk/utils.py b/packages/sdk/python/human-protocol-sdk/human_protocol_sdk/utils.py index 012580420e..55d0ea905b 100644 --- a/packages/sdk/python/human-protocol-sdk/human_protocol_sdk/utils.py +++ b/packages/sdk/python/human-protocol-sdk/human_protocol_sdk/utils.py @@ -3,7 +3,7 @@ import os import time import re -from typing import Tuple, Optional, Dict, Any +from typing import Tuple, Optional from dataclasses import dataclass import requests @@ -37,8 +37,8 @@ class SubgraphOptions: """Configuration for subgraph logic.""" - max_retries: int = 3 - base_delay: int = 1000 # milliseconds + max_retries: Optional[int] = None + base_delay: Optional[int] = None # milliseconds def is_indexer_error(error: Exception) -> bool: @@ -94,7 +94,17 @@ def custom_gql_fetch( if not options: return _fetch_subgraph_data(network, query, params) - max_retries = options.max_retries + if ( + options.max_retries is not None + and options.base_delay is None + or options.max_retries is None + and options.base_delay is not None + ): + raise ValueError( + "Retry configuration must include both max_retries and base_delay" + ) + + max_retries = int(options.max_retries) base_delay = options.base_delay / 1000 last_error = None diff --git a/packages/sdk/python/human-protocol-sdk/test/human_protocol_sdk/test_utils.py b/packages/sdk/python/human-protocol-sdk/test/human_protocol_sdk/test_utils.py index 4bfc829669..cb41e620ef 100644 --- a/packages/sdk/python/human-protocol-sdk/test/human_protocol_sdk/test_utils.py +++ b/packages/sdk/python/human-protocol-sdk/test/human_protocol_sdk/test_utils.py @@ -81,6 +81,18 @@ def test_retries_on_indexer_error_and_succeeds(self): self.assertEqual(mock_fetch.call_count, 2) mock_sleep.assert_called_once() + def test_raises_when_retry_options_incomplete(self): + options = SubgraphOptions(max_retries=2) + + with patch("human_protocol_sdk.utils._fetch_subgraph_data") as mock_fetch: + with self.assertRaises(ValueError) as ctx: + custom_gql_fetch( + self.network, self.query, self.variables, options=options + ) + + self.assertIn("max_retries", str(ctx.exception)) + mock_fetch.assert_not_called() + def test_raises_immediately_on_non_indexer_error(self): options = SubgraphOptions(max_retries=3, base_delay=50) with patch( diff --git a/packages/sdk/typescript/human-protocol-sdk/src/error.ts b/packages/sdk/typescript/human-protocol-sdk/src/error.ts index a4c4d9fae7..40af09be57 100644 --- a/packages/sdk/typescript/human-protocol-sdk/src/error.ts +++ b/packages/sdk/typescript/human-protocol-sdk/src/error.ts @@ -306,6 +306,13 @@ export const ErrorBulkPayOutVersion = new Error( 'Invalid bulkPayOut parameters for the contract version of the specified escrow address' ); +/** + * @constant {Error} - Retry configuration is missing required parameters. + */ +export const ErrorRetryParametersMissing = new Error( + 'Retry configuration must include both maxRetries and baseDelay' +); + /** * @constant {Warning} - Possible version mismatch. */ diff --git a/packages/sdk/typescript/human-protocol-sdk/src/interfaces.ts b/packages/sdk/typescript/human-protocol-sdk/src/interfaces.ts index 716c6e2d64..9a204751ce 100644 --- a/packages/sdk/typescript/human-protocol-sdk/src/interfaces.ts +++ b/packages/sdk/typescript/human-protocol-sdk/src/interfaces.ts @@ -317,8 +317,8 @@ export interface IEscrowWithdraw { * Configuration options for subgraph requests with retry logic. */ export interface SubgraphOptions { - /** Maximum number of retry attempts (default: 3) */ + /** Maximum number of retry attempts */ maxRetries?: number; - /** Base delay between retries in milliseconds (default: 1000) */ + /** Base delay between retries in milliseconds */ baseDelay?: number; } diff --git a/packages/sdk/typescript/human-protocol-sdk/src/utils.ts b/packages/sdk/typescript/human-protocol-sdk/src/utils.ts index d4cf870634..cfe35456b7 100644 --- a/packages/sdk/typescript/human-protocol-sdk/src/utils.ts +++ b/packages/sdk/typescript/human-protocol-sdk/src/utils.ts @@ -7,6 +7,7 @@ import { SUBGRAPH_API_KEY_PLACEHOLDER } from './constants'; import { ChainId } from './enums'; import { ContractExecutionError, + ErrorRetryParametersMissing, EthereumError, InvalidArgumentError, NonceExpired, @@ -131,19 +132,23 @@ export const customGqlFetch = async ( return await gqlFetch(url, query, variables); } - const maxRetries = options.maxRetries ?? 3; - const baseDelay = options.baseDelay ?? 1000; + if ( + (options.maxRetries && options.baseDelay === undefined) || + (options.baseDelay && options.maxRetries === undefined) + ) { + throw ErrorRetryParametersMissing; + } let lastError: any; - for (let attempt = 0; attempt <= maxRetries; attempt++) { + for (let attempt = 0; attempt <= (options.maxRetries as number); attempt++) { try { const result = await gqlFetch(url, query, variables); return result; } catch (error) { lastError = error; - if (attempt === maxRetries) { + if (attempt === options.maxRetries) { throw error; } @@ -151,7 +156,7 @@ export const customGqlFetch = async ( throw error; } - const delay = baseDelay * attempt; + const delay = (options.baseDelay as number) * attempt; await sleep(delay); } } diff --git a/packages/sdk/typescript/human-protocol-sdk/test/utils.test.ts b/packages/sdk/typescript/human-protocol-sdk/test/utils.test.ts index c419422ae8..6c6a0428b8 100644 --- a/packages/sdk/typescript/human-protocol-sdk/test/utils.test.ts +++ b/packages/sdk/typescript/human-protocol-sdk/test/utils.test.ts @@ -19,6 +19,7 @@ import { ReplacementUnderpriced, TransactionReplaced, WarnSubgraphApiKeyNotProvided, + ErrorRetryParametersMissing, } from '../src/error'; import { getSubgraphUrl, @@ -298,21 +299,24 @@ describe('customGqlFetch', () => { expect(gqlFetchSpy).toHaveBeenCalledTimes(3); }); - test('uses default values for missing maxRetries', async () => { - const badIndexerError = { - response: { - errors: [{ message: 'bad indexers: {0x123: Timeout}' }], - }, - }; - const gqlFetchSpy = vi - .spyOn(gqlFetch, 'default') - .mockRejectedValue(badIndexerError); + test('throws when retry options are incomplete', async () => { + const gqlFetchSpy = vi.spyOn(gqlFetch, 'default'); await expect( customGqlFetch(mockUrl, mockQuery, mockVariables, { baseDelay: 10 }) - ).rejects.toEqual(badIndexerError); + ).rejects.toBe(ErrorRetryParametersMissing); + + expect(gqlFetchSpy).not.toHaveBeenCalled(); + }); + + test('throws when only maxRetries is provided', async () => { + const gqlFetchSpy = vi.spyOn(gqlFetch, 'default'); + + await expect( + customGqlFetch(mockUrl, mockQuery, mockVariables, { maxRetries: 1 }) + ).rejects.toBe(ErrorRetryParametersMissing); - expect(gqlFetchSpy).toHaveBeenCalledTimes(4); + expect(gqlFetchSpy).not.toHaveBeenCalled(); }); test('uses custom maxRetries when provided', async () => {