Skip to content

Commit

Permalink
accounts endpoint make es heavy fiealds optional (#1426)
Browse files Browse the repository at this point in the history
* accounts endpoint make es heavy fiealds optional

* optimize provider query for simple accounts

* early return for null account

* use AccountFetchOptions

* fixes after review

* fixes after review

* Enhance AccountController tests to validate optional parameters in account retrieval. Added tests for withTxCount, withScrCount, withTimestamp, and withAssets parameters, ensuring correct behavior and response structure. Updated existing tests to reflect changes in expected account details when optional parameters are used. Improved overall test coverage for account details retrieval.

---------

Co-authored-by: bogdan-rosianu <bogdan.rosianu@yahoo.com>
Co-authored-by: cfaur09 <catalinfaurpaul@gmail.com>
  • Loading branch information
3 people authored Jan 8, 2025
1 parent 1f340f7 commit 4ac6654
Show file tree
Hide file tree
Showing 5 changed files with 279 additions and 37 deletions.
16 changes: 13 additions & 3 deletions src/endpoints/accounts/account.controller.ts
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,7 @@ import { DeepHistoryInterceptor } from 'src/interceptors/deep-history.intercepto
import { MexPairType } from '../mex/entities/mex.pair.type';
import { NftSubType } from '../nfts/entities/nft.sub.type';
import { AccountContract } from './entities/account.contract';
import { AccountFetchOptions } from './entities/account.fetch.options';

@Controller()
@ApiTags('accounts')
Expand Down Expand Up @@ -194,16 +195,25 @@ export class AccountController {
@UseInterceptors(DeepHistoryInterceptor)
@ApiOperation({ summary: 'Account details', description: 'Returns account details for a given address' })
@ApiQuery({ name: 'withGuardianInfo', description: 'Returns guardian data for a given address', required: false })
@ApiQuery({ name: 'fields', description: 'List of fields to filter by', required: false, isArray: true, style: 'form', explode: false })
@ApiQuery({ name: 'withTxCount', description: 'Returns the count of the transactions for a given address', required: false })
@ApiQuery({ name: 'withScrCount', description: 'Returns the sc results count for a given address', required: false })
@ApiQuery({ name: 'withTimestamp', description: 'Returns the timestamp of the last activity for a given address', required: false })
@ApiQuery({ name: 'withAssets', description: 'Returns the assets for a given address', required: false })
@ApiQuery({ name: 'timestamp', description: 'Retrieve entry from timestamp', required: false, type: Number })
@ApiOkResponse({ type: AccountDetailed })
async getAccountDetails(
@Param('address', ParseAddressPipe) address: string,
@Query('withGuardianInfo', ParseBoolPipe) withGuardianInfo?: boolean,
@Query('fields', ParseArrayPipe) fields?: string[],
@Query('withTxCount', ParseBoolPipe) withTxCount?: boolean,
@Query('withScrCount', ParseBoolPipe) withScrCount?: boolean,
@Query('withTimestamp', ParseBoolPipe) withTimestamp?: boolean,
@Query('withAssets', ParseBoolPipe) withAssets?: boolean,
@Query('timestamp', ParseIntPipe) _timestamp?: number,
): Promise<AccountDetailed> {
const account = await this.accountService.getAccount(address, fields, withGuardianInfo);
const account = await this.accountService.getAccount(
address,
new AccountFetchOptions({ withGuardianInfo, withTxCount, withScrCount, withTimestamp, withAssets }),
);
if (!account) {
throw new NotFoundException('Account not found');
}
Expand Down
50 changes: 27 additions & 23 deletions src/endpoints/accounts/account.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,6 @@ import { AddressUtils, BinaryUtils, OriginLogger } from '@multiversx/sdk-nestjs-
import { ApiService, ApiUtils } from "@multiversx/sdk-nestjs-http";
import { GatewayService } from 'src/common/gateway/gateway.service';
import { IndexerService } from "src/common/indexer/indexer.service";
import { AccountOptionalFieldOption } from './entities/account.optional.field.options';
import { AccountAssets } from 'src/common/assets/entities/account.assets';
import { CacheInfo } from 'src/utils/cache.info';
import { UsernameService } from '../usernames/username.service';
Expand All @@ -33,9 +32,10 @@ import { ProviderService } from '../providers/provider.service';
import { KeysService } from '../keys/keys.service';
import { NodeStatusRaw } from '../nodes/entities/node.status';
import { AccountKeyFilter } from './entities/account.key.filter';
import { Provider } from '../providers/entities/provider';
import { ApplicationMostUsed } from './entities/application.most.used';
import { AccountContract } from './entities/account.contract';
import { AccountFetchOptions } from './entities/account.fetch.options';
import { Provider } from '../providers/entities/provider';

@Injectable()
export class AccountService {
Expand Down Expand Up @@ -74,39 +74,38 @@ export class AccountService {
return await this.indexerService.getAccountsCount(filter);
}

async getAccount(address: string, fields?: string[], withGuardianInfo?: boolean): Promise<AccountDetailed | null> {
async getAccount(address: string, options?: AccountFetchOptions): Promise<AccountDetailed | null> {
if (!AddressUtils.isAddressValid(address)) {
return null;
}

const provider: Provider | undefined = await this.providerService.getProvider(address);

let txCount: number = 0;
let scrCount: number = 0;

if (!fields || fields.length === 0 || fields.includes(AccountOptionalFieldOption.txCount)) {
txCount = await this.getAccountTxCount(address);
const account = await this.getAccountRaw(address, options?.withAssets);
if (!account) {
return null;
}

if (!fields || fields.length === 0 || fields.includes(AccountOptionalFieldOption.scrCount)) {
scrCount = await this.getAccountScResults(address);
if (options?.withTxCount === true) {
account.txCount = await this.getAccountTxCount(address);
}

const [account, elasticSearchAccount] = await Promise.all([
this.getAccountRaw(address, txCount, scrCount),
this.indexerService.getAccount(address),
]);
if (options?.withScrCount === true) {
account.scrCount = await this.getAccountScResults(address);
}

if (account && withGuardianInfo === true) {
if (options?.withGuardianInfo === true) {
await this.applyGuardianInfo(account);
}

if (account && elasticSearchAccount) {
if (options?.withTimestamp) {
const elasticSearchAccount = await this.indexerService.getAccount(address);
account.timestamp = elasticSearchAccount.timestamp;
}

if (account && provider && provider.owner) {
account.ownerAddress = provider.owner;
if (AddressUtils.isSmartContractAddress(address)) {
const provider: Provider | undefined = await this.providerService.getProvider(address);
if (provider && provider.owner) {
account.ownerAddress = provider.owner;
}
}

return account;
Expand Down Expand Up @@ -161,16 +160,21 @@ export class AccountService {
return await this.getAccountRaw(address);
}

async getAccountRaw(address: string, txCount: number = 0, scrCount: number = 0): Promise<AccountDetailed | null> {
const assets = await this.assetsService.getAllAccountAssets();
async getAccountRaw(address: string, withAssets?: boolean): Promise<AccountDetailed | null> {
try {
const {
account: { nonce, balance, code, codeHash, rootHash, developerReward, ownerAddress, codeMetadata },
} = await this.gatewayService.getAddressDetails(address);

const shardCount = await this.protocolService.getShardCount();
const shard = AddressUtils.computeShard(AddressUtils.bech32Decode(address), shardCount);
let account = new AccountDetailed({ address, nonce, balance, code, codeHash, rootHash, txCount, scrCount, shard, developerReward, ownerAddress, scamInfo: undefined, assets: assets[address], ownerAssets: assets[ownerAddress], nftCollections: undefined, nfts: undefined });
let account = new AccountDetailed({ address, nonce, balance, code, codeHash, rootHash, shard, developerReward, ownerAddress, scamInfo: undefined, nftCollections: undefined, nfts: undefined });

if (withAssets === true) {
const assets = await this.assetsService.getAllAccountAssets();
account.assets = assets[address];
account.ownerAssets = assets[ownerAddress];
}

const codeAttributes = AddressUtils.decodeCodeMetadata(codeMetadata);
if (codeAttributes) {
Expand Down
11 changes: 11 additions & 0 deletions src/endpoints/accounts/entities/account.fetch.options.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
export class AccountFetchOptions {
constructor(init?: Partial<AccountFetchOptions>) {
Object.assign(this, init);
}

withGuardianInfo?: boolean;
withTxCount?: boolean;
withScrCount?: boolean;
withTimestamp?: boolean;
withAssets?: boolean;
}

This file was deleted.

Loading

0 comments on commit 4ac6654

Please sign in to comment.