Skip to content

Commit 5671e48

Browse files
authored
[Fortune Exchange Oracle] Save the manifest url in the database when webhook is received (#2621)
* Implemented manifest url to job entity * Updated migration. Implemented set manifest url script
1 parent 4eea80b commit 5671e48

File tree

9 files changed

+201
-40
lines changed

9 files changed

+201
-40
lines changed

packages/apps/fortune/exchange-oracle/server/package.json

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,7 @@
4343
"@nestjs/testing": "^10.3.1",
4444
"@types/express": "^4.17.13",
4545
"@types/jest": "29.5.12",
46+
"@types/pg": "8.11.10",
4647
"@types/node": "22.5.4",
4748
"@types/supertest": "^6.0.2",
4849
"@typescript-eslint/eslint-plugin": "^5.0.0",
@@ -52,6 +53,7 @@
5253
"eslint-plugin-prettier": "^5.2.1",
5354
"jest": "29.7.0",
5455
"prettier": "^3.2.5",
56+
"pg": "8.13.0",
5557
"source-map-support": "^0.5.20",
5658
"supertest": "^7.0.0",
5759
"ts-jest": "29.2.2",
Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,56 @@
1+
import { EscrowClient } from "@human-protocol/sdk";
2+
3+
import { Client } from 'pg';
4+
import { ethers } from 'ethers';
5+
6+
const dbConfig = {
7+
user: process.env.POSTGRES_USER,
8+
host: process.env.POSTGRES_HOST,
9+
database: process.env.POSTGRES_DATABASE,
10+
password: process.env.POSTGRES_PASSWORD,
11+
port: +process.env.POSTGRES_PORT!,
12+
};
13+
14+
async function createSigner() {
15+
const provider = new ethers.JsonRpcProvider(process.env.RPC_URL);
16+
const wallet = new ethers.Wallet(process.env.WEB3_PRIVATE_KEY!);
17+
return wallet.connect(provider);
18+
}
19+
20+
async function updateJobsWithManifestUrls() {
21+
const client = new Client(dbConfig);
22+
23+
try {
24+
await client.connect();
25+
26+
const signer = await createSigner();
27+
const escrowClient = await EscrowClient.build(signer);
28+
29+
console.log('Connected to the database.');
30+
31+
const res = await client.query('SELECT * FROM "hmt"."jobs" WHERE manifest_url IS NULL');
32+
const jobsWithoutManifest = res.rows;
33+
34+
for (const job of jobsWithoutManifest) {
35+
const { escrow_address: escrowAddress, id } = job;
36+
37+
try {
38+
const manifestUrl = await escrowClient.getManifestUrl(escrowAddress);
39+
40+
if (manifestUrl) {
41+
await client.query('UPDATE "hmt"."jobs" SET manifest_url = $1 WHERE id = $2', [manifestUrl, id]);
42+
console.log(`Updated job ${id} with manifestUrl: ${manifestUrl}`);
43+
}
44+
} catch (error) {
45+
console.error(`Failed to update job ${id}:`, error);
46+
}
47+
}
48+
} catch (error) {
49+
console.error('Failed to update manifest URLs:', error);
50+
} finally {
51+
await client.end();
52+
console.log('Disconnected from the database.');
53+
}
54+
}
55+
56+
updateJobsWithManifestUrls();
Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
import { MigrationInterface, QueryRunner } from "typeorm";
2+
3+
export class AddManifestUrl1728552691568 implements MigrationInterface {
4+
name = 'AddManifestUrl1728552691568'
5+
6+
public async up(queryRunner: QueryRunner): Promise<void> {
7+
await queryRunner.query(`
8+
ALTER TABLE "hmt"."jobs"
9+
ADD "manifest_url" character varying
10+
`);
11+
}
12+
13+
public async down(queryRunner: QueryRunner): Promise<void> {
14+
await queryRunner.query(`
15+
ALTER TABLE "hmt"."jobs" DROP COLUMN "manifest_url"
16+
`);
17+
}
18+
19+
}

packages/apps/fortune/exchange-oracle/server/src/modules/assignment/assignment.service.spec.ts

Lines changed: 13 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
import { createMock } from '@golevelup/ts-jest';
22
import { ConfigService } from '@nestjs/config';
33
import { Test } from '@nestjs/testing';
4-
import { MOCK_ADDRESS, MOCK_PRIVATE_KEY } from '../../../test/constants';
4+
import { MOCK_ADDRESS, MOCK_MANIFEST_URL, MOCK_PRIVATE_KEY } from '../../../test/constants';
55
import { TOKEN } from '../../common/constant';
66
import { AssignmentStatus, JobType } from '../../common/enums/job';
77
import { AssignmentRepository } from '../assignment/assignment.repository';
@@ -112,6 +112,7 @@ describe('AssignmentService', () => {
112112
.spyOn(jobRepository, 'findOneByChainIdAndEscrowAddress')
113113
.mockResolvedValue({
114114
id: 1,
115+
manifestUrl: MOCK_MANIFEST_URL,
115116
reputationNetwork: reputationNetwork,
116117
} as any);
117118
jest
@@ -132,7 +133,7 @@ describe('AssignmentService', () => {
132133

133134
expect(result).toEqual(undefined);
134135
expect(assignmentRepository.createUnique).toHaveBeenCalledWith({
135-
job: { id: 1, reputationNetwork: reputationNetwork },
136+
job: { id: 1, manifestUrl: MOCK_MANIFEST_URL, reputationNetwork: reputationNetwork },
136137
workerAddress: workerAddress,
137138
status: AssignmentStatus.ACTIVE,
138139
expiresAt: expect.any(Date),
@@ -141,6 +142,7 @@ describe('AssignmentService', () => {
141142
expect(jobService.getManifest).toHaveBeenCalledWith(
142143
chainId,
143144
escrowAddress,
145+
MOCK_MANIFEST_URL
144146
);
145147
});
146148

@@ -149,6 +151,7 @@ describe('AssignmentService', () => {
149151
.spyOn(jobRepository, 'findOneByChainIdAndEscrowAddress')
150152
.mockResolvedValue({
151153
id: 1,
154+
manifestUrl: MOCK_MANIFEST_URL,
152155
reputationNetwork: reputationNetwork,
153156
} as any);
154157
jest
@@ -171,6 +174,7 @@ describe('AssignmentService', () => {
171174
expect(jobService.getManifest).toHaveBeenCalledWith(
172175
chainId,
173176
escrowAddress,
177+
MOCK_MANIFEST_URL
174178
);
175179
});
176180

@@ -194,6 +198,7 @@ describe('AssignmentService', () => {
194198
.spyOn(jobRepository, 'findOneByChainIdAndEscrowAddress')
195199
.mockResolvedValue({
196200
id: 1,
201+
manifestUrl: MOCK_MANIFEST_URL,
197202
reputationNetwork: differentReputationNetwork,
198203
} as any);
199204

@@ -210,6 +215,7 @@ describe('AssignmentService', () => {
210215
.spyOn(jobRepository, 'findOneByChainIdAndEscrowAddress')
211216
.mockResolvedValue({
212217
id: 1,
218+
manifestUrl: MOCK_MANIFEST_URL,
213219
reputationNetwork: reputationNetwork,
214220
} as any);
215221
jest
@@ -229,6 +235,7 @@ describe('AssignmentService', () => {
229235
.spyOn(jobRepository, 'findOneByChainIdAndEscrowAddress')
230236
.mockResolvedValue({
231237
id: 1,
238+
manifestUrl: MOCK_MANIFEST_URL,
232239
reputationNetwork: reputationNetwork,
233240
} as any);
234241
jest
@@ -251,6 +258,7 @@ describe('AssignmentService', () => {
251258
.spyOn(jobRepository, 'findOneByChainIdAndEscrowAddress')
252259
.mockResolvedValue({
253260
id: 1,
261+
manifestUrl: MOCK_MANIFEST_URL,
254262
reputationNetwork: reputationNetwork,
255263
} as any);
256264
jest
@@ -274,6 +282,7 @@ describe('AssignmentService', () => {
274282
.spyOn(jobRepository, 'findOneByChainIdAndEscrowAddress')
275283
.mockResolvedValue({
276284
id: 1,
285+
manifestUrl: MOCK_MANIFEST_URL,
277286
reputationNetwork: reputationNetwork,
278287
} as any);
279288
jest
@@ -303,6 +312,7 @@ describe('AssignmentService', () => {
303312
job: {
304313
chainId: 1,
305314
escrowAddress,
315+
manifestUrl: MOCK_MANIFEST_URL
306316
},
307317
status: AssignmentStatus.ACTIVE,
308318
createdAt: new Date(),
@@ -362,6 +372,7 @@ describe('AssignmentService', () => {
362372
expect(jobService.getManifest).toHaveBeenCalledWith(
363373
chainId,
364374
escrowAddress,
375+
MOCK_MANIFEST_URL
365376
);
366377
expect(assignmentRepository.fetchFiltered).toHaveBeenCalledWith({
367378
page: 0,

packages/apps/fortune/exchange-oracle/server/src/modules/assignment/assignment.service.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -70,6 +70,7 @@ export class AssignmentService {
7070
const manifest = await this.jobService.getManifest(
7171
data.chainId,
7272
data.escrowAddress,
73+
jobEntity.manifestUrl
7374
);
7475

7576
// Check if all required qualifications are present

packages/apps/fortune/exchange-oracle/server/src/modules/job/job.entity.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,9 @@ export class JobEntity extends BaseEntity {
1414
@Column({ type: 'varchar' })
1515
public escrowAddress: string;
1616

17+
@Column({ type: 'varchar', nullable: true })
18+
public manifestUrl: string;
19+
1720
@Column({
1821
type: 'enum',
1922
enum: JobStatus,

packages/apps/fortune/exchange-oracle/server/src/modules/job/job.service.spec.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -142,6 +142,7 @@ describe('JobService', () => {
142142
beforeAll(async () => {
143143
jest.spyOn(jobRepository, 'createUnique');
144144
(EscrowClient.build as any).mockImplementation(() => ({
145+
getManifestUrl: jest.fn().mockResolvedValue(MOCK_MANIFEST_URL),
145146
getReputationOracleAddress: jest
146147
.fn()
147148
.mockResolvedValue(reputationNetwork),
@@ -163,6 +164,7 @@ describe('JobService', () => {
163164
expect(jobRepository.createUnique).toHaveBeenCalledWith({
164165
chainId: chainId,
165166
escrowAddress: escrowAddress,
167+
manifestUrl: MOCK_MANIFEST_URL,
166168
reputationNetwork: reputationNetwork,
167169
status: JobStatus.ACTIVE,
168170
});
@@ -337,6 +339,7 @@ describe('JobService', () => {
337339
jobId: 1,
338340
chainId: 1,
339341
escrowAddress,
342+
manifestUrl: MOCK_MANIFEST_URL,
340343
status: JobStatus.ACTIVE,
341344
createdAt: new Date(),
342345
},
@@ -384,6 +387,7 @@ describe('JobService', () => {
384387
expect(jobService.getManifest).toHaveBeenCalledWith(
385388
chainId,
386389
escrowAddress,
390+
MOCK_MANIFEST_URL
387391
);
388392
});
389393

packages/apps/fortune/exchange-oracle/server/src/modules/job/job.service.ts

Lines changed: 13 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -53,24 +53,26 @@ export class JobService {
5353
) {}
5454

5555
public async createJob(webhook: WebhookDto): Promise<void> {
56+
const { chainId, escrowAddress } = webhook;
5657
const jobEntity = await this.jobRepository.findOneByChainIdAndEscrowAddress(
57-
webhook.chainId,
58-
webhook.escrowAddress,
58+
chainId,
59+
escrowAddress,
5960
);
6061

6162
if (jobEntity) {
6263
this.logger.log(ErrorJob.AlreadyExists, JobService.name);
6364
throw new BadRequestException(ErrorJob.AlreadyExists);
6465
}
6566

66-
const signer = this.web3Service.getSigner(webhook.chainId);
67+
const signer = this.web3Service.getSigner(chainId);
6768
const escrowClient = await EscrowClient.build(signer);
6869
const reputationOracleAddress =
69-
await escrowClient.getReputationOracleAddress(webhook.escrowAddress);
70+
await escrowClient.getReputationOracleAddress(escrowAddress);
7071

7172
const newJobEntity = new JobEntity();
72-
newJobEntity.escrowAddress = webhook.escrowAddress;
73-
newJobEntity.chainId = webhook.chainId;
73+
newJobEntity.escrowAddress = escrowAddress;
74+
newJobEntity.manifestUrl = await escrowClient.getManifestUrl(escrowAddress);
75+
newJobEntity.chainId = chainId;
7476
newJobEntity.status = JobStatus.ACTIVE;
7577
newJobEntity.reputationNetwork = reputationOracleAddress;
7678
await this.jobRepository.createUnique(newJobEntity);
@@ -164,6 +166,7 @@ export class JobService {
164166
const manifest = await this.getManifest(
165167
entity.chainId,
166168
entity.escrowAddress,
169+
entity.manifestUrl
167170
);
168171
if (data.fields?.includes(JobFieldName.JobDescription)) {
169172
job.jobDescription = manifest.requesterDescription;
@@ -217,6 +220,7 @@ export class JobService {
217220
await this.addSolution(
218221
assignment.job.chainId,
219222
assignment.job.escrowAddress,
223+
assignment.job.manifestUrl,
220224
assignment.workerAddress,
221225
solution,
222226
);
@@ -278,6 +282,7 @@ export class JobService {
278282
private async addSolution(
279283
chainId: ChainId,
280284
escrowAddress: string,
285+
manifestUrl: string,
281286
workerAddress: string,
282287
solution: string,
283288
): Promise<string> {
@@ -294,7 +299,7 @@ export class JobService {
294299
throw new BadRequestException(ErrorJob.SolutionAlreadySubmitted);
295300
}
296301

297-
const manifest = await this.getManifest(chainId, escrowAddress);
302+
const manifest = await this.getManifest(chainId, escrowAddress, manifestUrl);
298303
if (
299304
existingJobSolutions.filter((solution) => !solution.error).length >=
300305
manifest.submissionsRequired
@@ -322,10 +327,8 @@ export class JobService {
322327
public async getManifest(
323328
chainId: number,
324329
escrowAddress: string,
330+
manifestUrl: string,
325331
): Promise<ManifestDto> {
326-
const signer = this.web3Service.getSigner(chainId);
327-
const escrowClient = await EscrowClient.build(signer);
328-
const manifestUrl = await escrowClient.getManifestUrl(escrowAddress);
329332
const manifestEncrypted =
330333
await StorageClient.downloadFileFromUrl(manifestUrl);
331334

0 commit comments

Comments
 (0)