Skip to content

Commit 713c4a8

Browse files
Merge pull request #1539 from multiversx/development
Development to main
2 parents 6bf7dc5 + 91f20cc commit 713c4a8

File tree

11 files changed

+187
-75
lines changed

11 files changed

+187
-75
lines changed

src/endpoints/accounts/account.service.ts

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -182,17 +182,20 @@ export class AccountService {
182182
}
183183

184184
if (AddressUtils.isSmartContractAddress(address) && account.code) {
185-
const deployTxHash = await this.getAccountDeployedTxHash(address);
185+
const [deployTxHash, deployedAt, isVerified] = await Promise.all([
186+
this.getAccountDeployedTxHash(address),
187+
this.getAccountDeployedAt(address),
188+
this.getAccountIsVerified(address, account.codeHash),
189+
]);
190+
186191
if (deployTxHash) {
187192
account.deployTxHash = deployTxHash;
188193
}
189194

190-
const deployedAt = await this.getAccountDeployedAt(address);
191195
if (deployedAt) {
192196
account.deployedAt = deployedAt;
193197
}
194198

195-
const isVerified = await this.getAccountIsVerified(address, account.codeHash);
196199
if (isVerified) {
197200
account.isVerified = isVerified;
198201
}

src/endpoints/blocks/block.service.ts

Lines changed: 18 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ import { IndexerService } from "src/common/indexer/indexer.service";
1010
import { NodeService } from "../nodes/node.service";
1111
import { IdentitiesService } from "../identities/identities.service";
1212
import { ApiConfigService } from "../../common/api-config/api.config.service";
13+
import { ConcurrencyUtils } from "src/utils/concurrency.utils";
1314

1415
@Injectable()
1516
export class BlockService {
@@ -34,8 +35,8 @@ export class BlockService {
3435

3536
async getBlocks(filter: BlockFilter, queryPagination: QueryPagination, withProposerIdentity?: boolean): Promise<Block[]> {
3637
const result = await this.indexerService.getBlocks(filter, queryPagination);
37-
const blocks = [];
38-
for (const item of result) {
38+
39+
const blocks = await Promise.all(result.map(async (item) => {
3940
const blockRaw = await this.computeProposerAndValidators(item);
4041

4142
const block = Block.mergeWithElasticResponse(new Block(), blockRaw);
@@ -44,8 +45,8 @@ export class BlockService {
4445
block.scheduledRootHash = blockRaw.scheduledData.rootHash;
4546
}
4647

47-
blocks.push(block);
48-
}
48+
return block;
49+
}));
4950

5051
if (withProposerIdentity === true) {
5152
await this.applyProposerIdentity(blocks);
@@ -58,17 +59,19 @@ export class BlockService {
5859
const proposerBlses = blocks.map(x => x.proposer);
5960

6061
const nodes = await this.nodeService.getAllNodes();
61-
for (const node of nodes) {
62-
if (!proposerBlses.includes(node.bls)) {
63-
continue;
64-
}
65-
66-
const nodeIdentity = node.identity;
67-
if (!nodeIdentity) {
68-
continue;
69-
}
70-
71-
const identity = await this.identitiesService.getIdentity(nodeIdentity);
62+
const relevantNodes = nodes.filter(node => proposerBlses.includes(node.bls) && node.identity);
63+
64+
const nodeIdentities = await ConcurrencyUtils.executeWithConcurrencyLimit(
65+
relevantNodes,
66+
async (node) => {
67+
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
68+
const identity = await this.identitiesService.getIdentity(node.identity!);
69+
return { node, identity };
70+
},
71+
25,
72+
'Block proposer identities'
73+
);
74+
for (const { node, identity } of nodeIdentities) {
7275
if (!identity) {
7376
continue;
7477
}

src/endpoints/collections/collection.controller.ts

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -225,6 +225,7 @@ export class CollectionController {
225225
@ApiQuery({ name: 'nonceAfter', description: 'Return all NFTs with given nonce after the given number', required: false, type: Number })
226226
@ApiQuery({ name: 'withOwner', description: 'Return owner where type = NonFungibleESDT', required: false, type: Boolean })
227227
@ApiQuery({ name: 'withSupply', description: 'Return supply where type = SemiFungibleESDT', required: false, type: Boolean })
228+
@ApiQuery({ name: 'withAssets', description: 'Return assets information (defaults to true)', required: false, type: Boolean })
228229
@ApiQuery({ name: 'sort', description: 'Sorting criteria', required: false, enum: SortCollectionNfts })
229230
@ApiQuery({ name: 'order', description: 'Sorting order (asc / desc)', required: false, enum: SortOrder })
230231
async getNfts(
@@ -244,6 +245,7 @@ export class CollectionController {
244245
@Query('nonceAfter', ParseIntPipe) nonceAfter?: number,
245246
@Query('withOwner', ParseBoolPipe) withOwner?: boolean,
246247
@Query('withSupply', ParseBoolPipe) withSupply?: boolean,
248+
@Query('withAssets', ParseBoolPipe) withAssets?: boolean,
247249
@Query('sort', new ParseEnumPipe(SortCollectionNfts)) sort?: SortCollectionNfts,
248250
@Query('order', new ParseEnumPipe(SortOrder)) order?: SortOrder,
249251
): Promise<Nft[]> {
@@ -255,7 +257,7 @@ export class CollectionController {
255257
return await this.nftService.getNfts(
256258
new QueryPagination({ from, size }),
257259
new NftFilter({ search, identifiers, collection, name, tags, creator, hasUris, isWhitelistedStorage, isNsfw, traits, nonceBefore, nonceAfter, sort, order }),
258-
new NftQueryOptions({ withOwner, withSupply }),
260+
new NftQueryOptions({ withOwner, withSupply, withAssets }),
259261
);
260262
}
261263

src/endpoints/collections/collection.service.ts

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -169,10 +169,12 @@ export class CollectionService {
169169
async batchGetCollectionsAssets(identifiers: string[]): Promise<{ [key: string]: TokenAssets | undefined }> {
170170
const collectionsAssets: { [key: string]: TokenAssets | undefined } = {};
171171

172+
const allAssets = await this.assetsService.getAllTokenAssets();
173+
172174
await this.cachingService.batchApplyAll(
173175
identifiers,
174176
identifier => CacheInfo.EsdtAssets(identifier).key,
175-
identifier => this.assetsService.getTokenAssets(identifier),
177+
identifier => Promise.resolve(allAssets[identifier]),
176178
(identifier, properties) => collectionsAssets[identifier] = properties,
177179
CacheInfo.EsdtAssets('').ttl
178180
);

src/endpoints/nfts/entities/nft.query.options.ts

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,11 +3,16 @@ import { BadRequestException } from "@nestjs/common";
33
export class NftQueryOptions {
44
constructor(init?: Partial<NftQueryOptions>) {
55
Object.assign(this, init);
6+
7+
if (this.withAssets === undefined) {
8+
this.withAssets = true;
9+
}
610
}
711

812
withOwner?: boolean;
913
withSupply?: boolean;
1014
withReceivedAt?: boolean;
15+
withAssets?: boolean;
1116

1217
validate(size: number): void {
1318
if (this.withReceivedAt && size > 25) {

src/endpoints/nfts/nft.service.ts

Lines changed: 16 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -70,7 +70,7 @@ export class NftService {
7070
const nfts = await this.getNftsInternal({ from, size }, filter);
7171

7272
await Promise.all([
73-
this.batchApplyAssetsAndTicker(nfts),
73+
this.conditionallyApplyAssetsAndTicker(nfts, undefined, queryOptions),
7474
this.conditionallyApplyOwners(nfts, queryOptions),
7575
this.conditionallyApplySupply(nfts, queryOptions),
7676
this.batchProcessNfts(nfts),
@@ -88,23 +88,25 @@ export class NftService {
8888
]);
8989
}
9090

91-
private async batchApplyAssetsAndTicker(nfts: Nft[], fields?: string[]): Promise<void> {
91+
private async conditionallyApplyAssetsAndTicker(nfts: Nft[], fields?: string[], queryOptions?: { withAssets?: boolean }): Promise<void> {
9292
if (fields && fields.includesNone(['ticker', 'assets'])) {
9393
return;
9494
}
9595

96-
await Promise.all(
97-
nfts.map(async (nft) => {
98-
nft.assets = await this.assetsService.getTokenAssets(nft.identifier) ??
99-
await this.assetsService.getTokenAssets(nft.collection);
100-
101-
if (nft.assets) {
102-
nft.ticker = nft.collection.split('-')[0];
103-
} else {
104-
nft.ticker = nft.collection;
105-
}
106-
})
107-
);
96+
const allAssets = await this.assetsService.getAllTokenAssets();
97+
if (queryOptions?.withAssets === false) {
98+
return;
99+
}
100+
101+
for (const nft of nfts) {
102+
nft.assets = allAssets[nft.identifier] ?? allAssets[nft.collection];
103+
104+
if (nft.assets) {
105+
nft.ticker = nft.collection.split('-')[0];
106+
} else {
107+
nft.ticker = nft.collection;
108+
}
109+
}
108110
}
109111

110112
private async conditionallyApplyOwners(nfts: Nft[], queryOptions?: NftQueryOptions): Promise<void> {

src/endpoints/tokens/token.service.ts

Lines changed: 23 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -93,16 +93,19 @@ export class TokenService {
9393

9494
this.applyTickerFromAssets(token);
9595

96-
await this.applySupply(token, supplyOptions);
97-
98-
if (token.type === TokenType.FungibleESDT) {
99-
token.roles = await this.getTokenRoles(identifier);
100-
} else if (token.type === TokenType.MetaESDT) {
101-
const elasticCollection = await this.indexerService.getCollection(identifier);
102-
if (elasticCollection) {
103-
await this.collectionService.applyCollectionRoles(token, elasticCollection);
104-
}
105-
}
96+
await Promise.all([
97+
this.applySupply(token, supplyOptions),
98+
(async () => {
99+
if (token.type === TokenType.FungibleESDT) {
100+
token.roles = await this.getTokenRoles(identifier);
101+
} else if (token.type === TokenType.MetaESDT) {
102+
const elasticCollection = await this.indexerService.getCollection(identifier);
103+
if (elasticCollection) {
104+
await this.collectionService.applyCollectionRoles(token, elasticCollection);
105+
}
106+
}
107+
})(),
108+
]);
106109

107110
return token;
108111
}
@@ -760,8 +763,10 @@ export class TokenService {
760763

761764
this.logger.log(`Fetched ${tokens.length} fungible tokens`);
762765

766+
const allAssets = await this.assetsService.getAllTokenAssets();
767+
763768
for (const token of tokens) {
764-
const assets = await this.assetsService.getTokenAssets(token.identifier);
769+
const assets = allAssets[token.identifier];
765770

766771
if (assets && assets.name) {
767772
token.name = assets.name;
@@ -797,10 +802,13 @@ export class TokenService {
797802

798803
await this.batchProcessTokens(tokens);
799804

800-
await this.applyMexLiquidity(tokens.filter(x => x.type !== TokenType.MetaESDT));
801-
await this.applyMexPrices(tokens.filter(x => x.type !== TokenType.MetaESDT));
802-
await this.applyMexPairType(tokens.filter(x => x.type !== TokenType.MetaESDT));
803-
await this.applyMexPairTradesCount(tokens.filter(x => x.type !== TokenType.MetaESDT));
805+
const nonMetaEsdtTokens = tokens.filter(x => x.type !== TokenType.MetaESDT);
806+
await Promise.all([
807+
this.applyMexLiquidity(nonMetaEsdtTokens),
808+
this.applyMexPrices(nonMetaEsdtTokens),
809+
this.applyMexPairType(nonMetaEsdtTokens),
810+
this.applyMexPairTradesCount(nonMetaEsdtTokens),
811+
]);
804812

805813
await this.cachingService.batchApplyAll(
806814
tokens,

src/endpoints/transactions/transaction.service.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -477,7 +477,7 @@ export class TransactionService {
477477
if (!resultsByHash.has(txHash)) {
478478
resultsByHash.set(txHash, []);
479479
}
480-
resultsByHash.get(txHash)!.push(result);
480+
resultsByHash.get(txHash)?.push(result);
481481
}
482482

483483
const results: Array<SmartContractResult[] | undefined> = [];

src/endpoints/transfers/transfer.service.ts

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,9 +21,16 @@ export class TransferService {
2121
) { }
2222

2323
private sortElasticTransfers(elasticTransfers: any[]): any[] {
24+
const transactionMap = new Map<string, any>();
25+
for (const transfer of elasticTransfers) {
26+
if (transfer.txHash) {
27+
transactionMap.set(transfer.txHash, transfer);
28+
}
29+
}
30+
2431
for (const elasticTransfer of elasticTransfers) {
2532
if (elasticTransfer.originalTxHash) {
26-
const transaction = elasticTransfers.find(x => x.txHash === elasticTransfer.originalTxHash);
33+
const transaction = transactionMap.get(elasticTransfer.originalTxHash);
2734
if (transaction) {
2835
elasticTransfer.order = (transaction.nonce * 10) + 1;
2936
} else {

src/test/unit/services/tokens.spec.ts

Lines changed: 26 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -110,6 +110,7 @@ describe('Token Service', () => {
110110
useValue: {
111111
getTokenAssets: jest.fn(),
112112
getAllAccountAssets: jest.fn(),
113+
getAllTokenAssets: jest.fn(),
113114
},
114115
},
115116
{
@@ -603,7 +604,7 @@ describe('Token Service', () => {
603604
expect(getAllTokensMock).toHaveBeenCalledTimes(1);
604605

605606
const secondToken = mockTokens[1];
606-
secondToken.assets.priceSource = {type: 'customUrl'};
607+
secondToken.assets.priceSource = { type: 'customUrl' };
607608
const newExpectedMarketCap = result - secondToken.marketCap;
608609
mockTokens[1] = secondToken;
609610

@@ -692,7 +693,6 @@ describe('Token Service', () => {
692693
});
693694

694695
it('should return tokens from other sources when isTokensFetchFeatureEnabled is false', async () => {
695-
696696
const mockTokenProperties: Partial<TokenProperties>[] = [{ identifier: 'mockIdentifier' }];
697697
let mockTokens: Partial<TokenDetailed>[] = mockTokenProperties.map(properties => ApiUtils.mergeObjects(new TokenDetailed(), properties));
698698
const mockTokenAssets: Partial<TokenAssets> = { name: 'mockName' };
@@ -702,6 +702,7 @@ describe('Token Service', () => {
702702
jest.spyOn(apiConfigService, 'isTokensFetchFeatureEnabled').mockReturnValue(false);
703703
jest.spyOn(esdtService, 'getAllFungibleTokenProperties').mockResolvedValue(mockTokenProperties as TokenProperties[]);
704704
jest.spyOn(assetsService, 'getTokenAssets').mockResolvedValue(mockTokenAssets as TokenAssets);
705+
jest.spyOn(assetsService, 'getAllTokenAssets').mockResolvedValue({ mockIdentifier: mockTokenAssets, 'EGLD-000000': mockTokenAssets } as any);
705706
jest.spyOn(collectionService, 'getNftCollections').mockResolvedValue(mockNftCollections as NftCollection[]);
706707

707708
jest.spyOn(tokenService as any, 'batchProcessTokens').mockImplementation(() => Promise.resolve());
@@ -723,16 +724,14 @@ describe('Token Service', () => {
723724
expect(apiConfigService.isTokensFetchFeatureEnabled).toHaveBeenCalled();
724725
expect(esdtService.getAllFungibleTokenProperties).toHaveBeenCalled();
725726

726-
mockTokens.forEach((mockToken) => {
727-
expect(assetsService.getTokenAssets).toHaveBeenCalledWith(mockToken.identifier);
728-
});
727+
expect(assetsService.getAllTokenAssets).toHaveBeenCalledTimes(1);
729728

730-
expect(esdtService.getAllFungibleTokenProperties).toHaveBeenCalled();
731729
mockTokens.forEach(mockToken => {
732-
expect(assetsService.getTokenAssets).toHaveBeenCalledWith(mockToken.identifier);
733730
mockToken.name = mockTokenAssets.name;
734731
});
735-
expect(assetsService.getTokenAssets).toHaveBeenCalledTimes(mockTokens.length + 1); // add 1 for EGLD-000000
732+
733+
expect(esdtService.getAllFungibleTokenProperties).toHaveBeenCalled();
734+
expect(assetsService.getTokenAssets).toHaveBeenCalledWith('EGLD-000000');
736735

737736

738737
expect((collectionService as any).getNftCollections).toHaveBeenCalledWith(expect.anything(), { type: [TokenType.MetaESDT] });
@@ -817,23 +816,26 @@ describe('Token Service', () => {
817816
new TokenProperties({ identifier: 'token5' }),
818817
]);
819818

820-
// Only token2 has a custom price source
819+
const mockAllAssets: { [key: string]: TokenAssets } = {
820+
token1: new TokenAssets({ name: 'Token token1' }),
821+
token2: new TokenAssets({
822+
name: 'Token token2',
823+
priceSource: {
824+
type: TokenAssetsPriceSourceType.customUrl,
825+
path: '0.usdPrice',
826+
url: 'url',
827+
},
828+
}),
829+
token3: new TokenAssets({ name: 'Token token3' }),
830+
token4: new TokenAssets({ name: 'Token token4' }),
831+
token5: new TokenAssets({ name: 'Token token5' }),
832+
'EGLD-000000': new TokenAssets({ name: 'EGLD' }),
833+
};
834+
jest.spyOn(tokenService['assetsService'], 'getAllTokenAssets').mockResolvedValue(mockAllAssets);
835+
821836
// eslint-disable-next-line require-await
822837
jest.spyOn(tokenService['assetsService'], 'getTokenAssets').mockImplementation(async (identifier: string) => {
823-
if (identifier === 'token2') {
824-
return new TokenAssets({
825-
name: `Token ${identifier}`,
826-
priceSource: {
827-
type: TokenAssetsPriceSourceType.customUrl,
828-
path: '0.usdPrice',
829-
url: 'url',
830-
},
831-
});
832-
}
833-
return new TokenAssets({
834-
name: `Token ${identifier}`,
835-
// No priceSource
836-
});
838+
return mockAllAssets[identifier];
837839
});
838840

839841
jest.spyOn(tokenService['collectionService'], 'getNftCollections').mockResolvedValue([]);
@@ -854,7 +856,7 @@ describe('Token Service', () => {
854856
jest.spyOn(tokenService as any, 'applyMexPrices').mockResolvedValue(undefined);
855857
jest.spyOn(tokenService as any, 'applyMexPairType').mockResolvedValue(undefined);
856858
jest.spyOn(tokenService as any, 'applyMexPairTradesCount').mockResolvedValue(undefined);
857-
jest.spyOn(tokenService['apiService'] as any, 'get').mockResolvedValue({data: [{usdPrice: 1.0}]});
859+
jest.spyOn(tokenService['apiService'] as any, 'get').mockResolvedValue({ data: [{ usdPrice: 1.0 }] });
858860
jest.spyOn(tokenService['cachingService'], 'batchApplyAll').mockImplementation(
859861
// eslint-disable-next-line require-await
860862
async (...args: unknown[]) => {

0 commit comments

Comments
 (0)