From 6abf6de6b3ffc4bc273bbe5d6bb9436a9edea3e7 Mon Sep 17 00:00:00 2001 From: Dmitry Nechay Date: Mon, 30 Jun 2025 17:30:25 +0300 Subject: [PATCH 1/7] [HUMAN App] feat: fix oracle url (#3432) --- packages/apps/human-app/server/package.json | 5 ++ .../apps/human-app/server/src/app.module.ts | 1 + .../config/environment-config.service.ts | 14 +++++ .../src/common/guards/strategy/jwt.http.ts | 1 - .../integrations/kv-store/kv-store.gateway.ts | 24 ++++---- .../kv-store/spec/kv-store.gateway.spec.ts | 56 ++++++++++--------- .../jobs-discovery/jobs-discovery.service.ts | 7 ++- yarn.lock | 5 ++ 8 files changed, 76 insertions(+), 37 deletions(-) diff --git a/packages/apps/human-app/server/package.json b/packages/apps/human-app/server/package.json index aeb54182fc..393f154574 100644 --- a/packages/apps/human-app/server/package.json +++ b/packages/apps/human-app/server/package.json @@ -31,10 +31,12 @@ "@nestjs/common": "^10.2.7", "@nestjs/config": "^3.1.1", "@nestjs/core": "^10.3.10", + "@nestjs/passport": "^10.0.0", "@nestjs/platform-express": "^10.3.10", "@nestjs/schedule": "^4.0.1", "@nestjs/swagger": "^7.4.2", "@nestjs/terminus": "^11.0.0", + "@types/passport-jwt": "^4.0.1", "axios": "^1.7.2", "cache-manager": "^5.4.0", "cache-manager-redis-yet": "^5.1.5", @@ -45,6 +47,8 @@ "jsonwebtoken": "^9.0.2", "jwt-decode": "^4.0.0", "lodash": "^4.17.21", + "passport": "^0.7.0", + "passport-jwt": "^4.0.1", "reflect-metadata": "^0.2.2", "rxjs": "^7.2.0" }, @@ -57,6 +61,7 @@ "@types/jsonwebtoken": "^9.0.7", "@types/lodash": "^4.17.14", "@types/node": "22.10.5", + "@types/passport": "^0", "@types/supertest": "^2.0.15", "@typescript-eslint/eslint-plugin": "^5.0.0", "@typescript-eslint/parser": "^5.0.0", diff --git a/packages/apps/human-app/server/src/app.module.ts b/packages/apps/human-app/server/src/app.module.ts index 6099bfa28e..a693e40889 100644 --- a/packages/apps/human-app/server/src/app.module.ts +++ b/packages/apps/human-app/server/src/app.module.ts @@ -96,6 +96,7 @@ const JOI_BOOLEAN_STRING_SCHEMA = Joi.string().valid('true', 'false'); CACHE_TTL_DAILY_HMT_SPENT: Joi.number(), CACHE_TTL_HCAPTCHA_USER_STATS: Joi.number(), CACHE_TTL_ORACLE_DISCOVERY: Joi.number(), + CACHE_TTL_ORACLE_AVAILABLE_JOBS: Joi.number(), JOB_ASSIGNMENTS_DATA_RETENTION_DAYS: Joi.number(), CACHE_TTL_EXCHANGE_ORACLE_URL: Joi.number(), CACHE_TTL_EXCHANGE_ORACLE_REGISTRATION_NEEDED: Joi.number(), diff --git a/packages/apps/human-app/server/src/common/config/environment-config.service.ts b/packages/apps/human-app/server/src/common/config/environment-config.service.ts index af1cd562f6..54980729d8 100644 --- a/packages/apps/human-app/server/src/common/config/environment-config.service.ts +++ b/packages/apps/human-app/server/src/common/config/environment-config.service.ts @@ -6,6 +6,7 @@ const DEFAULT_CACHE_TTL_HCAPTCHA_USER_STATS = 12 * 60 * 60; const DEFAULT_CACHE_TTL_ORACLE_STATS = 12 * 60 * 60; const DEFAULT_CACHE_TTL_USER_STATS = 15 * 60; const DEFAULT_CACHE_TTL_ORACLE_DISCOVERY = 24 * 60 * 60; +const DEFAULT_CACHE_TTL_ORACLE_AVAILABLE_JOBS = 2 * 60; const DEFAULT_JOB_ASSIGNMENTS_DATA_RETENTION_DAYS = 45; const DEFAULT_CACHE_TTL_DAILY_HMT_SPENT = 24 * 60 * 60; const DEFAULT_CORS_ALLOWED_ORIGIN = 'http://localhost:5173'; @@ -164,6 +165,19 @@ export class EnvironmentConfigService { ); } + /** + * The cache time-to-live (TTL) for oracle's available jobs. + * Default: 2 minutes + */ + get cacheTtlOracleAvailableJobs(): number { + return ( + this.configService.get( + 'CACHE_TTL_ORACLE_AVAILABLE_JOBS', + DEFAULT_CACHE_TTL_ORACLE_AVAILABLE_JOBS, + ) * 1000 + ); + } + /** * Number of days without updates assignments data is retained. * Default: 45 days diff --git a/packages/apps/human-app/server/src/common/guards/strategy/jwt.http.ts b/packages/apps/human-app/server/src/common/guards/strategy/jwt.http.ts index 56e70eb47e..1a06cd60ee 100644 --- a/packages/apps/human-app/server/src/common/guards/strategy/jwt.http.ts +++ b/packages/apps/human-app/server/src/common/guards/strategy/jwt.http.ts @@ -30,7 +30,6 @@ export class JwtHttpStrategy extends PassportStrategy(Strategy, 'jwt-http') { ); done(null, pubKey); } catch (error) { - console.error(error); done(error); } }, diff --git a/packages/apps/human-app/server/src/integrations/kv-store/kv-store.gateway.ts b/packages/apps/human-app/server/src/integrations/kv-store/kv-store.gateway.ts index 5341d6f44a..d57967369f 100644 --- a/packages/apps/human-app/server/src/integrations/kv-store/kv-store.gateway.ts +++ b/packages/apps/human-app/server/src/integrations/kv-store/kv-store.gateway.ts @@ -27,13 +27,13 @@ export class KvStoreGateway { if (cachedData) { return cachedData; } - let fetchedData: string; + let oracleUrl: string; try { const runner = new ethers.JsonRpcProvider(this.configService.rpcUrl); const network = await runner.provider?.getNetwork(); const chainId: ChainId = Number(network?.chainId); - fetchedData = await KVStoreUtils.get(chainId, address, KVStoreKeys.url); + oracleUrl = await KVStoreUtils.get(chainId, address, KVStoreKeys.url); } catch (e) { if (e.toString().includes('Error: Invalid address')) { throw new HttpException( @@ -46,19 +46,23 @@ export class KvStoreGateway { ); } } - if (!fetchedData || fetchedData === '') { + + if (!oracleUrl || oracleUrl === '') { throw new HttpException( `Unable to retrieve URL from address: ${address}`, 400, ); - } else { - await this.cacheManager.set( - key, - fetchedData, - this.configService.cacheTtlExchangeOracleUrl, - ); - return fetchedData; } + + oracleUrl = oracleUrl.replace(/\/$/, ''); + + await this.cacheManager.set( + key, + oracleUrl, + this.configService.cacheTtlExchangeOracleUrl, + ); + + return oracleUrl; } async getJobTypesByAddress( diff --git a/packages/apps/human-app/server/src/integrations/kv-store/spec/kv-store.gateway.spec.ts b/packages/apps/human-app/server/src/integrations/kv-store/spec/kv-store.gateway.spec.ts index 5818c72e22..19470e3bda 100644 --- a/packages/apps/human-app/server/src/integrations/kv-store/spec/kv-store.gateway.spec.ts +++ b/packages/apps/human-app/server/src/integrations/kv-store/spec/kv-store.gateway.spec.ts @@ -1,19 +1,12 @@ -import { Test, TestingModule } from '@nestjs/testing'; -import { KvStoreGateway } from '../kv-store.gateway'; -import { ChainId, KVStoreKeys, KVStoreUtils } from '@human-protocol/sdk'; -import { EnvironmentConfigService } from '../../../common/config/environment-config.service'; -import { CACHE_MANAGER } from '@nestjs/cache-manager'; -import { Cache } from 'cache-manager'; -import { ORACLE_URL_CACHE_KEY } from '../../../common/constants/cache'; - -const EXPECTED_URL = 'https://example.com'; jest.mock('@human-protocol/sdk', () => { const actualSdk = jest.requireActual('@human-protocol/sdk'); + const mockedSdk = jest.createMockFromModule< + typeof import('@human-protocol/sdk') + >('@human-protocol/sdk'); + return { ...actualSdk, - KVStoreUtils: { - get: jest.fn().mockResolvedValue('https://example.com'), - }, + KVStoreUtils: mockedSdk.KVStoreUtils, }; }); @@ -26,6 +19,7 @@ jest.mock('ethers', () => { }), }, }; + return { ...actualEthers, ethers: { @@ -35,10 +29,23 @@ jest.mock('ethers', () => { }; }); +import { ChainId, KVStoreKeys, KVStoreUtils } from '@human-protocol/sdk'; +import { CACHE_MANAGER } from '@nestjs/cache-manager'; +import { Test, TestingModule } from '@nestjs/testing'; + +import { Cache } from 'cache-manager'; + +import { KvStoreGateway } from '../kv-store.gateway'; +import { EnvironmentConfigService } from '../../../common/config/environment-config.service'; +import { ORACLE_URL_CACHE_KEY } from '../../../common/constants/cache'; + +const EXPECTED_URL = 'https://example.com'; + +const mockedKvStoreUtils = jest.mocked(KVStoreUtils); + describe('KvStoreGateway', () => { let service: KvStoreGateway; let configService: EnvironmentConfigService; - let mockKVStoreUtils: any; let cacheManager: Cache & { get: jest.Mock; set: jest.Mock }; beforeEach(async () => { @@ -47,9 +54,6 @@ describe('KvStoreGateway', () => { cacheTtlExchangeOracleUrl: 2137, } as any; - mockKVStoreUtils = { - get: jest.fn(), - }; const cacheManagerMock = { get: jest.fn(), set: jest.fn(), @@ -61,36 +65,36 @@ describe('KvStoreGateway', () => { provide: EnvironmentConfigService, useValue: configService, }, - { - provide: KVStoreUtils, - useValue: mockKVStoreUtils, - }, { provide: CACHE_MANAGER, useValue: cacheManagerMock, }, ], }).compile(); + cacheManager = module.get(CACHE_MANAGER); configService = module.get(EnvironmentConfigService); service = module.get(KvStoreGateway); }); afterEach(async () => { - jest.restoreAllMocks(); jest.clearAllMocks(); }); + it('should be defined', () => { expect(service).toBeDefined(); }); + describe('getExchangeOracleUrlByAddress', () => { it('should get data from kvStoreUtils, if not cached', async () => { const testAddress = 'testAddress'; const cacheKey = `${ORACLE_URL_CACHE_KEY}:${testAddress}`; - const expectedData = EXPECTED_URL; - mockKVStoreUtils.get.mockResolvedValue(expectedData); + const expectedUrl = EXPECTED_URL; + mockedKvStoreUtils.get.mockResolvedValue(expectedUrl + '/'); cacheManager.get.mockResolvedValue(undefined); + const result = await service.getExchangeOracleUrlByAddress(testAddress); + expect(KVStoreUtils.get).toHaveBeenCalledWith( ChainId.LOCALHOST, testAddress, @@ -99,17 +103,19 @@ describe('KvStoreGateway', () => { expect(cacheManager.set).toHaveBeenCalledWith( cacheKey, - expectedData, + expectedUrl, configService.cacheTtlExchangeOracleUrl, ); expect(cacheManager.get).toHaveBeenCalledWith(cacheKey); - expect(result).toBe(expectedData); + expect(result).toBe(expectedUrl); }); + it('should get data from cache, if available', async () => { const testAddress = 'testAddress'; const cacheKey = `${ORACLE_URL_CACHE_KEY}:${testAddress}`; const expectedData = EXPECTED_URL; cacheManager.get.mockResolvedValue(expectedData); + const result = await service.getExchangeOracleUrlByAddress(testAddress); expect(KVStoreUtils.get).not.toHaveBeenCalled(); diff --git a/packages/apps/human-app/server/src/modules/jobs-discovery/jobs-discovery.service.ts b/packages/apps/human-app/server/src/modules/jobs-discovery/jobs-discovery.service.ts index 8e590812d6..8a62cad6b2 100644 --- a/packages/apps/human-app/server/src/modules/jobs-discovery/jobs-discovery.service.ts +++ b/packages/apps/human-app/server/src/modules/jobs-discovery/jobs-discovery.service.ts @@ -124,6 +124,11 @@ export class JobsDiscoveryService { jobs: DiscoveredJob[], ): Promise { const cacheKey = JobsDiscoveryService.makeCacheKeyForOracle(oracleAddress); - await this.cacheManager.set(cacheKey, jobs); + + await this.cacheManager.set( + cacheKey, + jobs, + this.configService.cacheTtlOracleAvailableJobs, + ); } } diff --git a/yarn.lock b/yarn.lock index c0928c81b4..901b3ef69d 100644 --- a/yarn.lock +++ b/yarn.lock @@ -4274,6 +4274,7 @@ __metadata: "@nestjs/common": "npm:^10.2.7" "@nestjs/config": "npm:^3.1.1" "@nestjs/core": "npm:^10.3.10" + "@nestjs/passport": "npm:^10.0.0" "@nestjs/platform-express": "npm:^10.3.10" "@nestjs/schedule": "npm:^4.0.1" "@nestjs/schematics": "npm:^11.0.2" @@ -4285,6 +4286,8 @@ __metadata: "@types/jsonwebtoken": "npm:^9.0.7" "@types/lodash": "npm:^4.17.14" "@types/node": "npm:22.10.5" + "@types/passport": "npm:^0" + "@types/passport-jwt": "npm:^4.0.1" "@types/supertest": "npm:^2.0.15" "@typescript-eslint/eslint-plugin": "npm:^5.0.0" "@typescript-eslint/parser": "npm:^5.0.0" @@ -4303,6 +4306,8 @@ __metadata: jwt-decode: "npm:^4.0.0" lodash: "npm:^4.17.21" nock: "npm:^13.5.1" + passport: "npm:^0.7.0" + passport-jwt: "npm:^4.0.1" prettier: "npm:^3.4.2" reflect-metadata: "npm:^0.2.2" rxjs: "npm:^7.2.0" From 7a2bda127a795d14c4eefaca1a9da94d7603ed32 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 1 Jul 2025 11:48:59 +0200 Subject: [PATCH 2/7] chore(deps): bump dotenv from 16.5.0 to 17.0.0 (#3437) Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- packages/apps/faucet/client/package.json | 2 +- .../exchange-oracle/server/package.json | 2 +- .../fortune/recording-oracle/package.json | 2 +- .../apps/job-launcher/server/package.json | 2 +- .../reputation-oracle/server/package.json | 2 +- packages/examples/gcv/package.json | 2 +- yarn.lock | 21 ++++++++++++------- 7 files changed, 20 insertions(+), 13 deletions(-) diff --git a/packages/apps/faucet/client/package.json b/packages/apps/faucet/client/package.json index 019295bac1..9d0b0142d2 100644 --- a/packages/apps/faucet/client/package.json +++ b/packages/apps/faucet/client/package.json @@ -17,7 +17,7 @@ }, "devDependencies": { "@types/react": "^18.3.12", - "dotenv": "^16.3.2", + "dotenv": "^17.0.0", "eslint": "^8.55.0", "eslint-config-react-app": "^7.0.1", "eslint-import-resolver-typescript": "^3.7.0", diff --git a/packages/apps/fortune/exchange-oracle/server/package.json b/packages/apps/fortune/exchange-oracle/server/package.json index 52e07a4a4a..0b314d3a6b 100644 --- a/packages/apps/fortune/exchange-oracle/server/package.json +++ b/packages/apps/fortune/exchange-oracle/server/package.json @@ -44,7 +44,7 @@ "body-parser": "^1.20.3", "class-transformer": "^0.5.1", "class-validator": "0.14.1", - "dotenv": "^16.3.2", + "dotenv": "^17.0.0", "ethers": "~6.13.5", "joi": "^17.13.3", "jsonwebtoken": "^9.0.2", diff --git a/packages/apps/fortune/recording-oracle/package.json b/packages/apps/fortune/recording-oracle/package.json index 8d18412b67..2be152290d 100644 --- a/packages/apps/fortune/recording-oracle/package.json +++ b/packages/apps/fortune/recording-oracle/package.json @@ -34,7 +34,7 @@ "body-parser": "^1.20.2", "class-transformer": "^0.5.1", "class-validator": "0.14.1", - "dotenv": "^16.3.2", + "dotenv": "^17.0.0", "helmet": "^7.1.0", "joi": "^17.13.3", "minio": "7.1.3", diff --git a/packages/apps/job-launcher/server/package.json b/packages/apps/job-launcher/server/package.json index 45917b536c..fc7451e68c 100644 --- a/packages/apps/job-launcher/server/package.json +++ b/packages/apps/job-launcher/server/package.json @@ -53,7 +53,7 @@ "class-transformer": "^0.5.1", "class-validator": "0.14.1", "decimal.js": "^10.4.3", - "dotenv": "^16.3.2", + "dotenv": "^17.0.0", "helmet": "^7.1.0", "joi": "^17.13.3", "json-stable-stringify": "^1.2.1", diff --git a/packages/apps/reputation-oracle/server/package.json b/packages/apps/reputation-oracle/server/package.json index 966238f64d..bbdecfb4fd 100644 --- a/packages/apps/reputation-oracle/server/package.json +++ b/packages/apps/reputation-oracle/server/package.json @@ -52,7 +52,7 @@ "body-parser": "^1.20.3", "class-transformer": "^0.5.1", "class-validator": "^0.14.1", - "dotenv": "^16.3.2", + "dotenv": "^17.0.0", "ethers": "~6.13.5", "helmet": "^7.1.0", "joi": "^17.13.3", diff --git a/packages/examples/gcv/package.json b/packages/examples/gcv/package.json index 861f07d8ed..d45a6dd4ec 100644 --- a/packages/examples/gcv/package.json +++ b/packages/examples/gcv/package.json @@ -13,7 +13,7 @@ "@google-cloud/vision": "^4.3.2", "@nestjs/common": "^10.2.7", "axios": "^1.7.2", - "dotenv": "^16.5.0", + "dotenv": "^17.0.0", "xml2js": "^0.6.2" }, "devDependencies": { diff --git a/yarn.lock b/yarn.lock index 901b3ef69d..f31fed15c6 100644 --- a/yarn.lock +++ b/yarn.lock @@ -4017,7 +4017,7 @@ __metadata: "@mui/icons-material": "npm:^7.0.1" "@mui/material": "npm:^5.16.7" "@types/react": "npm:^18.3.12" - dotenv: "npm:^16.3.2" + dotenv: "npm:^17.0.0" eslint: "npm:^8.55.0" eslint-config-react-app: "npm:^7.0.1" eslint-import-resolver-typescript: "npm:^3.7.0" @@ -4131,7 +4131,7 @@ __metadata: body-parser: "npm:^1.20.3" class-transformer: "npm:^0.5.1" class-validator: "npm:0.14.1" - dotenv: "npm:^16.3.2" + dotenv: "npm:^17.0.0" eslint: "npm:^8.55.0" eslint-config-prettier: "npm:^9.1.0" eslint-plugin-prettier: "npm:^5.2.1" @@ -4177,7 +4177,7 @@ __metadata: body-parser: "npm:^1.20.2" class-transformer: "npm:^0.5.1" class-validator: "npm:0.14.1" - dotenv: "npm:^16.3.2" + dotenv: "npm:^17.0.0" eslint: "npm:^8.55.0" helmet: "npm:^7.1.0" jest: "npm:^29.7.0" @@ -4421,7 +4421,7 @@ __metadata: class-transformer: "npm:^0.5.1" class-validator: "npm:0.14.1" decimal.js: "npm:^10.4.3" - dotenv: "npm:^16.3.2" + dotenv: "npm:^17.0.0" eslint: "npm:^8.55.0" eslint-config-prettier: "npm:^9.1.0" eslint-plugin-prettier: "npm:^5.2.1" @@ -4494,7 +4494,7 @@ __metadata: body-parser: "npm:^1.20.3" class-transformer: "npm:^0.5.1" class-validator: "npm:^0.14.1" - dotenv: "npm:^16.3.2" + dotenv: "npm:^17.0.0" eslint: "npm:^8.55.0" eslint-config-prettier: "npm:^9.1.0" eslint-plugin-prettier: "npm:^5.2.1" @@ -16033,13 +16033,20 @@ __metadata: languageName: node linkType: hard -"dotenv@npm:^16.3.2, dotenv@npm:^16.4.5, dotenv@npm:^16.4.7, dotenv@npm:^16.5.0": +"dotenv@npm:^16.4.5, dotenv@npm:^16.4.7": version: 16.5.0 resolution: "dotenv@npm:16.5.0" checksum: 10c0/5bc94c919fbd955bf0ba44d33922a1e93d1078e64a1db5c30faeded1d996e7a83c55332cb8ea4fae5a9ca4d0be44cbceb95c5811e70f9f095298df09d1997dd9 languageName: node linkType: hard +"dotenv@npm:^17.0.0": + version: 17.0.0 + resolution: "dotenv@npm:17.0.0" + checksum: 10c0/961c9841529f691efb0547cf20b62c952b40e195ef23a254e6b3cd2487bd529b29e194ffcb1f97d4e6a0d9c2df9a6555464f2491da10e725c7bc40855c7b45af + languageName: node + linkType: hard + "dset@npm:^3.1.2": version: 3.1.4 resolution: "dset@npm:3.1.4" @@ -18435,7 +18442,7 @@ __metadata: "@nestjs/common": "npm:^10.2.7" "@types/xml2js": "npm:^0.4.14" axios: "npm:^1.7.2" - dotenv: "npm:^16.5.0" + dotenv: "npm:^17.0.0" eslint: "npm:^8.55.0" jest: "npm:^29.7.0" typescript: "npm:^5.8.3" From 993660d8830acdcbae971fa339694d5c2d5eb800 Mon Sep 17 00:00:00 2001 From: Nikolai Muhhin Date: Tue, 1 Jul 2025 13:12:40 +0300 Subject: [PATCH 3/7] Issue 3406 Add abuse report reason (#3412) Co-authored-by: Nikolai Muhhin Co-authored-by: portuu3 --- .../src/modules/abuse/model/abuse.model.ts | 18 +++++++++++++++++- .../src/modules/abuse/spec/abuse.fixtures.ts | 4 ++++ .../1750766313641-AddReasonToAbuse.ts | 13 +++++++++++++ .../src/modules/abuse/abuse.controller.ts | 2 ++ .../server/src/modules/abuse/abuse.dto.ts | 14 +++++++++++++- .../server/src/modules/abuse/abuse.entity.ts | 3 +++ .../src/modules/abuse/abuse.service.spec.ts | 3 +++ .../server/src/modules/abuse/abuse.service.ts | 1 + .../server/src/modules/abuse/fixtures/index.ts | 1 + .../server/src/modules/abuse/types.ts | 1 + 10 files changed, 58 insertions(+), 2 deletions(-) create mode 100644 packages/apps/reputation-oracle/server/src/database/migrations/1750766313641-AddReasonToAbuse.ts diff --git a/packages/apps/human-app/server/src/modules/abuse/model/abuse.model.ts b/packages/apps/human-app/server/src/modules/abuse/model/abuse.model.ts index 37eec9942a..fda6f2e6b3 100644 --- a/packages/apps/human-app/server/src/modules/abuse/model/abuse.model.ts +++ b/packages/apps/human-app/server/src/modules/abuse/model/abuse.model.ts @@ -1,7 +1,12 @@ import { AutoMap } from '@automapper/classes'; import { ApiProperty } from '@nestjs/swagger'; import { Type } from 'class-transformer'; -import { IsEthereumAddress, IsNumber } from 'class-validator'; +import { + IsEthereumAddress, + IsNotEmpty, + IsNumber, + IsString, +} from 'class-validator'; export class ReportAbuseDto { @AutoMap() @@ -13,6 +18,11 @@ export class ReportAbuseDto { @Type(() => Number) @ApiProperty() chain_id: number; + @AutoMap() + @IsString() + @IsNotEmpty() + @ApiProperty({ required: true, description: 'Reason for the abuse report' }) + reason: string; } export class ReportAbuseParams { @@ -20,7 +30,10 @@ export class ReportAbuseParams { chainId: number; @AutoMap() escrowAddress: string; + @AutoMap() + reason: string; } + export class ReportAbuseCommand { @AutoMap() data: ReportAbuseParams; @@ -33,6 +46,8 @@ export class ReportAbuseData { escrow_address: string; @AutoMap() chain_id: number; + @AutoMap() + reason: string; } export class ReportedAbuseItem { @@ -40,6 +55,7 @@ export class ReportedAbuseItem { escrowAddress: string; chainId: number; status: string; + reason: string; } export class ReportedAbuseResponse { diff --git a/packages/apps/human-app/server/src/modules/abuse/spec/abuse.fixtures.ts b/packages/apps/human-app/server/src/modules/abuse/spec/abuse.fixtures.ts index f0a0fe6439..84e9758301 100644 --- a/packages/apps/human-app/server/src/modules/abuse/spec/abuse.fixtures.ts +++ b/packages/apps/human-app/server/src/modules/abuse/spec/abuse.fixtures.ts @@ -10,16 +10,19 @@ const ESCROW_ADDRESS = 'test_address'; const CHAIN_ID = 1; const STATUS = 'reported'; const ABUSE_ID = 1; +const REASON = 'Test abuse reason'; export const TOKEN = 'test_user_token'; export const reportAbuseDtoFixture: ReportAbuseDto = { chain_id: CHAIN_ID, escrow_address: ESCROW_ADDRESS, + reason: REASON, }; export const reportAbuseParamsFixture: ReportAbuseParams = { chainId: CHAIN_ID, escrowAddress: ESCROW_ADDRESS, + reason: REASON, }; export const reportAbuseCommandFixture: ReportAbuseCommand = { @@ -32,6 +35,7 @@ export const reportedAbuseItemFixture: ReportedAbuseItem = { escrowAddress: ESCROW_ADDRESS, chainId: CHAIN_ID, status: STATUS, + reason: REASON, }; export const reportedAbuseResponseFixture: ReportedAbuseResponse = { diff --git a/packages/apps/reputation-oracle/server/src/database/migrations/1750766313641-AddReasonToAbuse.ts b/packages/apps/reputation-oracle/server/src/database/migrations/1750766313641-AddReasonToAbuse.ts new file mode 100644 index 0000000000..da01de6e2f --- /dev/null +++ b/packages/apps/reputation-oracle/server/src/database/migrations/1750766313641-AddReasonToAbuse.ts @@ -0,0 +1,13 @@ +import { MigrationInterface, QueryRunner } from 'typeorm'; + +export class AddReasonToAbuse1750766313641 implements MigrationInterface { + name = 'AddReasonToAbuse1750766313641'; + + public async up(queryRunner: QueryRunner): Promise { + await queryRunner.query(`ALTER TABLE "hmt"."abuses" ADD "reason" text NOT NULL`); + } + + public async down(queryRunner: QueryRunner): Promise { + await queryRunner.query(`ALTER TABLE "hmt"."abuses" DROP COLUMN "reason"`); + } +} diff --git a/packages/apps/reputation-oracle/server/src/modules/abuse/abuse.controller.ts b/packages/apps/reputation-oracle/server/src/modules/abuse/abuse.controller.ts index 35d4273953..ae1f146530 100644 --- a/packages/apps/reputation-oracle/server/src/modules/abuse/abuse.controller.ts +++ b/packages/apps/reputation-oracle/server/src/modules/abuse/abuse.controller.ts @@ -55,6 +55,7 @@ export class AbuseController { escrowAddress: data.escrowAddress, chainId: data.chainId, userId: request.user.id, + reason: data.reason, }); } @@ -82,6 +83,7 @@ export class AbuseController { escrowAddress: abuseEntity.escrowAddress, chainId: abuseEntity.chainId, status: abuseEntity.status, + reason: abuseEntity.reason, }; }); } diff --git a/packages/apps/reputation-oracle/server/src/modules/abuse/abuse.dto.ts b/packages/apps/reputation-oracle/server/src/modules/abuse/abuse.dto.ts index b828c78490..55afd67b15 100644 --- a/packages/apps/reputation-oracle/server/src/modules/abuse/abuse.dto.ts +++ b/packages/apps/reputation-oracle/server/src/modules/abuse/abuse.dto.ts @@ -1,6 +1,6 @@ import { ChainId } from '@human-protocol/sdk'; import { ApiProperty } from '@nestjs/swagger'; -import { IsEthereumAddress, IsString } from 'class-validator'; +import { IsEthereumAddress, IsString, MaxLength } from 'class-validator'; import { IsChainId } from '../../common/validators'; import { AbuseStatus } from './constants'; @@ -13,6 +13,15 @@ export class ReportAbuseDto { @ApiProperty({ name: 'escrow_address' }) @IsEthereumAddress() escrowAddress: string; + + @ApiProperty({ + name: 'reason', + required: true, + description: 'Reason for the abuse report', + }) + @IsString() + @MaxLength(1000) + reason: string; } export class AbuseResponseDto { @@ -27,6 +36,9 @@ export class AbuseResponseDto { @ApiProperty({ description: 'Current status of the abuse report' }) status: AbuseStatus; + + @ApiProperty({ description: 'Reason for the abuse report', required: true }) + reason: string; } export class SlackInteractionDto { diff --git a/packages/apps/reputation-oracle/server/src/modules/abuse/abuse.entity.ts b/packages/apps/reputation-oracle/server/src/modules/abuse/abuse.entity.ts index 1f5749b370..2f436a1afb 100644 --- a/packages/apps/reputation-oracle/server/src/modules/abuse/abuse.entity.ts +++ b/packages/apps/reputation-oracle/server/src/modules/abuse/abuse.entity.ts @@ -31,6 +31,9 @@ export class AbuseEntity extends BaseEntity { @Column({ type: 'decimal', precision: 30, scale: 18, nullable: true }) amount: number | null; + @Column({ type: 'text' }) + reason: string; + @JoinColumn() @ManyToOne('UserEntity', { nullable: false, onDelete: 'CASCADE' }) user?: UserEntity; diff --git a/packages/apps/reputation-oracle/server/src/modules/abuse/abuse.service.spec.ts b/packages/apps/reputation-oracle/server/src/modules/abuse/abuse.service.spec.ts index e39275ea72..90c31e8dcb 100644 --- a/packages/apps/reputation-oracle/server/src/modules/abuse/abuse.service.spec.ts +++ b/packages/apps/reputation-oracle/server/src/modules/abuse/abuse.service.spec.ts @@ -73,17 +73,20 @@ describe('AbuseService', () => { describe('reportAbuse', () => { it('should create a new abuse entity', async () => { const userId = faker.number.int(); + const reason = faker.lorem.sentence(); await abuseService.reportAbuse({ escrowAddress, chainId, userId, + reason, }); expect(mockAbuseRepository.createUnique).toHaveBeenCalledWith({ escrowAddress: escrowAddress, chainId: chainId, userId: userId, + reason: reason, retriesCount: 0, status: AbuseStatus.PENDING, waitUntil: expect.any(Date), 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 80853720eb..1ac68c5f12 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 @@ -42,6 +42,7 @@ export class AbuseService { abuseEntity.escrowAddress = data.escrowAddress; abuseEntity.chainId = data.chainId; abuseEntity.userId = data.userId; + abuseEntity.reason = data.reason; abuseEntity.status = AbuseStatus.PENDING; abuseEntity.retriesCount = 0; abuseEntity.waitUntil = new Date(); diff --git a/packages/apps/reputation-oracle/server/src/modules/abuse/fixtures/index.ts b/packages/apps/reputation-oracle/server/src/modules/abuse/fixtures/index.ts index e79e622b2b..c75c6a8826 100644 --- a/packages/apps/reputation-oracle/server/src/modules/abuse/fixtures/index.ts +++ b/packages/apps/reputation-oracle/server/src/modules/abuse/fixtures/index.ts @@ -19,6 +19,7 @@ export function generateAbuseEntity( status: AbuseStatus.PENDING, decision: null, amount: null, + reason: faker.lorem.sentence(), waitUntil: faker.date.future(), createdAt: faker.date.recent(), updatedAt: new Date(), diff --git a/packages/apps/reputation-oracle/server/src/modules/abuse/types.ts b/packages/apps/reputation-oracle/server/src/modules/abuse/types.ts index 9e733cea43..37dec9605c 100644 --- a/packages/apps/reputation-oracle/server/src/modules/abuse/types.ts +++ b/packages/apps/reputation-oracle/server/src/modules/abuse/types.ts @@ -4,6 +4,7 @@ export type ReportAbuseInput = { userId: number; chainId: ChainId; escrowAddress: string; + reason: string; }; export type SlackInteractionBase = { From e7920640c53e2af537e39292b0c5389de1bd12f9 Mon Sep 17 00:00:00 2001 From: portuu3 <61605646+portuu3@users.noreply.github.com> Date: Thu, 3 Jul 2025 09:18:22 +0200 Subject: [PATCH 4/7] update block number calculation for query filter (#3443) --- .../governance-banner/hooks/use-active-proposal-query.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/apps/human-app/frontend/src/modules/governance-banner/hooks/use-active-proposal-query.ts b/packages/apps/human-app/frontend/src/modules/governance-banner/hooks/use-active-proposal-query.ts index 2ce4913f56..dc781ff0f1 100644 --- a/packages/apps/human-app/frontend/src/modules/governance-banner/hooks/use-active-proposal-query.ts +++ b/packages/apps/human-app/frontend/src/modules/governance-banner/hooks/use-active-proposal-query.ts @@ -30,7 +30,7 @@ async function fetchActiveProposalFn() { const logs = await contract.queryFilter( filter, env.VITE_NETWORK === 'mainnet' - ? 68058296 + ? (await provider.getBlockNumber()) - 100000 : (await provider.getBlockNumber()) - 10000, 'latest' ); From 3c2751b1fbdd30c470ed179024873445f3cfd9de Mon Sep 17 00:00:00 2001 From: Maxim Zhiltsov Date: Fri, 4 Jul 2025 11:47:55 +0300 Subject: [PATCH 5/7] [CVAT] Updates (#3445) --- .../src/core/tasks/boxes_from_points.py | 13 +++++++++++++ .../exchange-oracle/src/core/tasks/roi_based.py | 1 + .../src/core/tasks/skeletons_from_boxes.py | 13 +++++++++++++ .../exchange-oracle/src/handlers/job_export.py | 16 ++++++++++++++-- .../cvat/recording-oracle/src/core/config.py | 6 ++++++ .../src/handlers/process_intermediate_results.py | 3 ++- 6 files changed, 49 insertions(+), 3 deletions(-) create mode 100644 packages/examples/cvat/exchange-oracle/src/core/tasks/roi_based.py diff --git a/packages/examples/cvat/exchange-oracle/src/core/tasks/boxes_from_points.py b/packages/examples/cvat/exchange-oracle/src/core/tasks/boxes_from_points.py index bd26aacc14..747d3a1c09 100644 --- a/packages/examples/cvat/exchange-oracle/src/core/tasks/boxes_from_points.py +++ b/packages/examples/cvat/exchange-oracle/src/core/tasks/boxes_from_points.py @@ -8,6 +8,8 @@ from attrs import frozen from datumaro.util import dump_json, parse_json +from src.core.tasks.roi_based import OUTPUT_OBJECT_ID_ATTR + BboxPointMapping = dict[int, int] @@ -90,3 +92,14 @@ def parse_roi_info(self, rois_info_data: bytes) -> RoiInfos: def parse_roi_filenames(self, roi_filenames_data: bytes) -> RoiFilenames: return {int(k): v for k, v in parse_json(roi_filenames_data).items()} + + +__all__ = [ + "OUTPUT_OBJECT_ID_ATTR", + "BboxPointMapping", + "RoiInfo", + "RoiInfos", + "RoiFilenames", + "TaskMetaLayout", + "TaskMetaSerializer", +] diff --git a/packages/examples/cvat/exchange-oracle/src/core/tasks/roi_based.py b/packages/examples/cvat/exchange-oracle/src/core/tasks/roi_based.py new file mode 100644 index 0000000000..b2d64a443c --- /dev/null +++ b/packages/examples/cvat/exchange-oracle/src/core/tasks/roi_based.py @@ -0,0 +1 @@ +OUTPUT_OBJECT_ID_ATTR = "object_id" diff --git a/packages/examples/cvat/exchange-oracle/src/core/tasks/skeletons_from_boxes.py b/packages/examples/cvat/exchange-oracle/src/core/tasks/skeletons_from_boxes.py index d48397e5d1..7459b904cb 100644 --- a/packages/examples/cvat/exchange-oracle/src/core/tasks/skeletons_from_boxes.py +++ b/packages/examples/cvat/exchange-oracle/src/core/tasks/skeletons_from_boxes.py @@ -9,6 +9,7 @@ from datumaro.util import dump_json, parse_json from src.core.config import Config +from src.core.tasks.roi_based import OUTPUT_OBJECT_ID_ATTR DEFAULT_ASSIGNMENT_SIZE_MULTIPLIER = Config.core_config.skeleton_assignment_size_mult @@ -133,3 +134,15 @@ def parse_point_labels(self, point_labels_data: bytes) -> PointLabelsMapping: (v["skeleton_label"], v["point_label"]): v["job_point_label"] for v in parse_json(point_labels_data) } + + +__all__ = [ + "OUTPUT_OBJECT_ID_ATTR", + "SkeletonBboxMapping", + "RoiInfo", + "RoiInfos", + "RoiFilenames", + "PointLabelsMapping", + "TaskMetaLayout", + "TaskMetaSerializer", +] diff --git a/packages/examples/cvat/exchange-oracle/src/handlers/job_export.py b/packages/examples/cvat/exchange-oracle/src/handlers/job_export.py index 1db5ae4be2..6b93567965 100644 --- a/packages/examples/cvat/exchange-oracle/src/handlers/job_export.py +++ b/packages/examples/cvat/exchange-oracle/src/handlers/job_export.py @@ -262,6 +262,18 @@ def _process_merged_dataset(self, input_dataset: Dataset) -> Dataset: ).elements[0] old_x, old_y = old_point.points[:2] + roi_anns = [ + roi_ann.wrap( + id=roi_info.point_id, + attributes={ + **roi_sample.attributes, + boxes_from_points_task.OUTPUT_OBJECT_ID_ATTR: roi_info.point_id, + }, + ) + for roi_ann in roi_sample.annotations + if isinstance(roi_ann, dm.Bbox) + ] + merged_sample.annotations.extend( annotation_utils.shift_ann( roi_ann, @@ -270,8 +282,7 @@ def _process_merged_dataset(self, input_dataset: Dataset) -> Dataset: img_w=image_w, img_h=image_h, ) - for roi_ann in roi_sample.annotations - if isinstance(roi_ann, dm.Bbox) + for roi_ann in roi_anns ) return merged_sample_dataset @@ -456,6 +467,7 @@ def _process_dataset(self, dataset: Dataset, *, ann_descriptor: FileDescriptor) ], label=self.bbox_label_to_merged[old_bbox.label], id=old_bbox.id, + attributes={skeletons_from_boxes_task.OUTPUT_OBJECT_ID_ATTR: old_bbox.id}, ) skeleton_sample.annotations.append(merged_skeleton) diff --git a/packages/examples/cvat/recording-oracle/src/core/config.py b/packages/examples/cvat/recording-oracle/src/core/config.py index 248f51cc6e..477046d0bf 100644 --- a/packages/examples/cvat/recording-oracle/src/core/config.py +++ b/packages/examples/cvat/recording-oracle/src/core/config.py @@ -177,6 +177,12 @@ class ValidationConfig: value small enough for faster convergence rate of the annotation process. """ + enable_gt_bans = to_bool(getenv("ENABLE_GT_BANS", "1")) + """ + Whether to allow automatic GT bans for bad images or not. By default, bans are allowed. + This can raise escrow annotation chances at the cost of reduced quality threshold. + """ + unverifiable_assignments_threshold = float(getenv("UNVERIFIABLE_ASSIGNMENTS_THRESHOLD", "0.1")) """ Deprecated. Not expected to happen in practice, kept only as a safety fallback rule. diff --git a/packages/examples/cvat/recording-oracle/src/handlers/process_intermediate_results.py b/packages/examples/cvat/recording-oracle/src/handlers/process_intermediate_results.py index 98d13d9cd7..74a1bde5ca 100644 --- a/packages/examples/cvat/recording-oracle/src/handlers/process_intermediate_results.py +++ b/packages/examples/cvat/recording-oracle/src/handlers/process_intermediate_results.py @@ -574,7 +574,8 @@ def _get_available_gt(self) -> tuple[GtStats, dict[int, set[GtKey]]]: gt_key for gt_key, gt_stat in self.gt_stats.items() if gt_stat.enabled - if gt_stat.rating > 1 - self.manifest.validation.min_quality + if not Config.validation.enable_gt_bans + or (gt_stat.rating > 1 - self.manifest.validation.min_quality) }, task_id_to_gt_keys def _check_warmup_annotation_speed(self): From 2dac59b240efa6fe9eb6bc743854dba36d3f5b99 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 4 Jul 2025 11:47:37 +0200 Subject: [PATCH 6/7] chore(deps-dev): bump @types/cors from 2.8.17 to 2.8.19 (#3435) Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- packages/apps/faucet/server/package.json | 2 +- yarn.lock | 10 +++++----- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/packages/apps/faucet/server/package.json b/packages/apps/faucet/server/package.json index 0c0d411238..3f582ac06f 100644 --- a/packages/apps/faucet/server/package.json +++ b/packages/apps/faucet/server/package.json @@ -25,7 +25,7 @@ "web3": "^4.12.1" }, "devDependencies": { - "@types/cors": "^2.8.17", + "@types/cors": "^2.8.19", "@types/express": "^4.17.14", "@types/node": "^22.15.16", "concurrently": "^9.1.2", diff --git a/yarn.lock b/yarn.lock index f31fed15c6..1f8abcec98 100644 --- a/yarn.lock +++ b/yarn.lock @@ -4042,7 +4042,7 @@ __metadata: resolution: "@human-protocol/faucet-server@workspace:packages/apps/faucet/server" dependencies: "@human-protocol/sdk": "workspace:*" - "@types/cors": "npm:^2.8.17" + "@types/cors": "npm:^2.8.19" "@types/express": "npm:^4.17.14" "@types/node": "npm:^22.15.16" axios: "npm:^1.3.4" @@ -9777,12 +9777,12 @@ __metadata: languageName: node linkType: hard -"@types/cors@npm:^2.8.17": - version: 2.8.17 - resolution: "@types/cors@npm:2.8.17" +"@types/cors@npm:^2.8.19": + version: 2.8.19 + resolution: "@types/cors@npm:2.8.19" dependencies: "@types/node": "npm:*" - checksum: 10c0/457364c28c89f3d9ed34800e1de5c6eaaf344d1bb39af122f013322a50bc606eb2aa6f63de4e41a7a08ba7ef454473926c94a830636723da45bf786df032696d + checksum: 10c0/b5dd407040db7d8aa1bd36e79e5f3f32292f6b075abc287529e9f48df1a25fda3e3799ba30b4656667ffb931d3b75690c1d6ca71e39f7337ea6dfda8581916d0 languageName: node linkType: hard From 67bc831c4adcc32525b49d9a6514a7e07254698c Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 4 Jul 2025 11:53:06 +0200 Subject: [PATCH 7/7] chore(deps-dev): bump solidity-coverage from 0.8.15 to 0.8.16 (#3436) Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- packages/core/package.json | 2 +- yarn.lock | 12 ++++++------ 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/packages/core/package.json b/packages/core/package.json index 6bd39aea2d..3523a8e5a4 100644 --- a/packages/core/package.json +++ b/packages/core/package.json @@ -79,7 +79,7 @@ "openpgp": "5.11.2", "prettier": "^3.4.2", "prettier-plugin-solidity": "^1.3.1", - "solidity-coverage": "^0.8.2", + "solidity-coverage": "^0.8.16", "tenderly": "^0.9.1", "ts-node": "^10.9.2", "typechain": "^8.3.2", diff --git a/yarn.lock b/yarn.lock index 1f8abcec98..c858ad4df4 100644 --- a/yarn.lock +++ b/yarn.lock @@ -3902,7 +3902,7 @@ __metadata: openpgp: "npm:5.11.2" prettier: "npm:^3.4.2" prettier-plugin-solidity: "npm:^1.3.1" - solidity-coverage: "npm:^0.8.2" + solidity-coverage: "npm:^0.8.16" tenderly: "npm:^0.9.1" ts-node: "npm:^10.9.2" typechain: "npm:^8.3.2" @@ -26718,12 +26718,12 @@ __metadata: languageName: node linkType: hard -"solidity-coverage@npm:^0.8.2": - version: 0.8.15 - resolution: "solidity-coverage@npm:0.8.15" +"solidity-coverage@npm:^0.8.16": + version: 0.8.16 + resolution: "solidity-coverage@npm:0.8.16" dependencies: "@ethersproject/abi": "npm:^5.0.9" - "@solidity-parser/parser": "npm:^0.19.0" + "@solidity-parser/parser": "npm:^0.20.1" chalk: "npm:^2.4.2" death: "npm:^1.1.0" difflib: "npm:^0.2.4" @@ -26745,7 +26745,7 @@ __metadata: hardhat: ^2.11.0 bin: solidity-coverage: plugins/bin.js - checksum: 10c0/b97bd862a4c446ad05af359391b6c7084d37c9ac90fee02d07dfd0c972a368fd1ab951e50f686551238b01e8730d2fe52700613ea361e369fa70b83e1cafc86d + checksum: 10c0/6a5d643f8cc11c926a898f2bc60720c893634a65d17b1a99f5df32c485ea25404022555c22c66ee85c388e67c96ea8366895111ed592539dd45697e1a6208203 languageName: node linkType: hard