From d35ed5181e078dc9ca74a682fb38a7fc617282d9 Mon Sep 17 00:00:00 2001 From: Vlad-Adrian Bucur Date: Thu, 2 Sep 2021 15:27:16 +0300 Subject: [PATCH 1/9] Added Transaction Scam Check logic --- config/config.devnet.yaml | 4 +- config/config.mainnet.yaml | 2 + config/config.testnet.yaml | 4 +- package.json | 6 +- src/common/api.config.service.ts | 19 +- src/common/external-dtos/extras-api/index.ts | 3 + .../extras-api/scam-transaction-result.dto.ts | 6 + .../extras-api/transaction-min-info.dto.ts | 7 + .../extras-api/transaction-scam-type.enum.ts | 5 + src/common/extras-api.service.ts | 52 +++++ .../entities/transaction-scam-info.ts | 6 + .../entities/transaction-scam-type.enum.ts | 20 ++ .../entities/transaction.detailed.ts | 14 +- ...potential-scam-transaction.checker.spec.ts | 173 +++++++++++++++++ .../potential-scam-transaction.checker.ts | 24 +++ .../transaction-scam-check.service.spec.ts | 178 ++++++++++++++++++ .../transaction-scam-check.service.ts | 81 ++++++++ .../transactions/transaction.service.ts | 32 ++-- src/public.app.module.ts | 12 +- 19 files changed, 619 insertions(+), 29 deletions(-) create mode 100644 src/common/external-dtos/extras-api/index.ts create mode 100644 src/common/external-dtos/extras-api/scam-transaction-result.dto.ts create mode 100644 src/common/external-dtos/extras-api/transaction-min-info.dto.ts create mode 100644 src/common/external-dtos/extras-api/transaction-scam-type.enum.ts create mode 100644 src/common/extras-api.service.ts create mode 100644 src/endpoints/transactions/entities/transaction-scam-info.ts create mode 100644 src/endpoints/transactions/entities/transaction-scam-type.enum.ts create mode 100644 src/endpoints/transactions/scam-check/potential-scam-transaction.checker.spec.ts create mode 100644 src/endpoints/transactions/scam-check/potential-scam-transaction.checker.ts create mode 100644 src/endpoints/transactions/scam-check/transaction-scam-check.service.spec.ts create mode 100644 src/endpoints/transactions/scam-check/transaction-scam-check.service.ts diff --git a/config/config.devnet.yaml b/config/config.devnet.yaml index 1f7fa6e40..b8f7785e2 100644 --- a/config/config.devnet.yaml +++ b/config/config.devnet.yaml @@ -19,6 +19,7 @@ urls: redis: '127.0.0.1' providers: 'https://devnet-delegation.maiarbrowser.com/providers' media: 'https://devnet-media.elrond.com' + extras: 'https://devnet-extras-api.elrond.com' caching: cacheTtl: 6 processTtl: 600 @@ -40,4 +41,5 @@ inflation: - 1335663 - 1130177 - 924690 - - 719203 \ No newline at end of file + - 719203 +transactionsScamCheck: true \ No newline at end of file diff --git a/config/config.mainnet.yaml b/config/config.mainnet.yaml index 579c55889..ed5e9d98e 100644 --- a/config/config.mainnet.yaml +++ b/config/config.mainnet.yaml @@ -20,6 +20,7 @@ urls: redis: '127.0.0.1' providers: 'https://internal-delegation-api.elrond.com/providers' media: 'https://media.elrond.com' + extras: 'https://extras-api.elrond.com' caching: cacheTtl: 6 processTtl: 600 @@ -42,3 +43,4 @@ inflation: - 1130177 - 924690 - 719203 +transactionsScamCheck: true diff --git a/config/config.testnet.yaml b/config/config.testnet.yaml index 169f1b0e2..5097a3a72 100644 --- a/config/config.testnet.yaml +++ b/config/config.testnet.yaml @@ -19,6 +19,7 @@ urls: redis: '127.0.0.1' providers: 'https://delegation.maiarbrowser.com/providers' media: 'https://testnet-media.elrond.com' + extras: 'https://devnet-extras-api.elrond.com' caching: cacheTtl: 6 processTtl: 600 @@ -40,4 +41,5 @@ inflation: - 1335663 - 1130177 - 924690 - - 719203 \ No newline at end of file + - 719203 +transactionsScamCheck: true \ No newline at end of file diff --git a/package.json b/package.json index 9cca5969b..4a7aca229 100644 --- a/package.json +++ b/package.json @@ -113,10 +113,6 @@ ], "moduleDirectories": [ "node_modules" - ], - "typeRoots": [ - "../node_modules/@types", - "./test/typings" - ] + ] } } diff --git a/src/common/api.config.service.ts b/src/common/api.config.service.ts index dec5a24cf..7ee28fafc 100644 --- a/src/common/api.config.service.ts +++ b/src/common/api.config.service.ts @@ -3,7 +3,7 @@ import { ConfigService } from "@nestjs/config"; @Injectable() export class ApiConfigService { - constructor(private readonly configService: ConfigService) {} + constructor(private readonly configService: ConfigService) { } getApiUrls(): string[] { const apiUrls = this.configService.get('urls.api'); @@ -225,4 +225,21 @@ export class ApiConfigService { return mediaUrl; } + + getExtrasApiUrl(): string { + let extrasApiUrl = this.configService.get('urls.extras'); + if (!extrasApiUrl) { + throw new Error('No extras api url present'); + } + + return extrasApiUrl; + } + + getTransactionsScamCheck(): boolean { + var transactionsScamCheck = this.configService.get('transactionsScamCheck'); + if (transactionsScamCheck === undefined) { + throw new Error('No extras api url present'); + } + return transactionsScamCheck; + } } \ No newline at end of file diff --git a/src/common/external-dtos/extras-api/index.ts b/src/common/external-dtos/extras-api/index.ts new file mode 100644 index 000000000..4b14b4369 --- /dev/null +++ b/src/common/external-dtos/extras-api/index.ts @@ -0,0 +1,3 @@ +export * from './scam-transaction-result.dto'; +export * from './transaction-min-info.dto'; +export * from './transaction-scam-type.enum'; \ No newline at end of file diff --git a/src/common/external-dtos/extras-api/scam-transaction-result.dto.ts b/src/common/external-dtos/extras-api/scam-transaction-result.dto.ts new file mode 100644 index 000000000..306c80184 --- /dev/null +++ b/src/common/external-dtos/extras-api/scam-transaction-result.dto.ts @@ -0,0 +1,6 @@ +import { ExtrasApiTransactionScamType } from './transaction-scam-type.enum'; + +export class ExtrasApiScamTransactionResult { + type: ExtrasApiTransactionScamType = ExtrasApiTransactionScamType.none; + info?: string | null; +} \ No newline at end of file diff --git a/src/common/external-dtos/extras-api/transaction-min-info.dto.ts b/src/common/external-dtos/extras-api/transaction-min-info.dto.ts new file mode 100644 index 000000000..06d7d1f83 --- /dev/null +++ b/src/common/external-dtos/extras-api/transaction-min-info.dto.ts @@ -0,0 +1,7 @@ +export class ExtrasApiTransactionMinInfoDto { + sender: string = ''; + receiver: string = ''; + data: string = ''; + value: string = ''; + hasScResults: boolean = false; +} \ No newline at end of file diff --git a/src/common/external-dtos/extras-api/transaction-scam-type.enum.ts b/src/common/external-dtos/extras-api/transaction-scam-type.enum.ts new file mode 100644 index 000000000..31b7e069e --- /dev/null +++ b/src/common/external-dtos/extras-api/transaction-scam-type.enum.ts @@ -0,0 +1,5 @@ +export enum ExtrasApiTransactionScamType { + none = 'none', + potentialScam = 'potentialScam', + scam = 'scam' +} \ No newline at end of file diff --git a/src/common/extras-api.service.ts b/src/common/extras-api.service.ts new file mode 100644 index 000000000..f460b9f5a --- /dev/null +++ b/src/common/extras-api.service.ts @@ -0,0 +1,52 @@ +import { forwardRef, Inject, Logger, Injectable } from '@nestjs/common'; +import { ApiConfigService } from './api.config.service'; +import { ApiService } from './api.service'; +import { ExtrasApiScamTransactionResult, ExtrasApiTransactionMinInfoDto } from './external-dtos/extras-api'; + +@Injectable() +export class ExtrasApiService { + private readonly logger: Logger; + + constructor( + private readonly apiConfigService: ApiConfigService, + @Inject(forwardRef(() => ApiService)) + private readonly apiService: ApiService + ) { + this.logger = new Logger(ExtrasApiService.name); + } + + async get(url: string): Promise { + let result = await this.getRaw(url); + return result.data.data; + } + + async getRaw(url: string): Promise { + return await this.apiService.get(`${this.getServiceUrl()}/${url}`); + } + + async create(url: string, data: any): Promise { + let result = await this.createRaw(url, data); + return result.data.data; + } + + async createRaw(url: string, data: any): Promise { + return await this.apiService.post(`${this.getServiceUrl()}/${url}`, data); + } + + async checkScamTransaction(transactionMinInfoDto: ExtrasApiTransactionMinInfoDto): Promise { + try { + const result: ExtrasApiScamTransactionResult = (await this.apiService.post(`${this.getServiceUrl()}/transactions/check-scam`, transactionMinInfoDto))?.data; + return result; + } catch (err) { + this.logger.error('An error occurred while calling check scam transaction API.', { + exception: err.toString(), + txInfo: transactionMinInfoDto, + }); + return null; + } + } + + private getServiceUrl(): string { + return this.apiConfigService.getExtrasApiUrl(); + } +} \ No newline at end of file diff --git a/src/endpoints/transactions/entities/transaction-scam-info.ts b/src/endpoints/transactions/entities/transaction-scam-info.ts new file mode 100644 index 000000000..f811cf1b3 --- /dev/null +++ b/src/endpoints/transactions/entities/transaction-scam-info.ts @@ -0,0 +1,6 @@ +import { TransactionScamType } from './transaction-scam-type.enum'; + +export class TransactionScamInfo { + type: TransactionScamType = TransactionScamType.none; + info?: string | null; +} \ No newline at end of file diff --git a/src/endpoints/transactions/entities/transaction-scam-type.enum.ts b/src/endpoints/transactions/entities/transaction-scam-type.enum.ts new file mode 100644 index 000000000..936857e23 --- /dev/null +++ b/src/endpoints/transactions/entities/transaction-scam-type.enum.ts @@ -0,0 +1,20 @@ +import { ExtrasApiTransactionScamType } from "src/common/external-dtos/extras-api"; + +export enum TransactionScamType { + none = 'none', + potentialScam = 'potentialScam', + scam = 'scam' +} + +export const mapTransactionScamTypeFromExtrasApi = (type: ExtrasApiTransactionScamType | null): TransactionScamType => { + switch (type) { + case ExtrasApiTransactionScamType.none: + return TransactionScamType.none; + case ExtrasApiTransactionScamType.potentialScam: + return TransactionScamType.potentialScam; + case ExtrasApiTransactionScamType.scam: + return TransactionScamType.scam; + default: + return TransactionScamType.none; + } +} \ No newline at end of file diff --git a/src/endpoints/transactions/entities/transaction.detailed.ts b/src/endpoints/transactions/entities/transaction.detailed.ts index 95cef91ab..3c4066cb6 100644 --- a/src/endpoints/transactions/entities/transaction.detailed.ts +++ b/src/endpoints/transactions/entities/transaction.detailed.ts @@ -1,8 +1,9 @@ -import { ApiProperty } from "@nestjs/swagger"; -import { SmartContractResult } from "./smart.contract.result"; -import { Transaction } from "./transaction"; -import { TransactionReceipt } from "./transaction.receipt"; -import { TransactionLog } from "./transaction.log"; +import { ApiProperty } from '@nestjs/swagger'; +import { SmartContractResult } from './smart.contract.result'; +import { Transaction } from './transaction'; +import { TransactionReceipt } from './transaction.receipt'; +import { TransactionLog } from './transaction.log'; +import { TransactionScamInfo } from './transaction-scam-info'; export class TransactionDetailed extends Transaction { @ApiProperty({ type: SmartContractResult, isArray: true }) @@ -16,5 +17,8 @@ export class TransactionDetailed extends Transaction { @ApiProperty({ type: TransactionLog }) logs: TransactionLog | undefined = undefined; + + @ApiProperty({ type: TransactionScamInfo }) + scamInfo?: TransactionScamInfo | null; } diff --git a/src/endpoints/transactions/scam-check/potential-scam-transaction.checker.spec.ts b/src/endpoints/transactions/scam-check/potential-scam-transaction.checker.spec.ts new file mode 100644 index 000000000..232f2e18d --- /dev/null +++ b/src/endpoints/transactions/scam-check/potential-scam-transaction.checker.spec.ts @@ -0,0 +1,173 @@ +import { SmartContractResult } from "../entities/smart.contract.result"; +import { TransactionDetailed } from "../entities/transaction.detailed"; +import { PotentialScamTransactionChecker } from "./potential-scam-transaction.checker"; + + +describe('PotentialScamTransactionChecker', () => { + let potentialScamTransactionChecker: PotentialScamTransactionChecker = new PotentialScamTransactionChecker(); + + it('empty data, no SC results, both non SC addresses - returns false', async () => { + // Arrange. + const input: TransactionDetailed = new TransactionDetailed(); + input.data = ''; + input.sender = 'erd15ws5qefhx49n666qtksumyr4tcy6ynzzga8frq4zfazexd3xng0s4explm'; + input.receiver = 'erd1k7j6ewjsla4zsgv8v6f6fe3dvrkgv3d0d9jerczw45hzedhyed8sh2u34u'; + input.results = []; + + // Act. + const result = await potentialScamTransactionChecker.check(input); + + // Assert. + expect(result).toBe(false); + }); + + it('data less than min, no SC results, both non SC addresses - returns false', async () => { + // Arrange. + const input: TransactionDetailed = new TransactionDetailed(); + input.data = 'a'.repeat(potentialScamTransactionChecker.minDataLength - 1); + input.sender = 'erd15ws5qefhx49n666qtksumyr4tcy6ynzzga8frq4zfazexd3xng0s4explm'; + input.receiver = 'erd1k7j6ewjsla4zsgv8v6f6fe3dvrkgv3d0d9jerczw45hzedhyed8sh2u34u'; + input.results = []; + + // Act. + const result = await potentialScamTransactionChecker.check(input); + + // Assert. + expect(result).toBe(false); + }); + + it('data equal to min, no SC results, both non SC addresses - returns true', async () => { + // Arrange. + const input: TransactionDetailed = new TransactionDetailed(); + input.data = 'a'.repeat(potentialScamTransactionChecker.minDataLength); + input.sender = 'erd15ws5qefhx49n666qtksumyr4tcy6ynzzga8frq4zfazexd3xng0s4explm'; + input.receiver = 'erd1k7j6ewjsla4zsgv8v6f6fe3dvrkgv3d0d9jerczw45hzedhyed8sh2u34u'; + input.results = []; + + // Act. + const result = await potentialScamTransactionChecker.check(input); + + // Assert. + expect(result).toBe(true); + }); + + it('data more than min, less than max, no SC results, both non SC addresses - returns true', async () => { + // Arrange. + const input: TransactionDetailed = new TransactionDetailed(); + input.data = 'a'.repeat(potentialScamTransactionChecker.minDataLength + 1); + input.sender = 'erd15ws5qefhx49n666qtksumyr4tcy6ynzzga8frq4zfazexd3xng0s4explm'; + input.receiver = 'erd1k7j6ewjsla4zsgv8v6f6fe3dvrkgv3d0d9jerczw45hzedhyed8sh2u34u'; + input.results = []; + + // Act. + const result = await potentialScamTransactionChecker.check(input); + + // Assert. + expect(result).toBe(true); + }); + + it('data more than min, less than max V2, no SC results, both non SC addresses - returns true', async () => { + // Arrange. + const input: TransactionDetailed = new TransactionDetailed(); + input.data = 'a'.repeat(potentialScamTransactionChecker.maxDataLength - 1); + input.sender = 'erd15ws5qefhx49n666qtksumyr4tcy6ynzzga8frq4zfazexd3xng0s4explm'; + input.receiver = 'erd1k7j6ewjsla4zsgv8v6f6fe3dvrkgv3d0d9jerczw45hzedhyed8sh2u34u'; + input.results = []; + + // Act. + const result = await potentialScamTransactionChecker.check(input); + + // Assert. + expect(result).toBe(true); + }); + + it('data equal to max, no SC results, both non SC addresses - returns true', async () => { + // Arrange. + const input: TransactionDetailed = new TransactionDetailed(); + input.data = 'a'.repeat(potentialScamTransactionChecker.maxDataLength); + input.sender = 'erd15ws5qefhx49n666qtksumyr4tcy6ynzzga8frq4zfazexd3xng0s4explm'; + input.receiver = 'erd1k7j6ewjsla4zsgv8v6f6fe3dvrkgv3d0d9jerczw45hzedhyed8sh2u34u'; + input.results = []; + + // Act. + const result = await potentialScamTransactionChecker.check(input); + + // Assert. + expect(result).toBe(true); + }); + + it('data more than max, no SC results, both non SC addresses - returns false', async () => { + // Arrange. + const input: TransactionDetailed = new TransactionDetailed(); + input.data = 'a'.repeat(potentialScamTransactionChecker.maxDataLength + 1); + input.sender = 'erd15ws5qefhx49n666qtksumyr4tcy6ynzzga8frq4zfazexd3xng0s4explm'; + input.receiver = 'erd1k7j6ewjsla4zsgv8v6f6fe3dvrkgv3d0d9jerczw45hzedhyed8sh2u34u'; + input.results = []; + + // Act. + const result = await potentialScamTransactionChecker.check(input); + + // Assert. + expect(result).toBe(false); + }); + + it('data more than max, no SC results, both non SC addresses - returns false', async () => { + // Arrange. + const input: TransactionDetailed = new TransactionDetailed(); + input.data = 'a'.repeat(potentialScamTransactionChecker.maxDataLength + 1); + input.sender = 'erd15ws5qefhx49n666qtksumyr4tcy6ynzzga8frq4zfazexd3xng0s4explm'; + input.receiver = 'erd1k7j6ewjsla4zsgv8v6f6fe3dvrkgv3d0d9jerczw45hzedhyed8sh2u34u'; + input.results = []; + + // Act. + const result = await potentialScamTransactionChecker.check(input); + + // Assert. + expect(result).toBe(false); + }); + + it('data between min and max, with SC results, both non SC addresses - returns false', async () => { + // Arrange. + const input: TransactionDetailed = new TransactionDetailed(); + input.data = 'a'.repeat(potentialScamTransactionChecker.minDataLength + 1); + input.sender = 'erd15ws5qefhx49n666qtksumyr4tcy6ynzzga8frq4zfazexd3xng0s4explm'; + input.receiver = 'erd1k7j6ewjsla4zsgv8v6f6fe3dvrkgv3d0d9jerczw45hzedhyed8sh2u34u'; + input.results = [new SmartContractResult()]; + + // Act. + const result = await potentialScamTransactionChecker.check(input); + + // Assert. + expect(result).toBe(false); + }); + + it('data between min and max, no SC results, sender is SC - returns false', async () => { + // Arrange. + const input: TransactionDetailed = new TransactionDetailed(); + input.data = 'a'.repeat(potentialScamTransactionChecker.minDataLength + 1); + input.sender = 'erd1qqqqqqqqqqqqqpgqy20dhf2t8dgpfuewhzncjl8vmsw59evv0n4sd3ntck'; + input.receiver = 'erd1k7j6ewjsla4zsgv8v6f6fe3dvrkgv3d0d9jerczw45hzedhyed8sh2u34u'; + input.results = [new SmartContractResult()]; + + // Act. + const result = await potentialScamTransactionChecker.check(input); + + // Assert. + expect(result).toBe(false); + }); + + it('data between min and max, no SC results, receiver is SC - returns false', async () => { + // Arrange. + const input: TransactionDetailed = new TransactionDetailed(); + input.data = 'a'.repeat(potentialScamTransactionChecker.minDataLength + 1); + input.sender = 'erd1k7j6ewjsla4zsgv8v6f6fe3dvrkgv3d0d9jerczw45hzedhyed8sh2u34u'; + input.receiver = 'erd1qqqqqqqqqqqqqpgqy20dhf2t8dgpfuewhzncjl8vmsw59evv0n4sd3ntck'; + input.results = [new SmartContractResult()]; + + // Act. + const result = await potentialScamTransactionChecker.check(input); + + // Assert. + expect(result).toBe(false); + }); +}); diff --git a/src/endpoints/transactions/scam-check/potential-scam-transaction.checker.ts b/src/endpoints/transactions/scam-check/potential-scam-transaction.checker.ts new file mode 100644 index 000000000..aa5dbd9e8 --- /dev/null +++ b/src/endpoints/transactions/scam-check/potential-scam-transaction.checker.ts @@ -0,0 +1,24 @@ +import { Address } from '@elrondnetwork/erdjs/out'; +import { Injectable } from '@nestjs/common'; +import { SmartContractResult } from '../entities/smart.contract.result'; +import { TransactionDetailed } from '../entities/transaction.detailed'; + +@Injectable() +export class PotentialScamTransactionChecker { + readonly minDataLength: number = 10; + readonly maxDataLength: number = 1000; + + check(transaction: TransactionDetailed): boolean { + const { sender, receiver, data, results } = transaction; + return this.isDataLengthValid(data) && + !this.hasScResults(results) && + !this.isScAddress(sender) && + !this.isScAddress(receiver); + } + + private hasScResults = (results: SmartContractResult[]): boolean => results?.length > 0; + + private isDataLengthValid = (data: string): boolean => data?.length >= 10 && data?.length <= 1000; + + private isScAddress = (address: string): boolean => new Address(address).isContractAddress(); +} diff --git a/src/endpoints/transactions/scam-check/transaction-scam-check.service.spec.ts b/src/endpoints/transactions/scam-check/transaction-scam-check.service.spec.ts new file mode 100644 index 000000000..e395c89cf --- /dev/null +++ b/src/endpoints/transactions/scam-check/transaction-scam-check.service.spec.ts @@ -0,0 +1,178 @@ +import { Test } from '@nestjs/testing'; +import { ApiConfigService } from 'src/common/api.config.service'; +import { CachingService } from 'src/common/caching.service'; +import { ExtrasApiScamTransactionResult, ExtrasApiTransactionMinInfoDto, ExtrasApiTransactionScamType } from 'src/common/external-dtos/extras-api'; +import { ExtrasApiService } from 'src/common/extras-api.service'; +import { TransactionScamType } from '../entities/transaction-scam-type.enum'; +import { TransactionDetailed } from '../entities/transaction.detailed'; +import { PotentialScamTransactionChecker } from './potential-scam-transaction.checker'; +import { TransactionScamCheckService } from './transaction-scam-check.service'; + +describe('TransactionScamCheckService', () => { + let transactionScamCheckService: TransactionScamCheckService; + let potentialScamTransactionChecker: PotentialScamTransactionChecker; + let extrasApiService: ExtrasApiService; + let apiConfigService: ApiConfigService; + let cachingService: CachingService; + + beforeEach(async () => { + const module = await Test.createTestingModule({ + providers: [ + TransactionScamCheckService, + PotentialScamTransactionChecker, + { + provide: ExtrasApiService, + useValue: { + checkScamTransaction() { return; } + }, + }, + { + provide: ApiConfigService, + useValue: { + getTransactionsScamCheck() { return; } + }, + }, + { + provide: CachingService, + useValue: { + getOrSetCache() { return; } + }, + } + ], + }).compile(); + + cachingService = module.get(CachingService); + apiConfigService = module.get(ApiConfigService); + extrasApiService = module.get(ExtrasApiService); + potentialScamTransactionChecker = module.get(PotentialScamTransactionChecker); + transactionScamCheckService = module.get(TransactionScamCheckService); + }); + + it('transactions scam check disabled - returns null', async () => { + // Arrange. + const tx = new TransactionDetailed(); + jest.spyOn(apiConfigService, 'getTransactionsScamCheck').mockImplementation(() => false); + jest.spyOn(potentialScamTransactionChecker, 'check').mockImplementation(() => true); + const extrasApiScamTransactionResult = new ExtrasApiScamTransactionResult(); + jest.spyOn(extrasApiService, 'checkScamTransaction').mockImplementation(() => Promise.resolve(extrasApiScamTransactionResult)); + jest.spyOn(cachingService, 'getOrSetCache').mockImplementation(() => extrasApiService.checkScamTransaction(new ExtrasApiTransactionMinInfoDto())); + + // Act. + const result = await transactionScamCheckService.getScamInfo(tx); + + // Assert. + expect(result).toBeNull(); + expect(apiConfigService.getTransactionsScamCheck).toHaveBeenCalled(); + expect(potentialScamTransactionChecker.check).not.toHaveBeenCalled(); + expect(cachingService.getOrSetCache).not.toHaveBeenCalled(); + expect(extrasApiService.checkScamTransaction).not.toHaveBeenCalled(); + }); + + it('potential scam checker returns false - returns null', async () => { + // Arrange. + const tx = new TransactionDetailed(); + jest.spyOn(apiConfigService, 'getTransactionsScamCheck').mockImplementation(() => true); + jest.spyOn(potentialScamTransactionChecker, 'check').mockImplementation(() => false); + const extrasApiScamTransactionResult = new ExtrasApiScamTransactionResult(); + jest.spyOn(extrasApiService, 'checkScamTransaction').mockImplementation(() => Promise.resolve(extrasApiScamTransactionResult)); + jest.spyOn(cachingService, 'getOrSetCache').mockImplementation(() => extrasApiService.checkScamTransaction(new ExtrasApiTransactionMinInfoDto())); + + // Act. + const result = await transactionScamCheckService.getScamInfo(tx); + + // Assert. + expect(result).toBeNull(); + expect(apiConfigService.getTransactionsScamCheck).toHaveBeenCalled(); + expect(potentialScamTransactionChecker.check).toHaveBeenCalled(); + expect(cachingService.getOrSetCache).not.toHaveBeenCalled(); + expect(extrasApiService.checkScamTransaction).not.toHaveBeenCalled(); + }); + + it('scam transaction result is null - returns null', async () => { + // Arrange. + const tx = new TransactionDetailed(); + jest.spyOn(apiConfigService, 'getTransactionsScamCheck').mockImplementation(() => true); + jest.spyOn(potentialScamTransactionChecker, 'check').mockImplementation(() => true); + jest.spyOn(extrasApiService, 'checkScamTransaction').mockImplementation(() => Promise.resolve(null)); + jest.spyOn(cachingService, 'getOrSetCache').mockImplementation(() => extrasApiService.checkScamTransaction(new ExtrasApiTransactionMinInfoDto())); + + // Act. + const result = await transactionScamCheckService.getScamInfo(tx); + + // Assert. + expect(result).toBeNull(); + expect(apiConfigService.getTransactionsScamCheck).toHaveBeenCalled(); + expect(potentialScamTransactionChecker.check).toHaveBeenCalled(); + expect(cachingService.getOrSetCache).toHaveBeenCalled(); + expect(extrasApiService.checkScamTransaction).toHaveBeenCalled(); + }); + + it('scam transaction result type is none - returns null', async () => { + // Arrange. + const tx = new TransactionDetailed(); + jest.spyOn(apiConfigService, 'getTransactionsScamCheck').mockImplementation(() => true); + jest.spyOn(potentialScamTransactionChecker, 'check').mockImplementation(() => true); + const extrasApiScamTransactionResult = new ExtrasApiScamTransactionResult(); + extrasApiScamTransactionResult.type = ExtrasApiTransactionScamType.none; + jest.spyOn(extrasApiService, 'checkScamTransaction').mockImplementation(() => Promise.resolve(extrasApiScamTransactionResult)); + jest.spyOn(cachingService, 'getOrSetCache').mockImplementation(() => extrasApiService.checkScamTransaction(new ExtrasApiTransactionMinInfoDto())); + + // Act. + const result = await transactionScamCheckService.getScamInfo(tx); + + // Assert. + expect(result).toBeNull(); + expect(apiConfigService.getTransactionsScamCheck).toHaveBeenCalled(); + expect(potentialScamTransactionChecker.check).toHaveBeenCalled(); + expect(cachingService.getOrSetCache).toHaveBeenCalled(); + expect(extrasApiService.checkScamTransaction).toHaveBeenCalled(); + }); + + it('scam transaction result type is scam - returns scam', async () => { + // Arrange. + const tx = new TransactionDetailed(); + jest.spyOn(apiConfigService, 'getTransactionsScamCheck').mockImplementation(() => true); + jest.spyOn(potentialScamTransactionChecker, 'check').mockImplementation(() => true); + const extrasApiScamTransactionResult = new ExtrasApiScamTransactionResult(); + extrasApiScamTransactionResult.type = ExtrasApiTransactionScamType.scam; + extrasApiScamTransactionResult.info = 'Scam report'; + jest.spyOn(extrasApiService, 'checkScamTransaction').mockImplementation(() => Promise.resolve(extrasApiScamTransactionResult)); + jest.spyOn(cachingService, 'getOrSetCache').mockImplementation(() => extrasApiService.checkScamTransaction(new ExtrasApiTransactionMinInfoDto())); + + // Act. + const result = await transactionScamCheckService.getScamInfo(tx); + + // Assert. + expect(result).not.toBeNull(); + expect(result?.type).toEqual(TransactionScamType.scam); + expect(result?.info).toEqual('Scam report'); + expect(apiConfigService.getTransactionsScamCheck).toHaveBeenCalled(); + expect(potentialScamTransactionChecker.check).toHaveBeenCalled(); + expect(cachingService.getOrSetCache).toHaveBeenCalled(); + expect(extrasApiService.checkScamTransaction).toHaveBeenCalled(); + }); + + it('scam transaction result type is potentialScam - returns potentialScam', async () => { + // Arrange. + const tx = new TransactionDetailed(); + jest.spyOn(apiConfigService, 'getTransactionsScamCheck').mockImplementation(() => true); + jest.spyOn(potentialScamTransactionChecker, 'check').mockImplementation(() => true); + const extrasApiScamTransactionResult = new ExtrasApiScamTransactionResult(); + extrasApiScamTransactionResult.type = ExtrasApiTransactionScamType.potentialScam; + extrasApiScamTransactionResult.info = 'Potential scam report'; + jest.spyOn(extrasApiService, 'checkScamTransaction').mockImplementation(() => Promise.resolve(extrasApiScamTransactionResult)); + jest.spyOn(cachingService, 'getOrSetCache').mockImplementation(() => extrasApiService.checkScamTransaction(new ExtrasApiTransactionMinInfoDto())); + + // Act. + const result = await transactionScamCheckService.getScamInfo(tx); + + // Assert. + expect(result).not.toBeNull(); + expect(result?.type).toEqual(TransactionScamType.potentialScam); + expect(result?.info).toEqual('Potential scam report'); + expect(apiConfigService.getTransactionsScamCheck).toHaveBeenCalled(); + expect(potentialScamTransactionChecker.check).toHaveBeenCalled(); + expect(cachingService.getOrSetCache).toHaveBeenCalled(); + expect(extrasApiService.checkScamTransaction).toHaveBeenCalled(); + }); +}); diff --git a/src/endpoints/transactions/scam-check/transaction-scam-check.service.ts b/src/endpoints/transactions/scam-check/transaction-scam-check.service.ts new file mode 100644 index 000000000..7e72e950e --- /dev/null +++ b/src/endpoints/transactions/scam-check/transaction-scam-check.service.ts @@ -0,0 +1,81 @@ +import { Injectable, Logger } from '@nestjs/common'; +import { ApiConfigService } from 'src/common/api.config.service'; +import { CachingService } from 'src/common/caching.service'; +import { ExtrasApiScamTransactionResult, ExtrasApiTransactionMinInfoDto } from 'src/common/external-dtos/extras-api'; +import { ExtrasApiService } from 'src/common/extras-api.service'; +import { TransactionScamInfo } from '../entities/transaction-scam-info'; +import { mapTransactionScamTypeFromExtrasApi, TransactionScamType } from '../entities/transaction-scam-type.enum'; +import { TransactionDetailed } from '../entities/transaction.detailed'; +import { PotentialScamTransactionChecker } from './potential-scam-transaction.checker'; + +@Injectable() +export class TransactionScamCheckService { + private readonly logger: Logger + + constructor( + private readonly cachingService: CachingService, + private readonly apiConfigService: ApiConfigService, + private readonly extrasApiService: ExtrasApiService, + private readonly potentialScamTransactionChecker: PotentialScamTransactionChecker, + ) { + this.logger = new Logger(TransactionScamCheckService.name); + } + + async getScamInfo(transaction: TransactionDetailed): Promise { + try { + if (!this.isScamCheckEnabled()) { + return null; + } + + if (!this.potentialScamTransactionChecker.check(transaction)) { + return null; + } + + const result = await this.loadScamTransactionResult(transaction); + if (result === null) { + return null; + } + return this.buildResult(result); + } catch (err) { + this.logger.error('An error occurred while getting scam info.', { + exception: err.toString() + }); + return null; + } + } + + private buildResult(result: ExtrasApiScamTransactionResult): TransactionScamInfo | null { + const { type: extrasApiType, info } = result; + const type = mapTransactionScamTypeFromExtrasApi(extrasApiType); + + if (type === TransactionScamType.none) { + return null; + } + + return { + type, + info + }; + } + + private async loadScamTransactionResult(transaction: TransactionDetailed): Promise { + const input = this.buildExtrasApiTransactionMinInfoDto(transaction); + return await this.cachingService.getOrSetCache( + `scam-info.${transaction.txHash}.${input.hasScResults}`, + () => this.extrasApiService.checkScamTransaction(input), 300, 300); + } + + private buildExtrasApiTransactionMinInfoDto(transaction: TransactionDetailed): ExtrasApiTransactionMinInfoDto { + return { + data: transaction.data, + hasScResults: transaction?.results?.length > 0, + receiver: transaction.receiver, + sender: transaction.sender, + value: transaction.value, + }; + } + + private isScamCheckEnabled(): boolean { + return this.apiConfigService.getTransactionsScamCheck(); + } +} diff --git a/src/endpoints/transactions/transaction.service.ts b/src/endpoints/transactions/transaction.service.ts index cfc03adb0..ff38a4c83 100644 --- a/src/endpoints/transactions/transaction.service.ts +++ b/src/endpoints/transactions/transaction.service.ts @@ -24,6 +24,7 @@ import { TransactionFilter } from './entities/transaction.filter'; import { TransactionLog } from './entities/transaction.log'; import { TransactionReceipt } from './entities/transaction.receipt'; import { TransactionSendResult } from './entities/transaction.send.result'; +import { TransactionScamCheckService } from './scam-check/transaction-scam-check.service'; @Injectable() export class TransactionService { @@ -31,10 +32,11 @@ export class TransactionService { constructor( private readonly elasticService: ElasticService, - private readonly cachingService: CachingService, + private readonly cachingService: CachingService, private readonly gatewayService: GatewayService, private readonly apiConfigService: ApiConfigService, private readonly dataApiService: DataApiService, + private readonly transactionScamCheckService: TransactionScamCheckService, ) { this.logger = new Logger(TransactionService.name); } @@ -77,7 +79,7 @@ export class TransactionService { async getTransactionCount(filter: TransactionFilter): Promise { const elasticQueryAdapter: ElasticQuery = new ElasticQuery(); elasticQueryAdapter.condition[filter.condition ?? QueryConditionOptions.must] = this.buildTransactionFilterQuery(filter); - + if (filter.before || filter.after) { elasticQueryAdapter.filter = [ QueryType.Range('timestamp', { before: filter.before, after: filter.after }), @@ -91,8 +93,8 @@ export class TransactionService { const elasticQueryAdapter: ElasticQuery = new ElasticQuery(); const { from, size } = filter; - const pagination: ElasticPagination = { - from, size + const pagination: ElasticPagination = { + from, size }; elasticQueryAdapter.pagination = pagination; elasticQueryAdapter.condition[filter.condition ?? QueryConditionOptions.must] = this.buildTransactionFilterQuery(filter); @@ -106,7 +108,7 @@ export class TransactionService { QueryType.Range('timestamp', { before: filter.before, after: filter.after }), ] } - + let transactions = await this.elasticService.getList('transactions', 'txHash', elasticQueryAdapter); return transactions.map(transaction => ApiUtils.mergeObjects(new Transaction(), transaction)); @@ -120,9 +122,15 @@ export class TransactionService { } if (transaction !== null) { - transaction.price = await this.getTransactionPrice(transaction); + const [price, scamInfo] = await Promise.all([ + this.getTransactionPrice(transaction), + this.transactionScamCheckService.getScamInfo(transaction), + ]); + + transaction.price = price; + transaction.scamInfo = scamInfo; } - + return transaction; } @@ -210,7 +218,7 @@ export class TransactionService { const elasticQueryAdapterReceipts: ElasticQuery = new ElasticQuery(); elasticQueryAdapterReceipts.pagination = { from: 0, size: 1 }; - + const receiptHashQuery = QueryType.Match('receiptHash', txHash); elasticQueryAdapterReceipts.condition.must = [receiptHashQuery]; @@ -222,15 +230,15 @@ export class TransactionService { const elasticQueryAdapterLogs: ElasticQuery = new ElasticQuery(); elasticQueryAdapterLogs.pagination = { from: 0, size: 100 }; - + let queries = []; for (let hash of hashes) { queries.push(QueryType.Match('_id', hash)); } elasticQueryAdapterLogs.condition.should = queries; - + let logs: any[] = await this.elasticService.getLogsForTransactionHashes(elasticQueryAdapterLogs); - + for (let log of logs) { if (log._id === txHash) { transactionDetailed.logs = ApiUtils.mergeObjects(new TransactionLog(), log._source); @@ -269,7 +277,7 @@ export class TransactionService { } } } - + let result = { txHash: txHash, data: transaction.data, diff --git a/src/public.app.module.ts b/src/public.app.module.ts index 86560434a..073f64162 100644 --- a/src/public.app.module.ts +++ b/src/public.app.module.ts @@ -54,6 +54,9 @@ import { WaitingListService } from './endpoints/waiting-list/waiting.list.servic import { BlsService } from './common/bls.service'; import { TagController } from './endpoints/nfttags/tag.controller'; import { TagService } from './endpoints/nfttags/tag.service'; +import { ExtrasApiService } from './common/extras-api.service'; +import { TransactionScamCheckService } from './endpoints/transactions/scam-check/transaction-scam-check.service'; +import { PotentialScamTransactionChecker } from './endpoints/transactions/scam-check/potential-scam-transaction.checker'; const DailyRotateFile = require('winston-daily-rotate-file'); @Module({ @@ -81,23 +84,24 @@ const DailyRotateFile = require('winston-daily-rotate-file'); }), ], controllers: [ - NetworkController, AccountController, TransactionController, TokenController, BlockController, + NetworkController, AccountController, TransactionController, TokenController, BlockController, MiniBlockController, RoundController, NodeController, ProviderController, DelegationLegacyController, StakeController, DelegationController, VmQueryController, ShardController, IdentitiesController, ProxyController, KeysController, WaitingListController, TagController ], providers: [ - NetworkService, ApiConfigService, AccountService, ElasticService, GatewayService, TransactionService, + NetworkService, ApiConfigService, AccountService, ElasticService, GatewayService, TransactionService, TokenService, BlockService, MiniBlockService, RoundService, NodeService, VmQueryService, CachingService, KeybaseService, ProviderService, StakeService, LoggingInterceptor, ApiService, ProfilerService, DelegationLegacyService, DelegationService, CacheConfigService, CachingInterceptor, ShardService, MetricsService, IdentitiesService, - TokenAssetService, DataApiService, KeysService, WaitingListService, BlsService, TagService, + TokenAssetService, DataApiService, KeysService, WaitingListService, BlsService, TagService, ExtrasApiService, + TransactionScamCheckService, PotentialScamTransactionChecker, ], exports: [ ApiConfigService, RoundService, CachingService, TransactionService, GatewayService, MetricsService, NodeService, TokenService, ShardService, IdentitiesService, ProviderService, KeybaseService, DataApiService, ApiService, ] }) -export class PublicAppModule {} +export class PublicAppModule { } From d87579f90a991a495df5686a8b44e556834f29e2 Mon Sep 17 00:00:00 2001 From: Vlad-Adrian Bucur Date: Thu, 2 Sep 2021 15:39:55 +0300 Subject: [PATCH 2/9] Fixes --- config/config.devnet.yaml | 12 +++-------- config/config.mainnet.yaml | 12 +++-------- config/config.testnet.yaml | 10 ++------- src/common/api.config.service.ts | 35 ++++++++++++++++---------------- 4 files changed, 25 insertions(+), 44 deletions(-) diff --git a/config/config.devnet.yaml b/config/config.devnet.yaml index 1cbdf78ed..e6a239eb6 100644 --- a/config/config.devnet.yaml +++ b/config/config.devnet.yaml @@ -18,12 +18,9 @@ urls: - 'https://devnet-gateway.elrond.com' redis: '127.0.0.1' providers: 'https://devnet-delegation.maiarbrowser.com/providers' - media: 'https://devnet-media.elrond.com' -<<<<<<< HEAD - extras: 'https://devnet-extras-api.elrond.com' -======= + media: 'https://devnet-media.elrond.com' nftThumbnails: 'https://devnet-media.elrond.com/nfts/thumbnail' ->>>>>>> development + extras: 'https://devnet-extras-api.elrond.com' caching: cacheTtl: 6 processTtl: 600 @@ -46,10 +43,7 @@ inflation: - 1130177 - 924690 - 719203 -<<<<<<< HEAD -transactionsScamCheck: true -======= security: admins: jwtSecret: ->>>>>>> development +transactionsScamCheck: true \ No newline at end of file diff --git a/config/config.mainnet.yaml b/config/config.mainnet.yaml index 909929925..69adce1a6 100644 --- a/config/config.mainnet.yaml +++ b/config/config.mainnet.yaml @@ -19,12 +19,9 @@ urls: - 'https://gateway.elrond.com' redis: '127.0.0.1' providers: 'https://internal-delegation-api.elrond.com/providers' - media: 'https://media.elrond.com' -<<<<<<< HEAD - extras: 'https://extras-api.elrond.com' -======= + media: 'https://media.elrond.com' nftThumbnails: 'https://media.elrond.com/nfts/thumbnail' ->>>>>>> development + extras: 'https://extras-api.elrond.com' caching: cacheTtl: 6 processTtl: 600 @@ -47,10 +44,7 @@ inflation: - 1130177 - 924690 - 719203 -<<<<<<< HEAD -transactionsScamCheck: true -======= security: admins: jwtSecret: ->>>>>>> development +transactionsScamCheck: true \ No newline at end of file diff --git a/config/config.testnet.yaml b/config/config.testnet.yaml index d0fbd7bb2..a41c09985 100644 --- a/config/config.testnet.yaml +++ b/config/config.testnet.yaml @@ -19,11 +19,8 @@ urls: redis: '127.0.0.1' providers: 'https://delegation.maiarbrowser.com/providers' media: 'https://testnet-media.elrond.com' -<<<<<<< HEAD - extras: 'https://devnet-extras-api.elrond.com' -======= nftThumbnails: 'https://testnet-media.elrond.com/nfts/thumbnail' ->>>>>>> development + extras: 'https://devnet-extras-api.elrond.com' caching: cacheTtl: 6 processTtl: 600 @@ -46,10 +43,7 @@ inflation: - 1130177 - 924690 - 719203 -<<<<<<< HEAD -transactionsScamCheck: true -======= security: admins: jwtSecret: ->>>>>>> development +transactionsScamCheck: true \ No newline at end of file diff --git a/src/common/api.config.service.ts b/src/common/api.config.service.ts index c25ec02c4..341eafe03 100644 --- a/src/common/api.config.service.ts +++ b/src/common/api.config.service.ts @@ -226,23 +226,6 @@ export class ApiConfigService { return mediaUrl; } -<<<<<<< HEAD - getExtrasApiUrl(): string { - let extrasApiUrl = this.configService.get('urls.extras'); - if (!extrasApiUrl) { - throw new Error('No extras api url present'); - } - - return extrasApiUrl; - } - - getTransactionsScamCheck(): boolean { - var transactionsScamCheck = this.configService.get('transactionsScamCheck'); - if (transactionsScamCheck === undefined) { - throw new Error('No extras api url present'); - } - return transactionsScamCheck; -======= getNftThumbnailsUrl(): string { let nftThumbnailsUrl = this.configService.get('urls.nftThumbnails'); if (!nftThumbnailsUrl) { @@ -268,6 +251,22 @@ export class ApiConfigService { } return jwtSecret; ->>>>>>> development + } + + getExtrasApiUrl(): string { + let extrasApiUrl = this.configService.get('urls.extras'); + if (!extrasApiUrl) { + throw new Error('No extras api url present'); + } + + return extrasApiUrl; + } + + getTransactionsScamCheck(): boolean { + var transactionsScamCheck = this.configService.get('transactionsScamCheck'); + if (transactionsScamCheck === undefined) { + throw new Error('No extras api url present'); + } + return transactionsScamCheck; } } \ No newline at end of file From acb249795e8686805fd56a49feb1ff9274a5546c Mon Sep 17 00:00:00 2001 From: Vlad-Adrian Bucur Date: Thu, 2 Sep 2021 15:40:42 +0300 Subject: [PATCH 3/9] Fixes --- config/config.devnet.yaml | 2 +- config/config.mainnet.yaml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/config/config.devnet.yaml b/config/config.devnet.yaml index e6a239eb6..44d60808d 100644 --- a/config/config.devnet.yaml +++ b/config/config.devnet.yaml @@ -18,7 +18,7 @@ urls: - 'https://devnet-gateway.elrond.com' redis: '127.0.0.1' providers: 'https://devnet-delegation.maiarbrowser.com/providers' - media: 'https://devnet-media.elrond.com' + media: 'https://devnet-media.elrond.com' nftThumbnails: 'https://devnet-media.elrond.com/nfts/thumbnail' extras: 'https://devnet-extras-api.elrond.com' caching: diff --git a/config/config.mainnet.yaml b/config/config.mainnet.yaml index 69adce1a6..170980244 100644 --- a/config/config.mainnet.yaml +++ b/config/config.mainnet.yaml @@ -19,7 +19,7 @@ urls: - 'https://gateway.elrond.com' redis: '127.0.0.1' providers: 'https://internal-delegation-api.elrond.com/providers' - media: 'https://media.elrond.com' + media: 'https://media.elrond.com' nftThumbnails: 'https://media.elrond.com/nfts/thumbnail' extras: 'https://extras-api.elrond.com' caching: From 1e50f361c79ab900835f3cb96df8ea22cadd2a53 Mon Sep 17 00:00:00 2001 From: Vlad-Adrian Bucur Date: Thu, 2 Sep 2021 16:22:53 +0300 Subject: [PATCH 4/9] Code review fixes --- .../scam-check/transaction-scam-check.service.ts | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/endpoints/transactions/scam-check/transaction-scam-check.service.ts b/src/endpoints/transactions/scam-check/transaction-scam-check.service.ts index 7e72e950e..b9009afa6 100644 --- a/src/endpoints/transactions/scam-check/transaction-scam-check.service.ts +++ b/src/endpoints/transactions/scam-check/transaction-scam-check.service.ts @@ -3,6 +3,7 @@ import { ApiConfigService } from 'src/common/api.config.service'; import { CachingService } from 'src/common/caching.service'; import { ExtrasApiScamTransactionResult, ExtrasApiTransactionMinInfoDto } from 'src/common/external-dtos/extras-api'; import { ExtrasApiService } from 'src/common/extras-api.service'; +import { Constants } from 'src/utils/constants'; import { TransactionScamInfo } from '../entities/transaction-scam-info'; import { mapTransactionScamTypeFromExtrasApi, TransactionScamType } from '../entities/transaction-scam-type.enum'; import { TransactionDetailed } from '../entities/transaction.detailed'; @@ -61,8 +62,8 @@ export class TransactionScamCheckService { private async loadScamTransactionResult(transaction: TransactionDetailed): Promise { const input = this.buildExtrasApiTransactionMinInfoDto(transaction); return await this.cachingService.getOrSetCache( - `scam-info.${transaction.txHash}.${input.hasScResults}`, - () => this.extrasApiService.checkScamTransaction(input), 300, 300); + `scam-info.${transaction.txHash}`, + () => this.extrasApiService.checkScamTransaction(input), Constants.oneMinute() * 5); } private buildExtrasApiTransactionMinInfoDto(transaction: TransactionDetailed): ExtrasApiTransactionMinInfoDto { From 987e9bcd66de90bfbce67e4eac947e7e96e5e375 Mon Sep 17 00:00:00 2001 From: Vlad-Adrian Bucur Date: Thu, 2 Sep 2021 16:24:10 +0300 Subject: [PATCH 5/9] Code review fixes --- src/common/api.config.service.ts | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/src/common/api.config.service.ts b/src/common/api.config.service.ts index 341eafe03..b10edf164 100644 --- a/src/common/api.config.service.ts +++ b/src/common/api.config.service.ts @@ -264,9 +264,6 @@ export class ApiConfigService { getTransactionsScamCheck(): boolean { var transactionsScamCheck = this.configService.get('transactionsScamCheck'); - if (transactionsScamCheck === undefined) { - throw new Error('No extras api url present'); - } - return transactionsScamCheck; + return transactionsScamCheck ?? true; } } \ No newline at end of file From da5deb7b4bbed4788c4c93b6a60171c0e58dabba Mon Sep 17 00:00:00 2001 From: Vlad-Adrian Bucur Date: Thu, 2 Sep 2021 18:37:58 +0300 Subject: [PATCH 6/9] Additional logic fixes --- config/config.devnet.yaml | 4 +- config/config.mainnet.yaml | 4 +- config/config.testnet.yaml | 1 - src/common/api.config.service.ts | 5 - .../extras-api/transaction-min-info.dto.ts | 1 - .../entities/transaction.detailed.ts | 2 +- ...potential-scam-transaction.checker.spec.ts | 96 ++----------------- .../potential-scam-transaction.checker.ts | 10 +- .../transaction-scam-check.service.spec.ts | 47 +-------- .../transaction-scam-check.service.ts | 23 ++--- .../transactions/transaction.service.ts | 16 +++- 11 files changed, 36 insertions(+), 173 deletions(-) diff --git a/config/config.devnet.yaml b/config/config.devnet.yaml index 44d60808d..5442f19a0 100644 --- a/config/config.devnet.yaml +++ b/config/config.devnet.yaml @@ -20,7 +20,6 @@ urls: providers: 'https://devnet-delegation.maiarbrowser.com/providers' media: 'https://devnet-media.elrond.com' nftThumbnails: 'https://devnet-media.elrond.com/nfts/thumbnail' - extras: 'https://devnet-extras-api.elrond.com' caching: cacheTtl: 6 processTtl: 600 @@ -45,5 +44,4 @@ inflation: - 719203 security: admins: - jwtSecret: -transactionsScamCheck: true \ No newline at end of file + jwtSecret: \ No newline at end of file diff --git a/config/config.mainnet.yaml b/config/config.mainnet.yaml index 170980244..39797342b 100644 --- a/config/config.mainnet.yaml +++ b/config/config.mainnet.yaml @@ -21,7 +21,6 @@ urls: providers: 'https://internal-delegation-api.elrond.com/providers' media: 'https://media.elrond.com' nftThumbnails: 'https://media.elrond.com/nfts/thumbnail' - extras: 'https://extras-api.elrond.com' caching: cacheTtl: 6 processTtl: 600 @@ -46,5 +45,4 @@ inflation: - 719203 security: admins: - jwtSecret: -transactionsScamCheck: true \ No newline at end of file + jwtSecret: \ No newline at end of file diff --git a/config/config.testnet.yaml b/config/config.testnet.yaml index a41c09985..3a615c04d 100644 --- a/config/config.testnet.yaml +++ b/config/config.testnet.yaml @@ -20,7 +20,6 @@ urls: providers: 'https://delegation.maiarbrowser.com/providers' media: 'https://testnet-media.elrond.com' nftThumbnails: 'https://testnet-media.elrond.com/nfts/thumbnail' - extras: 'https://devnet-extras-api.elrond.com' caching: cacheTtl: 6 processTtl: 600 diff --git a/src/common/api.config.service.ts b/src/common/api.config.service.ts index b10edf164..353cbb7bc 100644 --- a/src/common/api.config.service.ts +++ b/src/common/api.config.service.ts @@ -261,9 +261,4 @@ export class ApiConfigService { return extrasApiUrl; } - - getTransactionsScamCheck(): boolean { - var transactionsScamCheck = this.configService.get('transactionsScamCheck'); - return transactionsScamCheck ?? true; - } } \ No newline at end of file diff --git a/src/common/external-dtos/extras-api/transaction-min-info.dto.ts b/src/common/external-dtos/extras-api/transaction-min-info.dto.ts index 06d7d1f83..bb8b38cfa 100644 --- a/src/common/external-dtos/extras-api/transaction-min-info.dto.ts +++ b/src/common/external-dtos/extras-api/transaction-min-info.dto.ts @@ -3,5 +3,4 @@ export class ExtrasApiTransactionMinInfoDto { receiver: string = ''; data: string = ''; value: string = ''; - hasScResults: boolean = false; } \ No newline at end of file diff --git a/src/endpoints/transactions/entities/transaction.detailed.ts b/src/endpoints/transactions/entities/transaction.detailed.ts index d181ac49c..f5339a368 100644 --- a/src/endpoints/transactions/entities/transaction.detailed.ts +++ b/src/endpoints/transactions/entities/transaction.detailed.ts @@ -23,6 +23,6 @@ export class TransactionDetailed extends Transaction { operations: TransactionOperation[] = []; @ApiProperty({ type: TransactionScamInfo }) - scamInfo?: TransactionScamInfo | null; + scamInfo: TransactionScamInfo | undefined = undefined; } diff --git a/src/endpoints/transactions/scam-check/potential-scam-transaction.checker.spec.ts b/src/endpoints/transactions/scam-check/potential-scam-transaction.checker.spec.ts index 232f2e18d..2d5741cc2 100644 --- a/src/endpoints/transactions/scam-check/potential-scam-transaction.checker.spec.ts +++ b/src/endpoints/transactions/scam-check/potential-scam-transaction.checker.spec.ts @@ -6,13 +6,12 @@ import { PotentialScamTransactionChecker } from "./potential-scam-transaction.ch describe('PotentialScamTransactionChecker', () => { let potentialScamTransactionChecker: PotentialScamTransactionChecker = new PotentialScamTransactionChecker(); - it('empty data, no SC results, both non SC addresses - returns false', async () => { + it('empty data - returns false', async () => { // Arrange. const input: TransactionDetailed = new TransactionDetailed(); input.data = ''; input.sender = 'erd15ws5qefhx49n666qtksumyr4tcy6ynzzga8frq4zfazexd3xng0s4explm'; input.receiver = 'erd1k7j6ewjsla4zsgv8v6f6fe3dvrkgv3d0d9jerczw45hzedhyed8sh2u34u'; - input.results = []; // Act. const result = await potentialScamTransactionChecker.check(input); @@ -21,13 +20,12 @@ describe('PotentialScamTransactionChecker', () => { expect(result).toBe(false); }); - it('data less than min, no SC results, both non SC addresses - returns false', async () => { + it('data less than min - returns false', async () => { // Arrange. const input: TransactionDetailed = new TransactionDetailed(); input.data = 'a'.repeat(potentialScamTransactionChecker.minDataLength - 1); input.sender = 'erd15ws5qefhx49n666qtksumyr4tcy6ynzzga8frq4zfazexd3xng0s4explm'; input.receiver = 'erd1k7j6ewjsla4zsgv8v6f6fe3dvrkgv3d0d9jerczw45hzedhyed8sh2u34u'; - input.results = []; // Act. const result = await potentialScamTransactionChecker.check(input); @@ -36,13 +34,12 @@ describe('PotentialScamTransactionChecker', () => { expect(result).toBe(false); }); - it('data equal to min, no SC results, both non SC addresses - returns true', async () => { + it('data equal to min - returns true', async () => { // Arrange. const input: TransactionDetailed = new TransactionDetailed(); input.data = 'a'.repeat(potentialScamTransactionChecker.minDataLength); input.sender = 'erd15ws5qefhx49n666qtksumyr4tcy6ynzzga8frq4zfazexd3xng0s4explm'; input.receiver = 'erd1k7j6ewjsla4zsgv8v6f6fe3dvrkgv3d0d9jerczw45hzedhyed8sh2u34u'; - input.results = []; // Act. const result = await potentialScamTransactionChecker.check(input); @@ -51,7 +48,7 @@ describe('PotentialScamTransactionChecker', () => { expect(result).toBe(true); }); - it('data more than min, less than max, no SC results, both non SC addresses - returns true', async () => { + it('data more than min - returns true', async () => { // Arrange. const input: TransactionDetailed = new TransactionDetailed(); input.data = 'a'.repeat(potentialScamTransactionChecker.minDataLength + 1); @@ -66,72 +63,12 @@ describe('PotentialScamTransactionChecker', () => { expect(result).toBe(true); }); - it('data more than min, less than max V2, no SC results, both non SC addresses - returns true', async () => { - // Arrange. - const input: TransactionDetailed = new TransactionDetailed(); - input.data = 'a'.repeat(potentialScamTransactionChecker.maxDataLength - 1); - input.sender = 'erd15ws5qefhx49n666qtksumyr4tcy6ynzzga8frq4zfazexd3xng0s4explm'; - input.receiver = 'erd1k7j6ewjsla4zsgv8v6f6fe3dvrkgv3d0d9jerczw45hzedhyed8sh2u34u'; - input.results = []; - - // Act. - const result = await potentialScamTransactionChecker.check(input); - - // Assert. - expect(result).toBe(true); - }); - - it('data equal to max, no SC results, both non SC addresses - returns true', async () => { - // Arrange. - const input: TransactionDetailed = new TransactionDetailed(); - input.data = 'a'.repeat(potentialScamTransactionChecker.maxDataLength); - input.sender = 'erd15ws5qefhx49n666qtksumyr4tcy6ynzzga8frq4zfazexd3xng0s4explm'; - input.receiver = 'erd1k7j6ewjsla4zsgv8v6f6fe3dvrkgv3d0d9jerczw45hzedhyed8sh2u34u'; - input.results = []; - - // Act. - const result = await potentialScamTransactionChecker.check(input); - - // Assert. - expect(result).toBe(true); - }); - - it('data more than max, no SC results, both non SC addresses - returns false', async () => { - // Arrange. - const input: TransactionDetailed = new TransactionDetailed(); - input.data = 'a'.repeat(potentialScamTransactionChecker.maxDataLength + 1); - input.sender = 'erd15ws5qefhx49n666qtksumyr4tcy6ynzzga8frq4zfazexd3xng0s4explm'; - input.receiver = 'erd1k7j6ewjsla4zsgv8v6f6fe3dvrkgv3d0d9jerczw45hzedhyed8sh2u34u'; - input.results = []; - - // Act. - const result = await potentialScamTransactionChecker.check(input); - - // Assert. - expect(result).toBe(false); - }); - - it('data more than max, no SC results, both non SC addresses - returns false', async () => { - // Arrange. - const input: TransactionDetailed = new TransactionDetailed(); - input.data = 'a'.repeat(potentialScamTransactionChecker.maxDataLength + 1); - input.sender = 'erd15ws5qefhx49n666qtksumyr4tcy6ynzzga8frq4zfazexd3xng0s4explm'; - input.receiver = 'erd1k7j6ewjsla4zsgv8v6f6fe3dvrkgv3d0d9jerczw45hzedhyed8sh2u34u'; - input.results = []; - - // Act. - const result = await potentialScamTransactionChecker.check(input); - - // Assert. - expect(result).toBe(false); - }); - - it('data between min and max, with SC results, both non SC addresses - returns false', async () => { + it('data more than min, receiver is SC - returns false', async () => { // Arrange. const input: TransactionDetailed = new TransactionDetailed(); input.data = 'a'.repeat(potentialScamTransactionChecker.minDataLength + 1); - input.sender = 'erd15ws5qefhx49n666qtksumyr4tcy6ynzzga8frq4zfazexd3xng0s4explm'; - input.receiver = 'erd1k7j6ewjsla4zsgv8v6f6fe3dvrkgv3d0d9jerczw45hzedhyed8sh2u34u'; + input.sender = 'erd1k7j6ewjsla4zsgv8v6f6fe3dvrkgv3d0d9jerczw45hzedhyed8sh2u34u'; + input.receiver = 'erd1qqqqqqqqqqqqqpgqy20dhf2t8dgpfuewhzncjl8vmsw59evv0n4sd3ntck'; input.results = [new SmartContractResult()]; // Act. @@ -141,7 +78,7 @@ describe('PotentialScamTransactionChecker', () => { expect(result).toBe(false); }); - it('data between min and max, no SC results, sender is SC - returns false', async () => { + it('data more than min, sender is SC - returns true', async () => { // Arrange. const input: TransactionDetailed = new TransactionDetailed(); input.data = 'a'.repeat(potentialScamTransactionChecker.minDataLength + 1); @@ -153,21 +90,6 @@ describe('PotentialScamTransactionChecker', () => { const result = await potentialScamTransactionChecker.check(input); // Assert. - expect(result).toBe(false); - }); - - it('data between min and max, no SC results, receiver is SC - returns false', async () => { - // Arrange. - const input: TransactionDetailed = new TransactionDetailed(); - input.data = 'a'.repeat(potentialScamTransactionChecker.minDataLength + 1); - input.sender = 'erd1k7j6ewjsla4zsgv8v6f6fe3dvrkgv3d0d9jerczw45hzedhyed8sh2u34u'; - input.receiver = 'erd1qqqqqqqqqqqqqpgqy20dhf2t8dgpfuewhzncjl8vmsw59evv0n4sd3ntck'; - input.results = [new SmartContractResult()]; - - // Act. - const result = await potentialScamTransactionChecker.check(input); - - // Assert. - expect(result).toBe(false); + expect(result).toBe(true); }); }); diff --git a/src/endpoints/transactions/scam-check/potential-scam-transaction.checker.ts b/src/endpoints/transactions/scam-check/potential-scam-transaction.checker.ts index aa5dbd9e8..a65edcade 100644 --- a/src/endpoints/transactions/scam-check/potential-scam-transaction.checker.ts +++ b/src/endpoints/transactions/scam-check/potential-scam-transaction.checker.ts @@ -1,24 +1,18 @@ import { Address } from '@elrondnetwork/erdjs/out'; import { Injectable } from '@nestjs/common'; -import { SmartContractResult } from '../entities/smart.contract.result'; import { TransactionDetailed } from '../entities/transaction.detailed'; @Injectable() export class PotentialScamTransactionChecker { readonly minDataLength: number = 10; - readonly maxDataLength: number = 1000; check(transaction: TransactionDetailed): boolean { - const { sender, receiver, data, results } = transaction; + const { receiver, data } = transaction; return this.isDataLengthValid(data) && - !this.hasScResults(results) && - !this.isScAddress(sender) && !this.isScAddress(receiver); } - private hasScResults = (results: SmartContractResult[]): boolean => results?.length > 0; - - private isDataLengthValid = (data: string): boolean => data?.length >= 10 && data?.length <= 1000; + private isDataLengthValid = (data: string): boolean => data?.length >= this.minDataLength; private isScAddress = (address: string): boolean => new Address(address).isContractAddress(); } diff --git a/src/endpoints/transactions/scam-check/transaction-scam-check.service.spec.ts b/src/endpoints/transactions/scam-check/transaction-scam-check.service.spec.ts index e395c89cf..4c8e675bc 100644 --- a/src/endpoints/transactions/scam-check/transaction-scam-check.service.spec.ts +++ b/src/endpoints/transactions/scam-check/transaction-scam-check.service.spec.ts @@ -1,5 +1,4 @@ import { Test } from '@nestjs/testing'; -import { ApiConfigService } from 'src/common/api.config.service'; import { CachingService } from 'src/common/caching.service'; import { ExtrasApiScamTransactionResult, ExtrasApiTransactionMinInfoDto, ExtrasApiTransactionScamType } from 'src/common/external-dtos/extras-api'; import { ExtrasApiService } from 'src/common/extras-api.service'; @@ -12,7 +11,6 @@ describe('TransactionScamCheckService', () => { let transactionScamCheckService: TransactionScamCheckService; let potentialScamTransactionChecker: PotentialScamTransactionChecker; let extrasApiService: ExtrasApiService; - let apiConfigService: ApiConfigService; let cachingService: CachingService; beforeEach(async () => { @@ -26,12 +24,6 @@ describe('TransactionScamCheckService', () => { checkScamTransaction() { return; } }, }, - { - provide: ApiConfigService, - useValue: { - getTransactionsScamCheck() { return; } - }, - }, { provide: CachingService, useValue: { @@ -42,36 +34,14 @@ describe('TransactionScamCheckService', () => { }).compile(); cachingService = module.get(CachingService); - apiConfigService = module.get(ApiConfigService); extrasApiService = module.get(ExtrasApiService); potentialScamTransactionChecker = module.get(PotentialScamTransactionChecker); transactionScamCheckService = module.get(TransactionScamCheckService); }); - it('transactions scam check disabled - returns null', async () => { - // Arrange. - const tx = new TransactionDetailed(); - jest.spyOn(apiConfigService, 'getTransactionsScamCheck').mockImplementation(() => false); - jest.spyOn(potentialScamTransactionChecker, 'check').mockImplementation(() => true); - const extrasApiScamTransactionResult = new ExtrasApiScamTransactionResult(); - jest.spyOn(extrasApiService, 'checkScamTransaction').mockImplementation(() => Promise.resolve(extrasApiScamTransactionResult)); - jest.spyOn(cachingService, 'getOrSetCache').mockImplementation(() => extrasApiService.checkScamTransaction(new ExtrasApiTransactionMinInfoDto())); - - // Act. - const result = await transactionScamCheckService.getScamInfo(tx); - - // Assert. - expect(result).toBeNull(); - expect(apiConfigService.getTransactionsScamCheck).toHaveBeenCalled(); - expect(potentialScamTransactionChecker.check).not.toHaveBeenCalled(); - expect(cachingService.getOrSetCache).not.toHaveBeenCalled(); - expect(extrasApiService.checkScamTransaction).not.toHaveBeenCalled(); - }); - it('potential scam checker returns false - returns null', async () => { // Arrange. const tx = new TransactionDetailed(); - jest.spyOn(apiConfigService, 'getTransactionsScamCheck').mockImplementation(() => true); jest.spyOn(potentialScamTransactionChecker, 'check').mockImplementation(() => false); const extrasApiScamTransactionResult = new ExtrasApiScamTransactionResult(); jest.spyOn(extrasApiService, 'checkScamTransaction').mockImplementation(() => Promise.resolve(extrasApiScamTransactionResult)); @@ -81,8 +51,7 @@ describe('TransactionScamCheckService', () => { const result = await transactionScamCheckService.getScamInfo(tx); // Assert. - expect(result).toBeNull(); - expect(apiConfigService.getTransactionsScamCheck).toHaveBeenCalled(); + expect(result).toBeUndefined(); expect(potentialScamTransactionChecker.check).toHaveBeenCalled(); expect(cachingService.getOrSetCache).not.toHaveBeenCalled(); expect(extrasApiService.checkScamTransaction).not.toHaveBeenCalled(); @@ -91,7 +60,6 @@ describe('TransactionScamCheckService', () => { it('scam transaction result is null - returns null', async () => { // Arrange. const tx = new TransactionDetailed(); - jest.spyOn(apiConfigService, 'getTransactionsScamCheck').mockImplementation(() => true); jest.spyOn(potentialScamTransactionChecker, 'check').mockImplementation(() => true); jest.spyOn(extrasApiService, 'checkScamTransaction').mockImplementation(() => Promise.resolve(null)); jest.spyOn(cachingService, 'getOrSetCache').mockImplementation(() => extrasApiService.checkScamTransaction(new ExtrasApiTransactionMinInfoDto())); @@ -100,8 +68,7 @@ describe('TransactionScamCheckService', () => { const result = await transactionScamCheckService.getScamInfo(tx); // Assert. - expect(result).toBeNull(); - expect(apiConfigService.getTransactionsScamCheck).toHaveBeenCalled(); + expect(result).toBeUndefined(); expect(potentialScamTransactionChecker.check).toHaveBeenCalled(); expect(cachingService.getOrSetCache).toHaveBeenCalled(); expect(extrasApiService.checkScamTransaction).toHaveBeenCalled(); @@ -110,7 +77,6 @@ describe('TransactionScamCheckService', () => { it('scam transaction result type is none - returns null', async () => { // Arrange. const tx = new TransactionDetailed(); - jest.spyOn(apiConfigService, 'getTransactionsScamCheck').mockImplementation(() => true); jest.spyOn(potentialScamTransactionChecker, 'check').mockImplementation(() => true); const extrasApiScamTransactionResult = new ExtrasApiScamTransactionResult(); extrasApiScamTransactionResult.type = ExtrasApiTransactionScamType.none; @@ -121,8 +87,7 @@ describe('TransactionScamCheckService', () => { const result = await transactionScamCheckService.getScamInfo(tx); // Assert. - expect(result).toBeNull(); - expect(apiConfigService.getTransactionsScamCheck).toHaveBeenCalled(); + expect(result).toBeUndefined(); expect(potentialScamTransactionChecker.check).toHaveBeenCalled(); expect(cachingService.getOrSetCache).toHaveBeenCalled(); expect(extrasApiService.checkScamTransaction).toHaveBeenCalled(); @@ -131,7 +96,6 @@ describe('TransactionScamCheckService', () => { it('scam transaction result type is scam - returns scam', async () => { // Arrange. const tx = new TransactionDetailed(); - jest.spyOn(apiConfigService, 'getTransactionsScamCheck').mockImplementation(() => true); jest.spyOn(potentialScamTransactionChecker, 'check').mockImplementation(() => true); const extrasApiScamTransactionResult = new ExtrasApiScamTransactionResult(); extrasApiScamTransactionResult.type = ExtrasApiTransactionScamType.scam; @@ -143,10 +107,10 @@ describe('TransactionScamCheckService', () => { const result = await transactionScamCheckService.getScamInfo(tx); // Assert. + expect(result).not.toBeUndefined(); expect(result).not.toBeNull(); expect(result?.type).toEqual(TransactionScamType.scam); expect(result?.info).toEqual('Scam report'); - expect(apiConfigService.getTransactionsScamCheck).toHaveBeenCalled(); expect(potentialScamTransactionChecker.check).toHaveBeenCalled(); expect(cachingService.getOrSetCache).toHaveBeenCalled(); expect(extrasApiService.checkScamTransaction).toHaveBeenCalled(); @@ -155,7 +119,6 @@ describe('TransactionScamCheckService', () => { it('scam transaction result type is potentialScam - returns potentialScam', async () => { // Arrange. const tx = new TransactionDetailed(); - jest.spyOn(apiConfigService, 'getTransactionsScamCheck').mockImplementation(() => true); jest.spyOn(potentialScamTransactionChecker, 'check').mockImplementation(() => true); const extrasApiScamTransactionResult = new ExtrasApiScamTransactionResult(); extrasApiScamTransactionResult.type = ExtrasApiTransactionScamType.potentialScam; @@ -167,10 +130,10 @@ describe('TransactionScamCheckService', () => { const result = await transactionScamCheckService.getScamInfo(tx); // Assert. + expect(result).not.toBeUndefined(); expect(result).not.toBeNull(); expect(result?.type).toEqual(TransactionScamType.potentialScam); expect(result?.info).toEqual('Potential scam report'); - expect(apiConfigService.getTransactionsScamCheck).toHaveBeenCalled(); expect(potentialScamTransactionChecker.check).toHaveBeenCalled(); expect(cachingService.getOrSetCache).toHaveBeenCalled(); expect(extrasApiService.checkScamTransaction).toHaveBeenCalled(); diff --git a/src/endpoints/transactions/scam-check/transaction-scam-check.service.ts b/src/endpoints/transactions/scam-check/transaction-scam-check.service.ts index b9009afa6..ecc26bb61 100644 --- a/src/endpoints/transactions/scam-check/transaction-scam-check.service.ts +++ b/src/endpoints/transactions/scam-check/transaction-scam-check.service.ts @@ -1,5 +1,4 @@ import { Injectable, Logger } from '@nestjs/common'; -import { ApiConfigService } from 'src/common/api.config.service'; import { CachingService } from 'src/common/caching.service'; import { ExtrasApiScamTransactionResult, ExtrasApiTransactionMinInfoDto } from 'src/common/external-dtos/extras-api'; import { ExtrasApiService } from 'src/common/extras-api.service'; @@ -15,42 +14,37 @@ export class TransactionScamCheckService { constructor( private readonly cachingService: CachingService, - private readonly apiConfigService: ApiConfigService, private readonly extrasApiService: ExtrasApiService, private readonly potentialScamTransactionChecker: PotentialScamTransactionChecker, ) { this.logger = new Logger(TransactionScamCheckService.name); } - async getScamInfo(transaction: TransactionDetailed): Promise { + async getScamInfo(transaction: TransactionDetailed): Promise { try { - if (!this.isScamCheckEnabled()) { - return null; - } - if (!this.potentialScamTransactionChecker.check(transaction)) { - return null; + return; } const result = await this.loadScamTransactionResult(transaction); if (result === null) { - return null; + return; } return this.buildResult(result); } catch (err) { this.logger.error('An error occurred while getting scam info.', { exception: err.toString() }); - return null; + return; } } - private buildResult(result: ExtrasApiScamTransactionResult): TransactionScamInfo | null { + private buildResult(result: ExtrasApiScamTransactionResult): TransactionScamInfo | undefined { const { type: extrasApiType, info } = result; const type = mapTransactionScamTypeFromExtrasApi(extrasApiType); if (type === TransactionScamType.none) { - return null; + return; } return { @@ -69,14 +63,9 @@ export class TransactionScamCheckService { private buildExtrasApiTransactionMinInfoDto(transaction: TransactionDetailed): ExtrasApiTransactionMinInfoDto { return { data: transaction.data, - hasScResults: transaction?.results?.length > 0, receiver: transaction.receiver, sender: transaction.sender, value: transaction.value, }; } - - private isScamCheckEnabled(): boolean { - return this.apiConfigService.getTransactionsScamCheck(); - } } diff --git a/src/endpoints/transactions/transaction.service.ts b/src/endpoints/transactions/transaction.service.ts index 207f2eadc..40c496630 100644 --- a/src/endpoints/transactions/transaction.service.ts +++ b/src/endpoints/transactions/transaction.service.ts @@ -31,6 +31,7 @@ import { TransactionOperationType } from './entities/transaction.operation.type' import { TransactionOperationAction } from './entities/transaction.operation.action'; import { QueryOperator } from 'src/common/entities/elastic/query.operator'; import { TransactionScamCheckService } from './scam-check/transaction-scam-check.service'; +import { TransactionScamInfo } from './entities/transaction-scam-info'; @Injectable() export class TransactionService { @@ -174,7 +175,7 @@ export class TransactionService { if (transaction !== null) { const [price, scamInfo] = await Promise.all([ this.getTransactionPrice(transaction), - this.transactionScamCheckService.getScamInfo(transaction), + this.getScamInfo(transaction), ]); transaction.price = price; @@ -190,10 +191,6 @@ export class TransactionService { return undefined; } - if (transaction === null) { - return undefined; - } - let transactionDate = transaction.getDate(); if (!transactionDate) { return undefined; @@ -476,4 +473,13 @@ export class TransactionService { status: 'Pending', }; } + + private async getScamInfo(transaction: TransactionDetailed): Promise { + let extrasApiUrl = this.apiConfigService.getExtrasApiUrl(); + if (!extrasApiUrl) { + return undefined; + } + + return await this.transactionScamCheckService.getScamInfo(transaction); + } } From 5cf8035ec9b44d1c7cb0929d2767dd2b150935c2 Mon Sep 17 00:00:00 2001 From: Vlad-Adrian Bucur Date: Thu, 2 Sep 2021 18:39:50 +0300 Subject: [PATCH 7/9] Fixes --- config/config.testnet.yaml | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/config/config.testnet.yaml b/config/config.testnet.yaml index 3a615c04d..605cbea2e 100644 --- a/config/config.testnet.yaml +++ b/config/config.testnet.yaml @@ -44,5 +44,4 @@ inflation: - 719203 security: admins: - jwtSecret: -transactionsScamCheck: true \ No newline at end of file + jwtSecret: \ No newline at end of file From da60f65b34b8978a47e86e418bc419c75521e972 Mon Sep 17 00:00:00 2001 From: Vlad-Adrian Bucur Date: Thu, 2 Sep 2021 18:53:26 +0300 Subject: [PATCH 8/9] Fixes --- src/common/api.config.service.ts | 9 ++------- src/common/extras-api.service.ts | 25 ++++++++----------------- 2 files changed, 10 insertions(+), 24 deletions(-) diff --git a/src/common/api.config.service.ts b/src/common/api.config.service.ts index 353cbb7bc..02fc46bbc 100644 --- a/src/common/api.config.service.ts +++ b/src/common/api.config.service.ts @@ -253,12 +253,7 @@ export class ApiConfigService { return jwtSecret; } - getExtrasApiUrl(): string { - let extrasApiUrl = this.configService.get('urls.extras'); - if (!extrasApiUrl) { - throw new Error('No extras api url present'); - } - - return extrasApiUrl; + getExtrasApiUrl(): string | undefined { + return this.configService.get('urls.extras'); } } \ No newline at end of file diff --git a/src/common/extras-api.service.ts b/src/common/extras-api.service.ts index f460b9f5a..71d966140 100644 --- a/src/common/extras-api.service.ts +++ b/src/common/extras-api.service.ts @@ -15,27 +15,18 @@ export class ExtrasApiService { this.logger = new Logger(ExtrasApiService.name); } - async get(url: string): Promise { - let result = await this.getRaw(url); - return result.data.data; - } - - async getRaw(url: string): Promise { - return await this.apiService.get(`${this.getServiceUrl()}/${url}`); - } - - async create(url: string, data: any): Promise { - let result = await this.createRaw(url, data); - return result.data.data; - } + async post(route: string, data: any): Promise { + const url = this.getServiceUrl(); + if (!url) { + return null; + } - async createRaw(url: string, data: any): Promise { - return await this.apiService.post(`${this.getServiceUrl()}/${url}`, data); + return await this.apiService.post(`${this.getServiceUrl()}/${route}`, data); } async checkScamTransaction(transactionMinInfoDto: ExtrasApiTransactionMinInfoDto): Promise { try { - const result: ExtrasApiScamTransactionResult = (await this.apiService.post(`${this.getServiceUrl()}/transactions/check-scam`, transactionMinInfoDto))?.data; + const result: ExtrasApiScamTransactionResult = (await this.apiService.post('transactions/check-scam', transactionMinInfoDto))?.data; return result; } catch (err) { this.logger.error('An error occurred while calling check scam transaction API.', { @@ -46,7 +37,7 @@ export class ExtrasApiService { } } - private getServiceUrl(): string { + private getServiceUrl(): string | undefined { return this.apiConfigService.getExtrasApiUrl(); } } \ No newline at end of file From 31e1f50143398902886d1c7ec7755a0cd9ff4f6d Mon Sep 17 00:00:00 2001 From: Vlad-Adrian Bucur Date: Thu, 2 Sep 2021 18:56:42 +0300 Subject: [PATCH 9/9] Fixes --- src/common/extras-api.service.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/common/extras-api.service.ts b/src/common/extras-api.service.ts index 71d966140..510eef87b 100644 --- a/src/common/extras-api.service.ts +++ b/src/common/extras-api.service.ts @@ -21,7 +21,7 @@ export class ExtrasApiService { return null; } - return await this.apiService.post(`${this.getServiceUrl()}/${route}`, data); + return await this.apiService.post(`${url}/${route}`, data); } async checkScamTransaction(transactionMinInfoDto: ExtrasApiTransactionMinInfoDto): Promise {