Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,6 @@ export const envValidator = Joi.object({
GAS_PRICE_MULTIPLIER: Joi.number(),
APPROVE_AMOUNT_USD: Joi.number(),
REPUTATION_ORACLE_ADDRESS: Joi.string().required(),
REPUTATION_ORACLES: Joi.string().required(),
CVAT_EXCHANGE_ORACLE_ADDRESS: Joi.string().required(),
CVAT_RECORDING_ORACLE_ADDRESS: Joi.string().required(),
HCAPTCHA_ORACLE_ADDRESS: Joi.string().required(),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -37,14 +37,6 @@ export class Web3ConfigService {
return this.configService.getOrThrow<string>('REPUTATION_ORACLE_ADDRESS');
}

/**
* List of reputation oracle addresses, typically comma-separated.
* Required
*/
get reputationOracles(): string {
return this.configService.getOrThrow<string>('REPUTATION_ORACLES');
}

/**
* URI for the hCaptcha recording oracle service.
* Required
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -149,15 +149,6 @@ export enum ErrorWeb3 {
ReputationOracleUrlNotSet = 'Reputation oracle URL not set',
}

/**
* Represents error messages related to routing protocol.
*/
export enum ErrorRoutingProtocol {
ReputationOracleNotFound = 'The specified Reputation Oracle address is not found in the set of available oracles. Ensure the address is correct and check available oracles for this network.',
ExchangeOracleNotFound = 'The specified Exchange Oracle address is not found in the set of available oracles. Ensure the address is correct and part of the available oracle pool.',
RecordingOracleNotFound = 'The specified Recording Oracle address is not found in the set of available oracles. Ensure the address is correct and part of the available oracle pool.',
}

/**
* Represents error messages related to send grid.
*/
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,6 @@ import { PaymentRepository } from '../payment/payment.repository';
import { PaymentService } from '../payment/payment.service';
import { QualificationService } from '../qualification/qualification.service';
import { RateService } from '../rate/rate.service';
import { RoutingProtocolService } from '../routing-protocol/routing-protocol.service';
import { StorageService } from '../storage/storage.service';
import { Web3Service } from '../web3/web3.service';
import { WebhookEntity } from '../webhook/webhook.entity';
Expand Down Expand Up @@ -124,10 +123,6 @@ describe('CronJobService', () => {
{ provide: PaymentService, useValue: createMock<PaymentService>() },
{ provide: WhitelistService, useValue: createMock<WhitelistService>() },
{ provide: ConfigService, useValue: mockConfigService },
{
provide: RoutingProtocolService,
useValue: createMock<RoutingProtocolService>(),
},
{
provide: WebhookRepository,
useValue: createMock<WebhookRepository>(),
Expand Down
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import { ChainId } from '@human-protocol/sdk';
import { Test, TestingModule } from '@nestjs/testing';
import { JobController } from './job.controller';
import { JobService } from './job.service';
Expand Down Expand Up @@ -84,6 +85,7 @@ describe('JobController', () => {
describe('quickLaunch', () => {
it('should create a job and return job ID', async () => {
const jobDto: JobQuickLaunchDto = {
chainId: ChainId.POLYGON_AMOY,
requestType: 'type_a' as JobRequestType,
manifestUrl: MOCK_FILE_URL,
manifestHash: MOCK_FILE_HASH,
Expand Down Expand Up @@ -119,6 +121,7 @@ describe('JobController', () => {

it('should throw a conflict error if mutex manager fails', async () => {
const jobDto: JobQuickLaunchDto = {
chainId: ChainId.POLYGON_AMOY,
requestType: 'type_a' as JobRequestType,
manifestUrl: MOCK_FILE_URL,
manifestHash: MOCK_FILE_HASH,
Expand Down Expand Up @@ -159,6 +162,7 @@ describe('JobController', () => {

it('should return unauthorized error if user is not authenticated', async () => {
const jobDto: JobQuickLaunchDto = {
chainId: ChainId.POLYGON_AMOY,
requestType: 'type_a' as JobRequestType,
manifestUrl: MOCK_FILE_URL,
manifestHash: MOCK_FILE_HASH,
Expand All @@ -185,6 +189,7 @@ describe('JobController', () => {

describe('createFortuneJob', () => {
const jobFortuneDto: JobFortuneDto = {
chainId: ChainId.POLYGON_AMOY,
requesterTitle: MOCK_REQUESTER_TITLE,
requesterDescription: MOCK_REQUESTER_DESCRIPTION,
submissionsRequired: 10,
Expand Down
5 changes: 2 additions & 3 deletions packages/apps/job-launcher/server/src/modules/job/job.dto.ts
Original file line number Diff line number Diff line change
Expand Up @@ -32,10 +32,9 @@ import { IsValidToken } from '../../common/validators/tokens';
import { ManifestDetails } from '../manifest/manifest.dto';

export class JobDto {
@ApiProperty({ enum: ChainId, required: false, name: 'chain_id' })
@ApiProperty({ enum: ChainId, name: 'chain_id' })
@IsEnumCaseInsensitive(ChainId)
@IsOptional()
public chainId?: ChainId;
public chainId: ChainId;

@ApiPropertyOptional()
@IsArray()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,6 @@ import { WebhookRepository } from '../webhook/webhook.repository';
import { MutexManagerService } from '../mutex/mutex-manager.service';
import { QualificationModule } from '../qualification/qualification.module';
import { WhitelistModule } from '../whitelist/whitelist.module';
import { RoutingProtocolModule } from '../routing-protocol/routing-protocol.module';
import { RateModule } from '../rate/rate.module';
import { ManifestModule } from '../manifest/manifest.module';

Expand All @@ -29,7 +28,6 @@ import { ManifestModule } from '../manifest/manifest.module';
StorageModule,
QualificationModule,
WhitelistModule,
RoutingProtocolModule,
RateModule,
ManifestModule,
],
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import {
IEscrow,
KVStoreUtils,
NETWORKS,
Role,
} from '@human-protocol/sdk';
import { Test } from '@nestjs/testing';
import { ethers, ZeroAddress } from 'ethers';
Expand Down Expand Up @@ -45,7 +46,6 @@ import { PaymentRepository } from '../payment/payment.repository';
import { PaymentService } from '../payment/payment.service';
import { QualificationService } from '../qualification/qualification.service';
import { RateService } from '../rate/rate.service';
import { RoutingProtocolService } from '../routing-protocol/routing-protocol.service';
import { StorageService } from '../storage/storage.service';
import { createUser } from '../user/fixtures';
import { Web3Service } from '../web3/web3.service';
Expand All @@ -71,11 +71,14 @@ const mockPaymentRepository = createMock<PaymentRepository>();
const mockStorageService = createMock<StorageService>();
const mockPaymentService = createMock<PaymentService>();
const mockRateService = createMock<RateService>();
const mockRoutingProtocolService = createMock<RoutingProtocolService>();
const mockManifestService = createMock<ManifestService>();
const mockWhitelistService = createMock<WhitelistService>();
const mockWeb3ConfigService = {
txTimeoutMs: faker.number.int({ min: 30000, max: 120000 }),
reputationOracleAddress: faker.finance.ethereumAddress(),
cvatExchangeOracleAddress: faker.finance.ethereumAddress(),
cvatRecordingOracleAddress: faker.finance.ethereumAddress(),
hCaptchaOracleAddress: faker.finance.ethereumAddress(),
};

const mockedEscrowClient = jest.mocked(EscrowClient);
Expand Down Expand Up @@ -116,10 +119,6 @@ describe('JobService', () => {
{ provide: PaymentService, useValue: mockPaymentService },
{ provide: StorageService, useValue: mockStorageService },
{ provide: WhitelistService, useValue: mockWhitelistService },
{
provide: RoutingProtocolService,
useValue: mockRoutingProtocolService,
},
{
provide: ManifestService,
useValue: mockManifestService,
Expand Down Expand Up @@ -188,14 +187,7 @@ describe('JobService', () => {
expect(mockWeb3Service.validateChainId).toHaveBeenCalledWith(
fortuneJobDto.chainId,
);
expect(mockRoutingProtocolService.selectOracles).not.toHaveBeenCalled();
expect(mockRoutingProtocolService.validateOracles).toHaveBeenCalledWith(
fortuneJobDto.chainId,
FortuneJobType.FORTUNE,
fortuneJobDto.reputationOracle,
fortuneJobDto.exchangeOracle,
fortuneJobDto.recordingOracle,
);
expect(mockWeb3Service.findAvailableOracles).not.toHaveBeenCalled();
expect(mockManifestService.createManifest).toHaveBeenCalledWith(
fortuneJobDto,
FortuneJobType.FORTUNE,
Expand Down Expand Up @@ -288,14 +280,7 @@ describe('JobService', () => {
expect(mockWeb3Service.validateChainId).toHaveBeenCalledWith(
fortuneJobDto.chainId,
);
expect(mockRoutingProtocolService.selectOracles).not.toHaveBeenCalled();
expect(mockRoutingProtocolService.validateOracles).toHaveBeenCalledWith(
fortuneJobDto.chainId,
FortuneJobType.FORTUNE,
fortuneJobDto.reputationOracle,
fortuneJobDto.exchangeOracle,
fortuneJobDto.recordingOracle,
);
expect(mockWeb3Service.findAvailableOracles).not.toHaveBeenCalled();
expect(mockManifestService.createManifest).toHaveBeenCalledWith(
fortuneJobDto,
FortuneJobType.FORTUNE,
Expand Down Expand Up @@ -379,13 +364,20 @@ describe('JobService', () => {
const mockOracles = {
recordingOracle: faker.finance.ethereumAddress(),
exchangeOracle: faker.finance.ethereumAddress(),
reputationOracle: faker.finance.ethereumAddress(),
reputationOracle: mockWeb3ConfigService.reputationOracleAddress,
};
mockRoutingProtocolService.selectOracles.mockResolvedValueOnce({
recordingOracle: mockOracles.recordingOracle,
exchangeOracle: mockOracles.exchangeOracle,
reputationOracle: mockOracles.reputationOracle,
});
mockWeb3Service.findAvailableOracles.mockResolvedValueOnce([
{
address: mockOracles.exchangeOracle,
role: Role.ExchangeOracle,
url: null,
},
{
address: mockOracles.recordingOracle,
role: Role.RecordingOracle,
url: null,
},
]);
mockedKVStoreUtils.get.mockResolvedValueOnce('1');

const result = await jobService.createJob(
Expand All @@ -405,13 +397,11 @@ describe('JobService', () => {
expect(mockWeb3Service.validateChainId).toHaveBeenCalledWith(
fortuneJobDto.chainId,
);
expect(mockRoutingProtocolService.selectOracles).toHaveBeenCalledWith(
expect(mockWeb3Service.findAvailableOracles).toHaveBeenCalledWith(
fortuneJobDto.chainId,
FortuneJobType.FORTUNE,
mockOracles.reputationOracle,
);
expect(
mockRoutingProtocolService.validateOracles,
).not.toHaveBeenCalled();
expect(mockManifestService.createManifest).toHaveBeenCalledWith(
fortuneJobDto,
FortuneJobType.FORTUNE,
Expand Down Expand Up @@ -514,14 +504,7 @@ describe('JobService', () => {
expect(mockWeb3Service.validateChainId).toHaveBeenCalledWith(
jobQuickLaunchDto.chainId,
);
expect(mockRoutingProtocolService.selectOracles).not.toHaveBeenCalled();
expect(mockRoutingProtocolService.validateOracles).toHaveBeenCalledWith(
jobQuickLaunchDto.chainId,
HCaptchaJobType.HCAPTCHA,
jobQuickLaunchDto.reputationOracle,
jobQuickLaunchDto.exchangeOracle,
jobQuickLaunchDto.recordingOracle,
);
expect(mockWeb3Service.findAvailableOracles).not.toHaveBeenCalled();
expect(mockManifestService.createManifest).not.toHaveBeenCalled();
expect(mockManifestService.uploadManifest).not.toHaveBeenCalled();
expect(mockPaymentService.createWithdrawalPayment).toHaveBeenCalledWith(
Expand Down
74 changes: 50 additions & 24 deletions packages/apps/job-launcher/server/src/modules/job/job.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import {
KVStoreKeys,
KVStoreUtils,
NETWORKS,
Role,
} from '@human-protocol/sdk';
import { Inject, Injectable } from '@nestjs/common';
import { ModuleRef } from '@nestjs/core';
Expand Down Expand Up @@ -61,7 +62,6 @@ import { ManifestService } from '../manifest/manifest.service';
import { PaymentService } from '../payment/payment.service';
import { QualificationService } from '../qualification/qualification.service';
import { RateService } from '../rate/rate.service';
import { RoutingProtocolService } from '../routing-protocol/routing-protocol.service';
import { StorageService } from '../storage/storage.service';
import { UserEntity } from '../user/user.entity';
import { Web3Service } from '../web3/web3.service';
Expand Down Expand Up @@ -94,7 +94,6 @@ export class JobService {
private readonly webhookRepository: WebhookRepository,
private readonly paymentService: PaymentService,
private readonly serverConfigService: ServerConfigService,
private readonly routingProtocolService: RoutingProtocolService,
private readonly storageService: StorageService,
private readonly rateService: RateService,
private readonly whitelistService: WhitelistService,
Expand Down Expand Up @@ -124,10 +123,8 @@ export class JobService {
throw new ValidationError(ErrorPayment.HMTTokenDisabled);
}

let { chainId, reputationOracle, exchangeOracle, recordingOracle } = dto;

// Select network
chainId = chainId || this.routingProtocolService.selectNetwork();
const { chainId } = dto;
let { reputationOracle, exchangeOracle, recordingOracle } = dto;
this.web3Service.validateChainId(chainId);

// Check if not whitelisted user has an active payment method
Expand Down Expand Up @@ -197,25 +194,11 @@ export class JobService {
).toFixed(fundTokenDecimals),
);

// Select oracles
if (!reputationOracle || !exchangeOracle || !recordingOracle) {
const selectedOracles = await this.routingProtocolService.selectOracles(
chainId,
requestType,
);

exchangeOracle = exchangeOracle || selectedOracles.exchangeOracle;
recordingOracle = recordingOracle || selectedOracles.recordingOracle;
reputationOracle = reputationOracle || selectedOracles.reputationOracle;
} else {
// Validate if all oracles are provided
await this.routingProtocolService.validateOracles(
chainId,
requestType,
reputationOracle,
exchangeOracle,
recordingOracle,
);
const defaultOracles = await this.getDefaultOracles(chainId, requestType);
reputationOracle = reputationOracle ?? defaultOracles.reputationOracle;
exchangeOracle = exchangeOracle ?? defaultOracles.exchangeOracle;
recordingOracle = recordingOracle ?? defaultOracles.recordingOracle;
}

if (dto.qualifications) {
Expand Down Expand Up @@ -292,6 +275,49 @@ export class JobService {
return jobEntity.id;
}

private async getDefaultOracles(
chainId: ChainId,
requestType: JobRequestType,
): Promise<{
reputationOracle: string;
exchangeOracle: string;
recordingOracle: string;
}> {
if (requestType === HCaptchaJobType.HCAPTCHA) {
const oracleAddress = this.web3ConfigService.hCaptchaOracleAddress;
return {
reputationOracle: oracleAddress,
exchangeOracle: oracleAddress,
recordingOracle: oracleAddress,
};
}

if (Object.values(CvatJobType).includes(requestType as CvatJobType)) {
return {
reputationOracle: this.web3ConfigService.reputationOracleAddress,
exchangeOracle: this.web3ConfigService.cvatExchangeOracleAddress,
recordingOracle: this.web3ConfigService.cvatRecordingOracleAddress,
};
}

const reputationOracle = this.web3ConfigService.reputationOracleAddress;
const availableOracles = await this.web3Service.findAvailableOracles(
chainId,
requestType,
reputationOracle,
);

return {
reputationOracle,
exchangeOracle:
availableOracles.find((oracle) => oracle.role === Role.ExchangeOracle)
?.address || '',
recordingOracle:
availableOracles.find((oracle) => oracle.role === Role.RecordingOracle)
?.address || '',
};
}

public async createEscrow(jobEntity: JobEntity): Promise<JobEntity> {
const signer = this.web3Service.getSigner(jobEntity.chainId);

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@ describe('ManifestService', () => {
describe('createManifest', () => {
it('should create a fortune manifest', async () => {
const dto: JobFortuneDto = {
chainId: faker.number.int({ min: 1, max: 100 }),
requesterTitle: faker.lorem.sentence(),
requesterDescription: faker.lorem.sentence(),
submissionsRequired: faker.number.int({ min: 1, max: 100 }),
Expand Down
Loading
Loading