Skip to content

Commit

Permalink
withTxCount, withScrCount support in account list endpoint (#1186)
Browse files Browse the repository at this point in the history
* withTxCount, withScrCount support

* update unit tests

* fixed copy-paste description

---------

Co-authored-by: cfaur <catalinfaurpaul@gmail.com>
  • Loading branch information
tanghel and cfaur09 authored Jan 19, 2024
1 parent 0b29b73 commit 93a9abd
Show file tree
Hide file tree
Showing 17 changed files with 100 additions and 71 deletions.
4 changes: 2 additions & 2 deletions src/common/indexer/elastic/elastic.indexer.helper.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ import { EsdtType } from "src/endpoints/esdt/entities/esdt.type";
import { TokenWithRolesFilter } from "src/endpoints/tokens/entities/token.with.roles.filter";
import { TransactionFilter } from "src/endpoints/transactions/entities/transaction.filter";
import { TransactionType } from "src/endpoints/transactions/entities/transaction.type";
import { AccountFilter } from "src/endpoints/accounts/entities/account.query.options";
import { AccountQueryOptions } from "src/endpoints/accounts/entities/account.query.options";
import { AccountHistoryFilter } from "src/endpoints/accounts/entities/account.history.filter";

@Injectable()
Expand Down Expand Up @@ -527,7 +527,7 @@ export class ElasticIndexerHelper {
return elasticQuery;
}

public buildAccountFilterQuery(filter: AccountFilter): ElasticQuery {
public buildAccountFilterQuery(filter: AccountQueryOptions): ElasticQuery {
let elasticQuery = ElasticQuery.create();

if (filter.ownerAddress) {
Expand Down
6 changes: 3 additions & 3 deletions src/common/indexer/elastic/elastic.indexer.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ import { Tag } from "../entities/tag";
import { ElasticIndexerHelper } from "./elastic.indexer.helper";
import { TokenType } from "../entities";
import { SortCollections } from "src/endpoints/collections/entities/sort.collections";
import { AccountFilter } from "src/endpoints/accounts/entities/account.query.options";
import { AccountQueryOptions } from "src/endpoints/accounts/entities/account.query.options";
import { AccountSort } from "src/endpoints/accounts/entities/account.sort";
import { MiniBlockFilter } from "src/endpoints/miniblocks/entities/mini.block.filter";
import { AccountHistoryFilter } from "src/endpoints/accounts/entities/account.history.filter";
Expand All @@ -36,7 +36,7 @@ export class ElasticIndexerService implements IndexerInterface {
private readonly apiService: ApiService,
) { }

async getAccountsCount(filter: AccountFilter): Promise<number> {
async getAccountsCount(filter: AccountQueryOptions): Promise<number> {
const query = this.indexerHelper.buildAccountFilterQuery(filter);

return await this.elasticService.getCount('accounts', query);
Expand Down Expand Up @@ -380,7 +380,7 @@ export class ElasticIndexerService implements IndexerInterface {
return await this.elasticService.getList('scresults', 'hash', elasticQuery);
}

async getAccounts(queryPagination: QueryPagination, filter: AccountFilter): Promise<any[]> {
async getAccounts(queryPagination: QueryPagination, filter: AccountQueryOptions): Promise<any[]> {
let elasticQuery = this.indexerHelper.buildAccountFilterQuery(filter);

const sortOrder: ElasticSortOrder = !filter.order || filter.order === SortOrder.desc ? ElasticSortOrder.descending : ElasticSortOrder.ascending;
Expand Down
6 changes: 3 additions & 3 deletions src/common/indexer/indexer.interface.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { AccountFilter } from "src/endpoints/accounts/entities/account.query.options";
import { AccountQueryOptions } from "src/endpoints/accounts/entities/account.query.options";
import { AccountHistoryFilter } from "src/endpoints/accounts/entities/account.history.filter";
import { BlockFilter } from "src/endpoints/blocks/entities/block.filter";
import { CollectionFilter } from "src/endpoints/collections/entities/collection.filter";
Expand All @@ -15,7 +15,7 @@ import { QueryPagination } from "../entities/query.pagination";
import { Account, AccountHistory, AccountTokenHistory, Block, Collection, MiniBlock, Operation, Round, ScDeploy, ScResult, Tag, Token, TokenAccount, Transaction, TransactionLog, TransactionReceipt } from "./entities";

export interface IndexerInterface {
getAccountsCount(filter: AccountFilter): Promise<number>
getAccountsCount(filter: AccountQueryOptions): Promise<number>

getScResultsCount(): Promise<number>

Expand Down Expand Up @@ -99,7 +99,7 @@ export interface IndexerInterface {

getAccount(address: string): Promise<Account>

getAccounts(queryPagination: QueryPagination, filter: AccountFilter): Promise<Account[]>
getAccounts(queryPagination: QueryPagination, filter: AccountQueryOptions): Promise<Account[]>

getAccountContracts(pagination: QueryPagination, address: string): Promise<ScDeploy[]>

Expand Down
6 changes: 3 additions & 3 deletions src/common/indexer/indexer.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ import { QueryPagination } from "../entities/query.pagination";
import { Account, AccountHistory, AccountTokenHistory, Block, Collection, MiniBlock, Operation, Round, ScDeploy, ScResult, Tag, Token, TokenAccount, Transaction, TransactionLog, TransactionReceipt } from "./entities";
import { IndexerInterface } from "./indexer.interface";
import { LogPerformanceAsync } from "src/utils/log.performance.decorator";
import { AccountFilter } from "src/endpoints/accounts/entities/account.query.options";
import { AccountQueryOptions } from "src/endpoints/accounts/entities/account.query.options";
import { MiniBlockFilter } from "src/endpoints/miniblocks/entities/mini.block.filter";
import { AccountHistoryFilter } from "src/endpoints/accounts/entities/account.history.filter";

Expand All @@ -26,7 +26,7 @@ export class IndexerService implements IndexerInterface {
) { }

@LogPerformanceAsync(MetricsEvents.SetIndexerDuration)
async getAccountsCount(filter: AccountFilter): Promise<number> {
async getAccountsCount(filter: AccountQueryOptions): Promise<number> {
return await this.indexerInterface.getAccountsCount(filter);
}

Expand Down Expand Up @@ -222,7 +222,7 @@ export class IndexerService implements IndexerInterface {
}

@LogPerformanceAsync(MetricsEvents.SetIndexerDuration)
async getAccounts(queryPagination: QueryPagination, filter: AccountFilter): Promise<Account[]> {
async getAccounts(queryPagination: QueryPagination, filter: AccountQueryOptions): Promise<Account[]> {
return await this.indexerInterface.getAccounts(queryPagination, filter);
}

Expand Down
4 changes: 2 additions & 2 deletions src/crons/cache.warmer/cache.warmer.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ import { SettingsService } from "src/common/settings/settings.service";
import { TokenService } from "src/endpoints/tokens/token.service";
import { IndexerService } from "src/common/indexer/indexer.service";
import { NftService } from "src/endpoints/nfts/nft.service";
import { AccountFilter } from "src/endpoints/accounts/entities/account.query.options";
import { AccountQueryOptions } from "src/endpoints/accounts/entities/account.query.options";
import { TokenType } from "src/common/indexer/entities";
import { TokenDetailed } from "src/endpoints/tokens/entities/token.detailed";
import { DataApiService } from "src/common/data-api/data-api.service";
Expand Down Expand Up @@ -195,7 +195,7 @@ export class CacheWarmerService {
@Cron(CronExpression.EVERY_MINUTE)
@Lock({ name: 'Accounts invalidations', verbose: true })
async handleAccountInvalidations() {
const accounts = await this.accountService.getAccountsRaw({ from: 0, size: 25 }, new AccountFilter());
const accounts = await this.accountService.getAccountsRaw({ from: 0, size: 25 }, new AccountQueryOptions());

const accountsCacheInfo = CacheInfo.Accounts({ from: 0, size: 25 });
await this.invalidateKey(accountsCacheInfo.key, accounts, accountsCacheInfo.ttl);
Expand Down
4 changes: 2 additions & 2 deletions src/crons/status.checker/status.checker.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ import { NetworkService } from "src/endpoints/network/network.service";
import { QueryPagination } from "src/common/entities/query.pagination";
import { TokenFilter } from "src/endpoints/tokens/entities/token.filter";
import { NodeType } from "src/endpoints/nodes/entities/node.type";
import { AccountFilter } from "src/endpoints/accounts/entities/account.query.options";
import { AccountQueryOptions } from "src/endpoints/accounts/entities/account.query.options";

@Injectable()
export class StatusCheckerService {
Expand All @@ -45,7 +45,7 @@ export class StatusCheckerService {
@Cron(CronExpression.EVERY_MINUTE)
async handleAccountsCount() {
await Locker.lock('Status Checker Accounts Count', async () => {
const count = await this.elasticIndexerService.getAccountsCount(new AccountFilter());
const count = await this.elasticIndexerService.getAccountsCount(new AccountQueryOptions());
MetricsService.setClusterComparisonValue('total_accounts', count);
}, true);
}
Expand Down
12 changes: 8 additions & 4 deletions src/endpoints/accounts/account.controller.ts
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,7 @@ import { DelegationService } from '../delegation/delegation.service';
import { TokenType } from '../tokens/entities/token.type';
import { ContractUpgrades } from './entities/contract.upgrades';
import { AccountVerification } from './entities/account.verification';
import { AccountFilter as AccountQueryOptions } from './entities/account.query.options';
import { AccountQueryOptions } from './entities/account.query.options';
import { AccountSort } from './entities/account.sort';
import { AccountHistoryFilter } from './entities/account.history.filter';
import { ParseArrayPipeOptions } from '@multiversx/sdk-nestjs-common/lib/pipes/entities/parse.array.options';
Expand Down Expand Up @@ -84,19 +84,23 @@ export class AccountController {
@ApiQuery({ name: 'sort', description: 'Sort criteria (balance / timestamp)', required: false, enum: AccountSort })
@ApiQuery({ name: 'order', description: 'Sort order (asc/desc)', required: false, enum: SortOrder })
@ApiQuery({ name: 'isSmartContract', description: 'Filter accounts by whether they are smart contract or not', required: false })
@ApiQuery({ name: 'withDeployInfo', description: 'Include deployedAt and deployTxHash fields in the result', required: false })
@ApiQuery({ name: 'withOwnerAssets', description: 'Return a list accounts with owner assets', required: false })
@ApiQuery({ name: 'withDeployInfo', description: 'Include deployedAt and deployTxHash fields in the result', required: false })
@ApiQuery({ name: 'withTxCount', description: 'Include txCount field in the result', required: false })
@ApiQuery({ name: 'withScrCount', description: 'Include scrCount field in the result', required: false })
getAccounts(
@Query('from', new DefaultValuePipe(0), ParseIntPipe) from: number,
@Query("size", new DefaultValuePipe(25), ParseIntPipe) size: number,
@Query("ownerAddress", ParseAddressPipe) ownerAddress?: string,
@Query('sort', new ParseEnumPipe(AccountSort)) sort?: AccountSort,
@Query('order', new ParseEnumPipe(SortOrder)) order?: SortOrder,
@Query("isSmartContract", new ParseBoolPipe) isSmartContract?: boolean,
@Query("withDeployInfo", new ParseBoolPipe) withDeployInfo?: boolean,
@Query("withOwnerAssets", new ParseBoolPipe) withOwnerAssets?: boolean,
@Query("withDeployInfo", new ParseBoolPipe) withDeployInfo?: boolean,
@Query("withTxCount", new ParseBoolPipe) withTxCount?: boolean,
@Query("withScrCount", new ParseBoolPipe) withScrCount?: boolean,
): Promise<Account[]> {
const queryOptions = new AccountQueryOptions({ ownerAddress, sort, order, isSmartContract, withOwnerAssets, withDeployInfo });
const queryOptions = new AccountQueryOptions({ ownerAddress, sort, order, isSmartContract, withOwnerAssets, withDeployInfo, withTxCount, withScrCount });
queryOptions.validate(size);

return this.accountService.getAccounts(
Expand Down
25 changes: 17 additions & 8 deletions src/endpoints/accounts/account.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -28,15 +28,14 @@ import { CacheInfo } from 'src/utils/cache.info';
import { UsernameService } from '../usernames/username.service';
import { ContractUpgrades } from './entities/contract.upgrades';
import { AccountVerification } from './entities/account.verification';
import { AccountFilter } from './entities/account.query.options';
import { AccountQueryOptions } from './entities/account.query.options';
import { AccountHistoryFilter } from './entities/account.history.filter';
import { ProtocolService } from 'src/common/protocol/protocol.service';
import { ProviderService } from '../providers/provider.service';
import { Provider } from '../providers/entities/provider';
import { KeysService } from '../keys/keys.service';
import { NodeStatusRaw } from '../nodes/entities/node.status';
import { AccountKeyFilter } from './entities/account.key.filter';
import { Address } from '@multiversx/sdk-core/out';

@Injectable()
export class AccountService {
Expand Down Expand Up @@ -67,7 +66,7 @@ export class AccountService {
private readonly keysService: KeysService
) { }

async getAccountsCount(filter: AccountFilter): Promise<number> {
async getAccountsCount(filter: AccountQueryOptions): Promise<number> {
if (!filter.ownerAddress && filter.isSmartContract === undefined) {
return await this.cachingService.getOrSet(
CacheInfo.AccountsCount.key,
Expand Down Expand Up @@ -299,7 +298,7 @@ export class AccountService {
return null;
}

async getAccounts(queryPagination: QueryPagination, filter: AccountFilter): Promise<Account[]> {
async getAccounts(queryPagination: QueryPagination, filter: AccountQueryOptions): Promise<Account[]> {
if (!filter.isSet()) {
return await this.cachingService.getOrSet(
CacheInfo.Accounts(queryPagination).key,
Expand All @@ -326,8 +325,8 @@ export class AccountService {
return accounts;
}

async getAccountsRaw(queryPagination: QueryPagination, filter: AccountFilter): Promise<Account[]> {
const result = await this.indexerService.getAccounts(queryPagination, filter);
async getAccountsRaw(queryPagination: QueryPagination, options: AccountQueryOptions): Promise<Account[]> {
const result = await this.indexerService.getAccounts(queryPagination, options);
const assets = await this.assetsService.getAllAccountAssets();
const accounts: Account[] = result.map(item => {
const account = ApiUtils.mergeObjects(new Account(), item);
Expand All @@ -344,7 +343,7 @@ export class AccountService {
account.shard = AddressUtils.computeShard(AddressUtils.bech32Decode(account.address), shardCount);
account.assets = assets[account.address];

if (filter.withDeployInfo && new Address(account.address).isContractAddress()) {
if (options.withDeployInfo && AddressUtils.isSmartContractAddress(account.address)) {
const [deployedAt, deployTxHash] = await Promise.all([
this.getAccountDeployedAt(account.address),
this.getAccountDeployedTxHash(account.address),
Expand All @@ -354,13 +353,23 @@ export class AccountService {
account.deployTxHash = deployTxHash;
}

if (filter.withOwnerAssets && account.ownerAddress) {
if (options.withTxCount) {
account.txCount = await this.getAccountTxCount(account.address);
}

if (options.withScrCount) {
account.scrCount = await this.getAccountScResults(account.address);
}

if (options.withOwnerAssets && account.ownerAddress) {
account.ownerAssets = assets[account.ownerAddress];
}

if (verifiedAccounts && verifiedAccounts.includes(account.address)) {
account.isVerified = true;
}


}

return accounts;
Expand Down
10 changes: 1 addition & 9 deletions src/endpoints/accounts/entities/account.detailed.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { ComplexityEstimation } from "@multiversx/sdk-nestjs-common";
import { Field, Float, ObjectType } from "@nestjs/graphql";
import { Field, ObjectType } from "@nestjs/graphql";
import { ApiProperty } from "@nestjs/swagger";
import { ScamInfo } from "src/common/entities/scam-info.dto";
import { NftCollectionAccount } from "src/endpoints/collections/entities/nft.collection.account";
Expand Down Expand Up @@ -37,14 +37,6 @@ export class AccountDetailed extends Account {
@ApiProperty({ description: 'The address in bech 32 format of owner account' })
ownerAddress: string = '';

@Field(() => Float, { description: 'Transactions count for the given detailed account.' })
@ApiProperty({ description: 'The number of transactions performed on this account' })
txCount?: number;

@Field(() => Float, { description: 'Smart contract results count for the given detailed account.' })
@ApiProperty({ description: 'The number of smart contract results of this account' })
scrCount: number = 0;

@Field(() => Boolean, { description: 'If the given detailed account is upgradeable.', nullable: true })
@ApiProperty({ description: 'Specific property flag for smart contract', type: Boolean })
isUpgradeable?: boolean;
Expand Down
14 changes: 12 additions & 2 deletions src/endpoints/accounts/entities/account.query.options.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,8 @@ import { SortOrder } from "src/common/entities/sort.order";
import { AccountSort } from "./account.sort";
import { BadRequestException } from "@nestjs/common";

export class AccountFilter {
constructor(init?: Partial<AccountFilter>) {
export class AccountQueryOptions {
constructor(init?: Partial<AccountQueryOptions>) {
Object.assign(this, init);
}

Expand All @@ -14,11 +14,21 @@ export class AccountFilter {
isSmartContract?: boolean;
withOwnerAssets?: boolean;
withDeployInfo?: boolean;
withTxCount?: boolean;
withScrCount?: boolean;

validate(size: number) {
if (this.withDeployInfo && size > 25) {
throw new BadRequestException('Size must be less than or equal to 25 when withDeployInfo is set');
}

if (this.withTxCount && size > 25) {
throw new BadRequestException('Size must be less than or equal to 25 when withTxCount is set');
}

if (this.withScrCount && size > 25) {
throw new BadRequestException('Size must be less than or equal to 25 when withScrCount is set');
}
}

isSet(): boolean {
Expand Down
8 changes: 8 additions & 0 deletions src/endpoints/accounts/entities/account.ts
Original file line number Diff line number Diff line change
Expand Up @@ -52,4 +52,12 @@ export class Account {
@Field(() => Boolean, { description: 'If the given detailed account is verified.', nullable: true })
@ApiProperty({ description: 'Specific property flag for smart contract', type: Boolean })
isVerified?: boolean;

@Field(() => Float, { description: 'Transactions count for the given detailed account.' })
@ApiProperty({ description: 'The number of transactions performed on this account' })
txCount?: number;

@Field(() => Float, { description: 'Smart contract results count for the given detailed account.' })
@ApiProperty({ description: 'The number of smart contract results of this account' })
scrCount?: number;
}
4 changes: 2 additions & 2 deletions src/endpoints/network/network.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ import { About } from './entities/about';
import { PluginService } from 'src/common/plugins/plugin.service';
import { SmartContractResultService } from '../sc-results/scresult.service';
import { TokenService } from '../tokens/token.service';
import { AccountFilter } from '../accounts/entities/account.query.options';
import { AccountQueryOptions } from '../accounts/entities/account.query.options';
import { DataApiService } from 'src/common/data-api/data-api.service';
import { FeatureConfigs } from './entities/feature.configs';

Expand Down Expand Up @@ -232,7 +232,7 @@ export class NetworkService {
this.gatewayService.getNetworkConfig(),
this.gatewayService.getNetworkStatus(metaChainShard),
this.blockService.getBlocksCount(new BlockFilter()),
this.accountService.getAccountsCount(new AccountFilter()),
this.accountService.getAccountsCount(new AccountQueryOptions()),
this.transactionService.getTransactionCount(new TransactionFilter()),
this.smartContractResultService.getScResultsCount(),
]);
Expand Down
6 changes: 3 additions & 3 deletions src/graphql/entities/account/account.input.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { Field, InputType, Float } from "@nestjs/graphql";
import { AccountFilter } from "src/endpoints/accounts/entities/account.query.options";
import { AccountQueryOptions } from "src/endpoints/accounts/entities/account.query.options";

@InputType({ description: "Input to retrieve the given accounts for." })
export class GetAccountFilteredInput {
Expand All @@ -9,8 +9,8 @@ export class GetAccountFilteredInput {

@Field(() => String, { name: "ownerAddress", description: "Owner address to retrieve for the given result set.", nullable: true })
ownerAddress: string | undefined = undefined;
public static resolve(input: GetAccountFilteredInput): AccountFilter {
return new AccountFilter({
public static resolve(input: GetAccountFilteredInput): AccountQueryOptions {
return new AccountQueryOptions({
ownerAddress: input.ownerAddress,
});
}
Expand Down
Loading

0 comments on commit 93a9abd

Please sign in to comment.