From 97e06f120a5b56a5814a31c9bc7716145b51c768 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Francisco=20L=C3=B3pez?= Date: Thu, 12 Feb 2026 10:05:16 +0100 Subject: [PATCH 1/3] Add SDK transaction timeout constant and apply it across services --- .../src/common/constants/index.ts | 1 + .../src/modules/job/job.service.ts | 3 +++ .../server/src/common/constants/index.ts | 1 + .../server/src/modules/job/job.service.ts | 5 ++++- .../server/src/common/constants/index.ts | 1 + .../server/src/modules/abuse/abuse.service.ts | 2 ++ .../escrow-completion.service.ts | 17 +++++++++++++---- .../server/src/modules/user/user.service.ts | 9 +++++++-- 8 files changed, 32 insertions(+), 7 deletions(-) diff --git a/packages/apps/fortune/recording-oracle/src/common/constants/index.ts b/packages/apps/fortune/recording-oracle/src/common/constants/index.ts index 993982eef8..16af0bbea6 100644 --- a/packages/apps/fortune/recording-oracle/src/common/constants/index.ts +++ b/packages/apps/fortune/recording-oracle/src/common/constants/index.ts @@ -1 +1,2 @@ export const HEADER_SIGNATURE_KEY = 'human-signature'; +export const SDK_TX_TIMEOUT_MS = 90000; diff --git a/packages/apps/fortune/recording-oracle/src/modules/job/job.service.ts b/packages/apps/fortune/recording-oracle/src/modules/job/job.service.ts index 09210cec12..d4c7b1544b 100644 --- a/packages/apps/fortune/recording-oracle/src/modules/job/job.service.ts +++ b/packages/apps/fortune/recording-oracle/src/modules/job/job.service.ts @@ -11,6 +11,7 @@ import * as Minio from 'minio'; import logger from '../../logger'; import { Web3ConfigService } from '../../common/config/web3-config.service'; +import { SDK_TX_TIMEOUT_MS } from '../../common/constants'; import { ErrorJob } from '../../common/constants/errors'; import { JobRequestType, SolutionError } from '../../common/enums/job'; import { EventType } from '../../common/enums/webhook'; @@ -202,6 +203,7 @@ export class JobService { jobSolutionUploaded.url, jobSolutionUploaded.hash, !lastProcessedSolution?.error ? amountToReserve : 0n, + { timeoutMs: SDK_TX_TIMEOUT_MS }, ); if ( @@ -307,6 +309,7 @@ export class JobService { intermediateResultsURL, intermediateResultsHash, 0n, + { timeoutMs: SDK_TX_TIMEOUT_MS }, ); let reputationOracleWebhook: string | null = null; diff --git a/packages/apps/job-launcher/server/src/common/constants/index.ts b/packages/apps/job-launcher/server/src/common/constants/index.ts index 72ada66583..44ae65b6fd 100644 --- a/packages/apps/job-launcher/server/src/common/constants/index.ts +++ b/packages/apps/job-launcher/server/src/common/constants/index.ts @@ -7,6 +7,7 @@ export const COINGECKO_API_URL = 'https://api.coingecko.com/api/v3/simple/price'; export const DEFAULT_MAX_RETRY_COUNT = 3; export const TX_CONFIRMATION_TRESHOLD = 1; +export const SDK_TX_TIMEOUT_MS = 90000; export const JWT_PREFIX = 'bearer '; diff --git a/packages/apps/job-launcher/server/src/modules/job/job.service.ts b/packages/apps/job-launcher/server/src/modules/job/job.service.ts index 5984349463..eb7e0c9a7f 100644 --- a/packages/apps/job-launcher/server/src/modules/job/job.service.ts +++ b/packages/apps/job-launcher/server/src/modules/job/job.service.ts @@ -15,7 +15,7 @@ import { } from 'class-validator'; import { ethers } from 'ethers'; import { ServerConfigService } from '../../common/config/server-config.service'; -import { CANCEL_JOB_STATUSES } from '../../common/constants'; +import { CANCEL_JOB_STATUSES, SDK_TX_TIMEOUT_MS } from '../../common/constants'; import { ErrorEscrow, ErrorJob, @@ -347,6 +347,7 @@ export class JobService { escrowConfig, { gasPrice: await this.web3Service.calculateGasPrice(jobEntity.chainId), + timeoutMs: SDK_TX_TIMEOUT_MS, }, ); @@ -611,6 +612,7 @@ export class JobService { try { await (escrowClient as any).requestCancellation(escrowAddress!, { gasPrice: await this.web3Service.calculateGasPrice(chainId), + timeoutMs: SDK_TX_TIMEOUT_MS, }); } catch (error: any) { this.logger.warn( @@ -624,6 +626,7 @@ export class JobService { ); await (escrowClient as any).cancel(escrowAddress!, { gasPrice: await this.web3Service.calculateGasPrice(chainId), + timeoutMs: SDK_TX_TIMEOUT_MS, }); } } diff --git a/packages/apps/reputation-oracle/server/src/common/constants/index.ts b/packages/apps/reputation-oracle/server/src/common/constants/index.ts index 02d82593be..607d597e19 100644 --- a/packages/apps/reputation-oracle/server/src/common/constants/index.ts +++ b/packages/apps/reputation-oracle/server/src/common/constants/index.ts @@ -29,3 +29,4 @@ export const SUPPORTED_EXCHANGES_INFO: readonly SupportedExchangeInfo[] = [ ] as const; export const DEFAULT_TIMEOUT_MS = 5000; +export const SDK_TX_TIMEOUT_MS = 90000; diff --git a/packages/apps/reputation-oracle/server/src/modules/abuse/abuse.service.ts b/packages/apps/reputation-oracle/server/src/modules/abuse/abuse.service.ts index bec00cc32b..60ab4ca90d 100644 --- a/packages/apps/reputation-oracle/server/src/modules/abuse/abuse.service.ts +++ b/packages/apps/reputation-oracle/server/src/modules/abuse/abuse.service.ts @@ -7,6 +7,7 @@ import { import { Injectable } from '@nestjs/common'; import { ethers } from 'ethers'; +import { SDK_TX_TIMEOUT_MS } from '@/common/constants'; import { ServerConfigService } from '@/config'; import { isDuplicatedError } from '@/database'; import logger from '@/logger'; @@ -72,6 +73,7 @@ export class AbuseService { data.staker, data.escrowAddress, BigInt(ethers.parseUnits(data.amount.toString(), 18)), + { timeoutMs: SDK_TX_TIMEOUT_MS }, ); } diff --git a/packages/apps/reputation-oracle/server/src/modules/escrow-completion/escrow-completion.service.ts b/packages/apps/reputation-oracle/server/src/modules/escrow-completion/escrow-completion.service.ts index 205e915fcc..f79cfbc53c 100644 --- a/packages/apps/reputation-oracle/server/src/modules/escrow-completion/escrow-completion.service.ts +++ b/packages/apps/reputation-oracle/server/src/modules/escrow-completion/escrow-completion.service.ts @@ -14,7 +14,10 @@ import stringify from 'json-stable-stringify'; import _ from 'lodash'; import { v4 as uuidv4 } from 'uuid'; -import { BACKOFF_INTERVAL_SECONDS } from '@/common/constants'; +import { + BACKOFF_INTERVAL_SECONDS, + SDK_TX_TIMEOUT_MS, +} from '@/common/constants'; import { JobManifest, JobRequestType } from '@/common/types'; import { ServerConfigService } from '@/config'; import { isDuplicatedError } from '@/database'; @@ -243,10 +246,16 @@ export class EscrowCompletionService { const gasPrice = await this.web3Service.calculateGasPrice(chainId); if (escrowStatus === EscrowStatus.ToCancel) { - await escrowClient.cancel(escrowAddress, { gasPrice }); + await escrowClient.cancel(escrowAddress, { + gasPrice, + timeoutMs: SDK_TX_TIMEOUT_MS, + }); escrowStatus = EscrowStatus.Cancelled; } else { - await escrowClient.complete(escrowAddress, { gasPrice }); + await escrowClient.complete(escrowAddress, { + gasPrice, + timeoutMs: SDK_TX_TIMEOUT_MS, + }); escrowStatus = EscrowStatus.Complete; } @@ -453,7 +462,7 @@ export class EscrowCompletionService { try { const transactionResponse = await signer.sendTransaction(rawTransaction); - await transactionResponse.wait(); + await transactionResponse.wait(undefined, SDK_TX_TIMEOUT_MS); await this.escrowPayoutsBatchRepository.deleteOne(payoutsBatch); } catch (error) { diff --git a/packages/apps/reputation-oracle/server/src/modules/user/user.service.ts b/packages/apps/reputation-oracle/server/src/modules/user/user.service.ts index 331a6003c0..b3e5b76931 100644 --- a/packages/apps/reputation-oracle/server/src/modules/user/user.service.ts +++ b/packages/apps/reputation-oracle/server/src/modules/user/user.service.ts @@ -1,6 +1,7 @@ import { KVStoreClient, KVStoreUtils } from '@human-protocol/sdk'; import { Injectable } from '@nestjs/common'; +import { SDK_TX_TIMEOUT_MS } from '@/common/constants'; import { SignatureType } from '@/common/enums'; import { KycStatus, UserRole } from '@/common/enums'; import { Web3ConfigService } from '@/config'; @@ -222,7 +223,9 @@ export class UserService { ); } - await kvstore.set(operatorUser.evmAddress, OperatorStatus.ACTIVE); + await kvstore.set(operatorUser.evmAddress, OperatorStatus.ACTIVE, { + timeoutMs: SDK_TX_TIMEOUT_MS, + }); } async disableOperator(userId: number, signature: string): Promise { @@ -266,7 +269,9 @@ export class UserService { ); } - await kvstore.set(operatorUser.evmAddress, OperatorStatus.INACTIVE); + await kvstore.set(operatorUser.evmAddress, OperatorStatus.INACTIVE, { + timeoutMs: SDK_TX_TIMEOUT_MS, + }); } async registrationInExchangeOracle( From 9df804ecb9540727d206762d5e5d5b4f9f0242fc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Francisco=20L=C3=B3pez?= Date: Thu, 12 Feb 2026 10:12:14 +0100 Subject: [PATCH 2/3] Add transaction timeout to job and escrow completion tests --- .../job-launcher/server/src/modules/job/job.service.spec.ts | 5 +++-- .../escrow-completion/escrow-completion.service.spec.ts | 2 ++ .../server/src/modules/user/user.service.spec.ts | 4 +++- 3 files changed, 8 insertions(+), 3 deletions(-) diff --git a/packages/apps/job-launcher/server/src/modules/job/job.service.spec.ts b/packages/apps/job-launcher/server/src/modules/job/job.service.spec.ts index 42f7b763b0..3119ad8c06 100644 --- a/packages/apps/job-launcher/server/src/modules/job/job.service.spec.ts +++ b/packages/apps/job-launcher/server/src/modules/job/job.service.spec.ts @@ -15,6 +15,7 @@ import { Test } from '@nestjs/testing'; import { ethers, ZeroAddress } from 'ethers'; import { createSignerMock } from '../../../test/fixtures/web3'; import { ServerConfigService } from '../../common/config/server-config.service'; +import { SDK_TX_TIMEOUT_MS } from '../../common/constants'; import { ErrorEscrow, ErrorJob } from '../../common/constants/errors'; import { TOKEN_ADDRESSES } from '../../common/constants/tokens'; import { @@ -791,7 +792,7 @@ describe('JobService', () => { manifest: jobEntity.manifestUrl, manifestHash: jobEntity.manifestHash, }), - { gasPrice: 1n }, + { gasPrice: 1n, timeoutMs: SDK_TX_TIMEOUT_MS }, ); expect(result.status).toBe(JobStatus.LAUNCHED); expect(result.escrowAddress).toBe(escrowAddress); @@ -864,7 +865,7 @@ describe('JobService', () => { expectedWeiAmount, jobEntity.userId.toString(), expect.any(Object), - { gasPrice: 1n }, + { gasPrice: 1n, timeoutMs: SDK_TX_TIMEOUT_MS }, ); expect(mockJobRepository.updateOne).not.toHaveBeenCalled(); diff --git a/packages/apps/reputation-oracle/server/src/modules/escrow-completion/escrow-completion.service.spec.ts b/packages/apps/reputation-oracle/server/src/modules/escrow-completion/escrow-completion.service.spec.ts index e13aa05392..15b8301cb7 100644 --- a/packages/apps/reputation-oracle/server/src/modules/escrow-completion/escrow-completion.service.spec.ts +++ b/packages/apps/reputation-oracle/server/src/modules/escrow-completion/escrow-completion.service.spec.ts @@ -25,6 +25,7 @@ import { Test } from '@nestjs/testing'; import stringify from 'json-stable-stringify'; import _ from 'lodash'; +import { SDK_TX_TIMEOUT_MS } from '@/common/constants'; import { CvatJobType, FortuneJobType } from '@/common/enums'; import { ServerConfigService } from '@/config'; import { ReputationService } from '@/modules/reputation'; @@ -1043,6 +1044,7 @@ describe('EscrowCompletionService', () => { paidPayoutsRecord.escrowAddress, { gasPrice: mockGasPrice, + timeoutMs: SDK_TX_TIMEOUT_MS, }, ); expect(mockReputationService.assessEscrowParties).toHaveBeenCalledTimes( diff --git a/packages/apps/reputation-oracle/server/src/modules/user/user.service.spec.ts b/packages/apps/reputation-oracle/server/src/modules/user/user.service.spec.ts index be2744f64a..606e3e8505 100644 --- a/packages/apps/reputation-oracle/server/src/modules/user/user.service.spec.ts +++ b/packages/apps/reputation-oracle/server/src/modules/user/user.service.spec.ts @@ -5,6 +5,7 @@ import { createMock } from '@golevelup/ts-jest'; import { KVStoreClient, KVStoreUtils } from '@human-protocol/sdk'; import { Test } from '@nestjs/testing'; +import { SDK_TX_TIMEOUT_MS } from '@/common/constants'; import { SignatureType } from '@/common/enums'; import { UserRole, KycStatus } from '@/common/enums'; import { Web3ConfigService } from '@/config'; @@ -30,7 +31,6 @@ import { } from './user.error'; import { UserRepository } from './user.repository'; import { UserService, OperatorStatus } from './user.service'; - const mockUserRepository = createMock(); const mockSiteKeyRepository = createMock(); const mockHCaptchaService = createMock(); @@ -472,6 +472,7 @@ describe('UserService', () => { expect(mockedKVStoreSet).toHaveBeenCalledWith( user.evmAddress, OperatorStatus.ACTIVE, + { timeoutMs: SDK_TX_TIMEOUT_MS }, ); }); }); @@ -554,6 +555,7 @@ describe('UserService', () => { expect(mockedKVStoreSet).toHaveBeenCalledWith( user.evmAddress, OperatorStatus.INACTIVE, + { timeoutMs: SDK_TX_TIMEOUT_MS }, ); }); }); From 87ada758be12bb75988b95f869159b9037a7c897 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Francisco=20L=C3=B3pez?= Date: Thu, 12 Feb 2026 11:51:48 +0100 Subject: [PATCH 3/3] Add SDK transaction timeout configuration across services using env variables --- .../src/common/config/env-schema.ts | 1 + .../src/common/config/web3-config.service.ts | 8 ++++++++ .../src/common/constants/index.ts | 1 - .../src/modules/job/job.service.ts | 5 ++--- .../server/src/common/config/env-schema.ts | 1 + .../src/common/config/web3-config.service.ts | 8 ++++++++ .../server/src/common/constants/index.ts | 1 - .../server/src/modules/job/job.service.spec.ts | 10 +++++++--- .../server/src/modules/job/job.service.ts | 10 ++++++---- .../server/src/common/constants/index.ts | 1 - .../server/src/config/env-schema.ts | 1 + .../server/src/config/web3-config.service.ts | 8 ++++++++ .../server/src/modules/abuse/abuse.service.ts | 2 -- .../escrow-completion.service.spec.ts | 14 ++++++++++---- .../escrow-completion.service.ts | 17 +++++++++-------- .../src/modules/user/user.service.spec.ts | 5 ++--- .../server/src/modules/user/user.service.ts | 5 ++--- .../server/src/modules/web3/fixtures/index.ts | 1 + 18 files changed, 66 insertions(+), 33 deletions(-) diff --git a/packages/apps/fortune/recording-oracle/src/common/config/env-schema.ts b/packages/apps/fortune/recording-oracle/src/common/config/env-schema.ts index 7a1a4d6144..865b9f747d 100644 --- a/packages/apps/fortune/recording-oracle/src/common/config/env-schema.ts +++ b/packages/apps/fortune/recording-oracle/src/common/config/env-schema.ts @@ -6,6 +6,7 @@ export const envValidator = Joi.object({ PORT: Joi.string(), // Web3 WEB3_PRIVATE_KEY: Joi.string().required(), + SDK_TX_TIMEOUT_MS: Joi.number(), RPC_URL_POLYGON: Joi.string(), RPC_URL_BSC: Joi.string(), RPC_URL_POLYGON_AMOY: Joi.string(), diff --git a/packages/apps/fortune/recording-oracle/src/common/config/web3-config.service.ts b/packages/apps/fortune/recording-oracle/src/common/config/web3-config.service.ts index 619e9a0444..e740cf11ca 100644 --- a/packages/apps/fortune/recording-oracle/src/common/config/web3-config.service.ts +++ b/packages/apps/fortune/recording-oracle/src/common/config/web3-config.service.ts @@ -12,4 +12,12 @@ export class Web3ConfigService { get privateKey(): string { return this.configService.getOrThrow('WEB3_PRIVATE_KEY'); } + + /** + * Timeout for web3 transactions in milliseconds. + * Default: 60000 (60 seconds) + */ + get txTimeoutMs(): number { + return +this.configService.get('SDK_TX_TIMEOUT_MS', 60000); + } } diff --git a/packages/apps/fortune/recording-oracle/src/common/constants/index.ts b/packages/apps/fortune/recording-oracle/src/common/constants/index.ts index 16af0bbea6..993982eef8 100644 --- a/packages/apps/fortune/recording-oracle/src/common/constants/index.ts +++ b/packages/apps/fortune/recording-oracle/src/common/constants/index.ts @@ -1,2 +1 @@ export const HEADER_SIGNATURE_KEY = 'human-signature'; -export const SDK_TX_TIMEOUT_MS = 90000; diff --git a/packages/apps/fortune/recording-oracle/src/modules/job/job.service.ts b/packages/apps/fortune/recording-oracle/src/modules/job/job.service.ts index d4c7b1544b..9ce3e2b726 100644 --- a/packages/apps/fortune/recording-oracle/src/modules/job/job.service.ts +++ b/packages/apps/fortune/recording-oracle/src/modules/job/job.service.ts @@ -11,7 +11,6 @@ import * as Minio from 'minio'; import logger from '../../logger'; import { Web3ConfigService } from '../../common/config/web3-config.service'; -import { SDK_TX_TIMEOUT_MS } from '../../common/constants'; import { ErrorJob } from '../../common/constants/errors'; import { JobRequestType, SolutionError } from '../../common/enums/job'; import { EventType } from '../../common/enums/webhook'; @@ -203,7 +202,7 @@ export class JobService { jobSolutionUploaded.url, jobSolutionUploaded.hash, !lastProcessedSolution?.error ? amountToReserve : 0n, - { timeoutMs: SDK_TX_TIMEOUT_MS }, + { timeoutMs: this.web3ConfigService.txTimeoutMs }, ); if ( @@ -309,7 +308,7 @@ export class JobService { intermediateResultsURL, intermediateResultsHash, 0n, - { timeoutMs: SDK_TX_TIMEOUT_MS }, + { timeoutMs: this.web3ConfigService.txTimeoutMs }, ); let reputationOracleWebhook: string | null = null; diff --git a/packages/apps/job-launcher/server/src/common/config/env-schema.ts b/packages/apps/job-launcher/server/src/common/config/env-schema.ts index 2dca07e7b4..195f9becb1 100644 --- a/packages/apps/job-launcher/server/src/common/config/env-schema.ts +++ b/packages/apps/job-launcher/server/src/common/config/env-schema.ts @@ -26,6 +26,7 @@ export const envValidator = Joi.object({ // Web3 WEB3_ENV: Joi.string(), WEB3_PRIVATE_KEY: Joi.string().required(), + SDK_TX_TIMEOUT_MS: Joi.number(), GAS_PRICE_MULTIPLIER: Joi.number(), APPROVE_AMOUNT_USD: Joi.number(), REPUTATION_ORACLE_ADDRESS: Joi.string().required(), diff --git a/packages/apps/job-launcher/server/src/common/config/web3-config.service.ts b/packages/apps/job-launcher/server/src/common/config/web3-config.service.ts index b2f0f8a5d0..9c5806f0f4 100644 --- a/packages/apps/job-launcher/server/src/common/config/web3-config.service.ts +++ b/packages/apps/job-launcher/server/src/common/config/web3-config.service.ts @@ -96,4 +96,12 @@ export class Web3ConfigService { get approveAmountUsd(): number { return this.configService.get('APPROVE_AMOUNT_USD', 0); } + + /** + * Timeout for web3 transactions in milliseconds. + * Default: 60000 (60 seconds) + */ + get txTimeoutMs(): number { + return +this.configService.get('SDK_TX_TIMEOUT_MS', 60000); + } } diff --git a/packages/apps/job-launcher/server/src/common/constants/index.ts b/packages/apps/job-launcher/server/src/common/constants/index.ts index 44ae65b6fd..72ada66583 100644 --- a/packages/apps/job-launcher/server/src/common/constants/index.ts +++ b/packages/apps/job-launcher/server/src/common/constants/index.ts @@ -7,7 +7,6 @@ export const COINGECKO_API_URL = 'https://api.coingecko.com/api/v3/simple/price'; export const DEFAULT_MAX_RETRY_COUNT = 3; export const TX_CONFIRMATION_TRESHOLD = 1; -export const SDK_TX_TIMEOUT_MS = 90000; export const JWT_PREFIX = 'bearer '; diff --git a/packages/apps/job-launcher/server/src/modules/job/job.service.spec.ts b/packages/apps/job-launcher/server/src/modules/job/job.service.spec.ts index 3119ad8c06..ece16d7eed 100644 --- a/packages/apps/job-launcher/server/src/modules/job/job.service.spec.ts +++ b/packages/apps/job-launcher/server/src/modules/job/job.service.spec.ts @@ -15,7 +15,7 @@ import { Test } from '@nestjs/testing'; import { ethers, ZeroAddress } from 'ethers'; import { createSignerMock } from '../../../test/fixtures/web3'; import { ServerConfigService } from '../../common/config/server-config.service'; -import { SDK_TX_TIMEOUT_MS } from '../../common/constants'; +import { Web3ConfigService } from '../../common/config/web3-config.service'; import { ErrorEscrow, ErrorJob } from '../../common/constants/errors'; import { TOKEN_ADDRESSES } from '../../common/constants/tokens'; import { @@ -83,6 +83,9 @@ const mockRateService = createMock(); const mockRoutingProtocolService = createMock(); const mockManifestService = createMock(); const mockWhitelistService = createMock(); +const mockWeb3ConfigService = { + txTimeoutMs: faker.number.int({ min: 30000, max: 120000 }), +}; const mockedEscrowClient = jest.mocked(EscrowClient); const mockedEscrowUtils = jest.mocked(EscrowUtils); @@ -130,6 +133,7 @@ describe('JobService', () => { provide: ManifestService, useValue: mockManifestService, }, + { provide: Web3ConfigService, useValue: mockWeb3ConfigService }, ], }).compile(); @@ -792,7 +796,7 @@ describe('JobService', () => { manifest: jobEntity.manifestUrl, manifestHash: jobEntity.manifestHash, }), - { gasPrice: 1n, timeoutMs: SDK_TX_TIMEOUT_MS }, + { gasPrice: 1n, timeoutMs: mockWeb3ConfigService.txTimeoutMs }, ); expect(result.status).toBe(JobStatus.LAUNCHED); expect(result.escrowAddress).toBe(escrowAddress); @@ -865,7 +869,7 @@ describe('JobService', () => { expectedWeiAmount, jobEntity.userId.toString(), expect.any(Object), - { gasPrice: 1n, timeoutMs: SDK_TX_TIMEOUT_MS }, + { gasPrice: 1n, timeoutMs: mockWeb3ConfigService.txTimeoutMs }, ); expect(mockJobRepository.updateOne).not.toHaveBeenCalled(); diff --git a/packages/apps/job-launcher/server/src/modules/job/job.service.ts b/packages/apps/job-launcher/server/src/modules/job/job.service.ts index eb7e0c9a7f..dda75a1783 100644 --- a/packages/apps/job-launcher/server/src/modules/job/job.service.ts +++ b/packages/apps/job-launcher/server/src/modules/job/job.service.ts @@ -14,8 +14,9 @@ import { validate, } from 'class-validator'; import { ethers } from 'ethers'; +import { Web3ConfigService } from '../../common/config/web3-config.service'; import { ServerConfigService } from '../../common/config/server-config.service'; -import { CANCEL_JOB_STATUSES, SDK_TX_TIMEOUT_MS } from '../../common/constants'; +import { CANCEL_JOB_STATUSES } from '../../common/constants'; import { ErrorEscrow, ErrorJob, @@ -87,6 +88,7 @@ export class JobService { constructor( @Inject(Web3Service) private readonly web3Service: Web3Service, + private readonly web3ConfigService: Web3ConfigService, private readonly jobRepository: JobRepository, private readonly webhookRepository: WebhookRepository, private readonly paymentService: PaymentService, @@ -347,7 +349,7 @@ export class JobService { escrowConfig, { gasPrice: await this.web3Service.calculateGasPrice(jobEntity.chainId), - timeoutMs: SDK_TX_TIMEOUT_MS, + timeoutMs: this.web3ConfigService.txTimeoutMs, }, ); @@ -612,7 +614,7 @@ export class JobService { try { await (escrowClient as any).requestCancellation(escrowAddress!, { gasPrice: await this.web3Service.calculateGasPrice(chainId), - timeoutMs: SDK_TX_TIMEOUT_MS, + timeoutMs: this.web3ConfigService.txTimeoutMs, }); } catch (error: any) { this.logger.warn( @@ -626,7 +628,7 @@ export class JobService { ); await (escrowClient as any).cancel(escrowAddress!, { gasPrice: await this.web3Service.calculateGasPrice(chainId), - timeoutMs: SDK_TX_TIMEOUT_MS, + timeoutMs: this.web3ConfigService.txTimeoutMs, }); } } diff --git a/packages/apps/reputation-oracle/server/src/common/constants/index.ts b/packages/apps/reputation-oracle/server/src/common/constants/index.ts index 607d597e19..02d82593be 100644 --- a/packages/apps/reputation-oracle/server/src/common/constants/index.ts +++ b/packages/apps/reputation-oracle/server/src/common/constants/index.ts @@ -29,4 +29,3 @@ export const SUPPORTED_EXCHANGES_INFO: readonly SupportedExchangeInfo[] = [ ] as const; export const DEFAULT_TIMEOUT_MS = 5000; -export const SDK_TX_TIMEOUT_MS = 90000; diff --git a/packages/apps/reputation-oracle/server/src/config/env-schema.ts b/packages/apps/reputation-oracle/server/src/config/env-schema.ts index 78876e7ec8..c2a55a98d8 100644 --- a/packages/apps/reputation-oracle/server/src/config/env-schema.ts +++ b/packages/apps/reputation-oracle/server/src/config/env-schema.ts @@ -47,6 +47,7 @@ export const envValidator = Joi.object({ // Web3 WEB3_ENV: Joi.string().valid(...Object.values(Web3Network)), WEB3_PRIVATE_KEY: Joi.string().required(), + SDK_TX_TIMEOUT_MS: Joi.number().integer(), GAS_PRICE_MULTIPLIER: Joi.number().positive(), RPC_URL_SEPOLIA: Joi.string().uri({ scheme: ['http', 'https'] }), RPC_URL_POLYGON: Joi.string().uri({ scheme: ['http', 'https'] }), diff --git a/packages/apps/reputation-oracle/server/src/config/web3-config.service.ts b/packages/apps/reputation-oracle/server/src/config/web3-config.service.ts index dbb40d55f4..98cc7b4a0e 100644 --- a/packages/apps/reputation-oracle/server/src/config/web3-config.service.ts +++ b/packages/apps/reputation-oracle/server/src/config/web3-config.service.ts @@ -56,6 +56,14 @@ export class Web3ConfigService { return Number(this.configService.get('GAS_PRICE_MULTIPLIER')) || 1; } + /** + * Timeout for web3 transactions in milliseconds. + * Default: 60000 (60 seconds) + */ + get txTimeoutMs(): number { + return +this.configService.get('SDK_TX_TIMEOUT_MS', 60000); + } + getRpcUrlByChainId(chainId: number): string | undefined { const rpcUrlsByChainId: Record = { [ChainId.POLYGON]: this.configService.get('RPC_URL_POLYGON'), diff --git a/packages/apps/reputation-oracle/server/src/modules/abuse/abuse.service.ts b/packages/apps/reputation-oracle/server/src/modules/abuse/abuse.service.ts index 60ab4ca90d..bec00cc32b 100644 --- a/packages/apps/reputation-oracle/server/src/modules/abuse/abuse.service.ts +++ b/packages/apps/reputation-oracle/server/src/modules/abuse/abuse.service.ts @@ -7,7 +7,6 @@ import { import { Injectable } from '@nestjs/common'; import { ethers } from 'ethers'; -import { SDK_TX_TIMEOUT_MS } from '@/common/constants'; import { ServerConfigService } from '@/config'; import { isDuplicatedError } from '@/database'; import logger from '@/logger'; @@ -73,7 +72,6 @@ export class AbuseService { data.staker, data.escrowAddress, BigInt(ethers.parseUnits(data.amount.toString(), 18)), - { timeoutMs: SDK_TX_TIMEOUT_MS }, ); } diff --git a/packages/apps/reputation-oracle/server/src/modules/escrow-completion/escrow-completion.service.spec.ts b/packages/apps/reputation-oracle/server/src/modules/escrow-completion/escrow-completion.service.spec.ts index 15b8301cb7..f672538392 100644 --- a/packages/apps/reputation-oracle/server/src/modules/escrow-completion/escrow-completion.service.spec.ts +++ b/packages/apps/reputation-oracle/server/src/modules/escrow-completion/escrow-completion.service.spec.ts @@ -25,13 +25,15 @@ import { Test } from '@nestjs/testing'; import stringify from 'json-stable-stringify'; import _ from 'lodash'; -import { SDK_TX_TIMEOUT_MS } from '@/common/constants'; import { CvatJobType, FortuneJobType } from '@/common/enums'; -import { ServerConfigService } from '@/config'; +import { ServerConfigService, Web3ConfigService } from '@/config'; import { ReputationService } from '@/modules/reputation'; import { StorageService } from '@/modules/storage'; import { WalletWithProvider, Web3Service } from '@/modules/web3'; -import { generateTestnetChainId } from '@/modules/web3/fixtures'; +import { + generateTestnetChainId, + mockWeb3ConfigService, +} from '@/modules/web3/fixtures'; import { OutgoingWebhookService } from '@/modules/webhook'; import { createSignerMock, type SignerMock } from '~/test/fixtures/web3'; @@ -100,6 +102,10 @@ describe('EscrowCompletionService', () => { provide: StorageService, useValue: mockStorageService, }, + { + provide: Web3ConfigService, + useValue: mockWeb3ConfigService, + }, { provide: OutgoingWebhookService, useValue: mockOutgoingWebhookService, @@ -1044,7 +1050,7 @@ describe('EscrowCompletionService', () => { paidPayoutsRecord.escrowAddress, { gasPrice: mockGasPrice, - timeoutMs: SDK_TX_TIMEOUT_MS, + timeoutMs: mockWeb3ConfigService.txTimeoutMs, }, ); expect(mockReputationService.assessEscrowParties).toHaveBeenCalledTimes( diff --git a/packages/apps/reputation-oracle/server/src/modules/escrow-completion/escrow-completion.service.ts b/packages/apps/reputation-oracle/server/src/modules/escrow-completion/escrow-completion.service.ts index f79cfbc53c..30654789aa 100644 --- a/packages/apps/reputation-oracle/server/src/modules/escrow-completion/escrow-completion.service.ts +++ b/packages/apps/reputation-oracle/server/src/modules/escrow-completion/escrow-completion.service.ts @@ -14,12 +14,9 @@ import stringify from 'json-stable-stringify'; import _ from 'lodash'; import { v4 as uuidv4 } from 'uuid'; -import { - BACKOFF_INTERVAL_SECONDS, - SDK_TX_TIMEOUT_MS, -} from '@/common/constants'; +import { BACKOFF_INTERVAL_SECONDS } from '@/common/constants'; import { JobManifest, JobRequestType } from '@/common/types'; -import { ServerConfigService } from '@/config'; +import { ServerConfigService, Web3ConfigService } from '@/config'; import { isDuplicatedError } from '@/database'; import logger from '@/logger'; import { ReputationService } from '@/modules/reputation'; @@ -61,6 +58,7 @@ export class EscrowCompletionService { private readonly escrowCompletionRepository: EscrowCompletionRepository, private readonly escrowPayoutsBatchRepository: EscrowPayoutsBatchRepository, private readonly web3Service: Web3Service, + private readonly web3ConfigService: Web3ConfigService, private readonly storageService: StorageService, private readonly outgoingWebhookService: OutgoingWebhookService, private readonly reputationService: ReputationService, @@ -248,13 +246,13 @@ export class EscrowCompletionService { if (escrowStatus === EscrowStatus.ToCancel) { await escrowClient.cancel(escrowAddress, { gasPrice, - timeoutMs: SDK_TX_TIMEOUT_MS, + timeoutMs: this.web3ConfigService.txTimeoutMs, }); escrowStatus = EscrowStatus.Cancelled; } else { await escrowClient.complete(escrowAddress, { gasPrice, - timeoutMs: SDK_TX_TIMEOUT_MS, + timeoutMs: this.web3ConfigService.txTimeoutMs, }); escrowStatus = EscrowStatus.Complete; } @@ -462,7 +460,10 @@ export class EscrowCompletionService { try { const transactionResponse = await signer.sendTransaction(rawTransaction); - await transactionResponse.wait(undefined, SDK_TX_TIMEOUT_MS); + await transactionResponse.wait( + undefined, + this.web3ConfigService.txTimeoutMs, + ); await this.escrowPayoutsBatchRepository.deleteOne(payoutsBatch); } catch (error) { diff --git a/packages/apps/reputation-oracle/server/src/modules/user/user.service.spec.ts b/packages/apps/reputation-oracle/server/src/modules/user/user.service.spec.ts index 606e3e8505..c81002a821 100644 --- a/packages/apps/reputation-oracle/server/src/modules/user/user.service.spec.ts +++ b/packages/apps/reputation-oracle/server/src/modules/user/user.service.spec.ts @@ -5,7 +5,6 @@ import { createMock } from '@golevelup/ts-jest'; import { KVStoreClient, KVStoreUtils } from '@human-protocol/sdk'; import { Test } from '@nestjs/testing'; -import { SDK_TX_TIMEOUT_MS } from '@/common/constants'; import { SignatureType } from '@/common/enums'; import { UserRole, KycStatus } from '@/common/enums'; import { Web3ConfigService } from '@/config'; @@ -472,7 +471,7 @@ describe('UserService', () => { expect(mockedKVStoreSet).toHaveBeenCalledWith( user.evmAddress, OperatorStatus.ACTIVE, - { timeoutMs: SDK_TX_TIMEOUT_MS }, + { timeoutMs: mockWeb3ConfigService.txTimeoutMs }, ); }); }); @@ -555,7 +554,7 @@ describe('UserService', () => { expect(mockedKVStoreSet).toHaveBeenCalledWith( user.evmAddress, OperatorStatus.INACTIVE, - { timeoutMs: SDK_TX_TIMEOUT_MS }, + { timeoutMs: mockWeb3ConfigService.txTimeoutMs }, ); }); }); diff --git a/packages/apps/reputation-oracle/server/src/modules/user/user.service.ts b/packages/apps/reputation-oracle/server/src/modules/user/user.service.ts index b3e5b76931..9c18211859 100644 --- a/packages/apps/reputation-oracle/server/src/modules/user/user.service.ts +++ b/packages/apps/reputation-oracle/server/src/modules/user/user.service.ts @@ -1,7 +1,6 @@ import { KVStoreClient, KVStoreUtils } from '@human-protocol/sdk'; import { Injectable } from '@nestjs/common'; -import { SDK_TX_TIMEOUT_MS } from '@/common/constants'; import { SignatureType } from '@/common/enums'; import { KycStatus, UserRole } from '@/common/enums'; import { Web3ConfigService } from '@/config'; @@ -224,7 +223,7 @@ export class UserService { } await kvstore.set(operatorUser.evmAddress, OperatorStatus.ACTIVE, { - timeoutMs: SDK_TX_TIMEOUT_MS, + timeoutMs: this.web3ConfigService.txTimeoutMs, }); } @@ -270,7 +269,7 @@ export class UserService { } await kvstore.set(operatorUser.evmAddress, OperatorStatus.INACTIVE, { - timeoutMs: SDK_TX_TIMEOUT_MS, + timeoutMs: this.web3ConfigService.txTimeoutMs, }); } diff --git a/packages/apps/reputation-oracle/server/src/modules/web3/fixtures/index.ts b/packages/apps/reputation-oracle/server/src/modules/web3/fixtures/index.ts index fde77f55ed..fe1eff7c1f 100644 --- a/packages/apps/reputation-oracle/server/src/modules/web3/fixtures/index.ts +++ b/packages/apps/reputation-oracle/server/src/modules/web3/fixtures/index.ts @@ -18,6 +18,7 @@ export const mockWeb3ConfigService: Omit = { operatorAddress: testWallet.address, network: Web3Network.TESTNET, gasPriceMultiplier: faker.number.int({ min: 1, max: 42 }), + txTimeoutMs: faker.number.int({ min: 30000, max: 120000 }), reputationNetworkChainId: generateTestnetChainId(), getRpcUrlByChainId: () => faker.internet.url(), };