Skip to content
Merged
Show file tree
Hide file tree
Changes from 4 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions src/common/rabbitmq/entities/notifier.event.identifier.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,9 @@
export enum NotifierEventIdentifier {
ESDTNFTCreate = 'ESDTNFTCreate',
ESDTNFTUpdateAttributes = 'ESDTNFTUpdateAttributes',
ESDTNFTBurn = 'ESDTNFTBurn',
ESDTMetaDataUpdate = 'ESDTMetaDataUpdate',
ESDTMetaDataRecreate = 'ESDTMetaDataRecreate',
ESDTModifyCreator = 'ESDTModifyCreator',
transferOwnership = 'transferOwnership',
}
10 changes: 10 additions & 0 deletions src/common/rabbitmq/rabbitmq.consumer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,16 @@ export class RabbitMqConsumer {
case NotifierEventIdentifier.ESDTNFTUpdateAttributes:
await this.nftHandlerService.handleNftUpdateAttributesEvent(event);
break;
case NotifierEventIdentifier.ESDTNFTBurn:
await this.nftHandlerService.handleNftBurnEvent(event);
break;
case NotifierEventIdentifier.ESDTMetaDataUpdate:
case NotifierEventIdentifier.ESDTMetaDataRecreate:
await this.nftHandlerService.handleNftMetadataEvent(event);
break;
case NotifierEventIdentifier.ESDTModifyCreator:
await this.nftHandlerService.handleNftModifyCreatorEvent(event);
break;
case NotifierEventIdentifier.transferOwnership:
await this.tokenHandlerService.handleTransferOwnershipEvent(event);
break;
Expand Down
98 changes: 97 additions & 1 deletion src/common/rabbitmq/rabbitmq.nft.handler.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import { NotifierEvent } from './entities/notifier.event';
import { CacheService } from "@multiversx/sdk-nestjs-cache";
import { BinaryUtils, OriginLogger } from '@multiversx/sdk-nestjs-common';
import { IndexerService } from '../indexer/indexer.service';
import { NftSubType } from 'src/endpoints/nfts/entities/nft.sub.type';

@Injectable()
export class RabbitMqNftHandlerService {
Expand Down Expand Up @@ -62,7 +63,19 @@ export class RabbitMqNftHandlerService {
nft.attributes = attributes;

try {
await this.nftWorkerService.addProcessNftQueueJob(nft, new ProcessNftSettings({ forceRefreshMetadata: true }));
const isDynamicNft = this.isDynamicNftType(nft.subType);

if (isDynamicNft) {
this.logger.log(`Processing dynamic NFT with identifier '${identifier}', forcing refresh of metadata and media`);

await this.nftWorkerService.addProcessNftQueueJob(nft, new ProcessNftSettings({
forceRefreshMetadata: true,
forceRefreshMedia: true,
forceRefreshThumbnail: true,
}));
} else {
await this.nftWorkerService.addProcessNftQueueJob(nft, new ProcessNftSettings({ forceRefreshMetadata: true }));
}
} catch (error) {
this.logger.error(`An unhandled error occurred when processing NFT update attributes event for NFT with identifier '${identifier}'`);
this.logger.error(error);
Expand All @@ -71,6 +84,18 @@ export class RabbitMqNftHandlerService {
}
}

private isDynamicNftType(subType?: NftSubType): boolean {
if (subType) {
return [
NftSubType.DynamicNonFungibleESDT,
NftSubType.DynamicSemiFungibleESDT,
NftSubType.DynamicMetaESDT,
].includes(subType);
}

return false;
}

public async handleNftCreateEvent(event: NotifierEvent): Promise<boolean> {
const identifier = this.getNftIdentifier(event.topics);

Expand All @@ -92,6 +117,21 @@ export class RabbitMqNftHandlerService {
}

try {
const isDynamicNft = this.isDynamicNftType(nft.subType);

if (isDynamicNft) {
this.logger.log(`Processing dynamic NFT creation with identifier '${identifier}', forcing full refresh`);

await this.nftWorkerService.addProcessNftQueueJob(nft, new ProcessNftSettings({
uploadAsset: true,
forceRefreshMetadata: true,
forceRefreshMedia: true,
forceRefreshThumbnail: true,
}));

return true;
}

const needsProcessing = await this.nftWorkerService.needsProcessing(nft, new ProcessNftSettings());
if (needsProcessing) {
await this.nftWorkerService.addProcessNftQueueJob(nft, new ProcessNftSettings({ uploadAsset: true }));
Expand All @@ -105,6 +145,62 @@ export class RabbitMqNftHandlerService {
}
}

public async handleNftBurnEvent(event: NotifierEvent): Promise<boolean> {
const identifier = this.getNftIdentifier(event.topics);

this.logger.log(`Detected 'ESDTNFTBurn' event for NFT with identifier '${identifier}'`);

try {
await this.cachingService.delete(`nft:${identifier}`);
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think here we also need to use redis pub-sub service in order to emit a delete local cache key event for the rest of the api instances within the cluster

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

added

this.logger.log(`Cache invalidated for NFT with identifier '${identifier}'`);
return true;
} catch (error) {
this.logger.error(`An unhandled error occurred when processing NFT Burn event for NFT with identifier '${identifier}'`);
this.logger.error(error);
return false;
}
}

public async handleNftMetadataEvent(event: NotifierEvent): Promise<boolean> {
const identifier = this.getNftIdentifier(event.topics);

this.logger.log(`Detected '${event.identifier}' event for NFT with identifier '${identifier}'`);

const nft = await this.nftService.getSingleNft(identifier);
if (!nft) {
this.logger.log(`Could not fetch NFT details for NFT with identifier '${identifier}'`);
return false;
}

try {
await this.nftWorkerService.addProcessNftQueueJob(nft, new ProcessNftSettings({
forceRefreshMetadata: true,
forceRefreshMedia: true,
}));
return true;
} catch (error) {
this.logger.error(`An unhandled error occurred when processing '${event.identifier}' event for NFT with identifier '${identifier}'`);
this.logger.error(error);
return false;
}
}

public async handleNftModifyCreatorEvent(event: NotifierEvent): Promise<boolean> {
const identifier = this.getNftIdentifier(event.topics);

this.logger.log(`Detected 'ESDTModifyCreator' event for NFT with identifier '${identifier}'`);

try {
await this.cachingService.delete(`nft:${identifier}`);
this.logger.log(`Cache invalidated for NFT with identifier '${identifier}'`);
return true;
} catch (error) {
this.logger.error(`An unhandled error occurred when processing NFT ModifyCreator event for NFT with identifier '${identifier}'`);
this.logger.error(error);
return false;
}
}

private getNftIdentifier(topics: string[]): string {
const collection = BinaryUtils.base64Decode(topics[0]);
const nonce = BinaryUtils.base64ToHex(topics[1]);
Expand Down
2 changes: 1 addition & 1 deletion src/endpoints/process-nfts/process.nfts.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ import { OriginLogger } from "@multiversx/sdk-nestjs-common";
@Injectable()
export class ProcessNftsService {
private static readonly MAX_DEPTH = 10;
private static readonly MAXIMUM_PROCESS_RETRIES = 2;
private static readonly MAXIMUM_PROCESS_RETRIES = 10; // TODO: undo to 2

private readonly logger = new OriginLogger(ProcessNftsService.name);

Expand Down
38 changes: 37 additions & 1 deletion src/test/unit/services/rabbitmq.consumer.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,9 @@ describe('RabbitMqConsumer', () => {
const nftHandlerServiceMock = {
handleNftCreateEvent: jest.fn(),
handleNftUpdateAttributesEvent: jest.fn(),
handleNftBurnEvent: jest.fn(),
handleNftMetadataEvent: jest.fn(),
handleNftModifyCreatorEvent: jest.fn(),
};

const tokenHandlerServiceMock = {
Expand Down Expand Up @@ -54,10 +57,43 @@ describe('RabbitMqConsumer', () => {
topics: [''],
};

await service.consumeEvents({ events: [event1, event2] });
const event3: NotifierEvent = {
identifier: NotifierEventIdentifier.ESDTNFTBurn,
address: "erd1",
topics: [''],
};

const event4: NotifierEvent = {
identifier: NotifierEventIdentifier.ESDTMetaDataUpdate,
address: "erd1",
topics: [''],
};

const event5: NotifierEvent = {
identifier: NotifierEventIdentifier.ESDTModifyCreator,
address: "erd1",
topics: [''],
};

await service.consumeEvents({ events: [event1, event2, event3, event4, event5] });

expect(nftHandlerService.handleNftCreateEvent).toHaveBeenCalledWith(event1);
expect(tokenHandlerService.handleTransferOwnershipEvent).toHaveBeenCalledWith(event2);
expect(nftHandlerService.handleNftBurnEvent).toHaveBeenCalledWith(event3);
expect(nftHandlerService.handleNftMetadataEvent).toHaveBeenCalledWith(event4);
expect(nftHandlerService.handleNftModifyCreatorEvent).toHaveBeenCalledWith(event5);
});

it('should handle ESDTMetaDataRecreate with the metadata event handler', async () => {
const event: NotifierEvent = {
identifier: NotifierEventIdentifier.ESDTMetaDataRecreate,
address: "erd1",
topics: [''],
};

await service.consumeEvents({ events: [event] });

expect(nftHandlerService.handleNftMetadataEvent).toHaveBeenCalledWith(event);
});

it('should log the error when an unhandled error occurs', async () => {
Expand Down
Loading