Skip to content

Commit

Permalink
add events support (#1367)
Browse files Browse the repository at this point in the history
* add events support

* fixes and improvements
  • Loading branch information
cfaur09 authored Oct 30, 2024
1 parent ec39adc commit 62cef99
Show file tree
Hide file tree
Showing 14 changed files with 416 additions and 5 deletions.
31 changes: 31 additions & 0 deletions src/common/indexer/elastic/elastic.indexer.helper.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ import { AccountHistoryFilter } from "src/endpoints/accounts/entities/account.hi
import { SmartContractResultFilter } from "src/endpoints/sc-results/entities/smart.contract.result.filter";
import { ApplicationFilter } from "src/endpoints/applications/entities/application.filter";
import { NftType } from "../entities/nft.type";
import { EventsFilter } from "src/endpoints/events/entities/events.filter";

@Injectable()
export class ElasticIndexerHelper {
Expand Down Expand Up @@ -717,4 +718,34 @@ export class ElasticIndexerHelper {
}
return elasticQuery.withMustCondition(QueryType.Should(functionConditions));
}

public buildEventsFilter(filter: EventsFilter): ElasticQuery {
let elasticQuery = ElasticQuery.create();

if (filter.before) {
elasticQuery = elasticQuery.withRangeFilter('timestamp', new RangeLowerThanOrEqual(filter.before));
}

if (filter.after) {
elasticQuery = elasticQuery.withRangeFilter('timestamp', new RangeGreaterThanOrEqual(filter.after));
}

if (filter.identifier) {
elasticQuery = elasticQuery.withMustMatchCondition('identifier', filter.identifier);
}

if (filter.txHash) {
elasticQuery = elasticQuery.withMustMatchCondition('txHash', filter.txHash);
}

if (filter.shard) {
elasticQuery = elasticQuery.withCondition(QueryConditionOptions.must, QueryType.Match('shardID', filter.shard));
}

if (filter.address) {
elasticQuery = elasticQuery.withMustMatchCondition('address', filter.address);
}

return elasticQuery;
}
}
20 changes: 20 additions & 0 deletions src/common/indexer/elastic/elastic.indexer.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,8 @@ import { AccountAssets } from "src/common/assets/entities/account.assets";
import { NotWritableError } from "../entities/not.writable.error";
import { ApplicationFilter } from "src/endpoints/applications/entities/application.filter";
import { NftType } from "../entities/nft.type";
import { EventsFilter } from "src/endpoints/events/entities/events.filter";
import { Events } from "../entities/events";

@Injectable()
export class ElasticIndexerService implements IndexerInterface {
Expand Down Expand Up @@ -975,4 +977,22 @@ export class ElasticIndexerService implements IndexerInterface {

return await this.elasticService.getCount('scdeploys', elasticQuery);
}

async getEvents(pagination: QueryPagination, filter: EventsFilter): Promise<Events[]> {
const elasticQuery = this.indexerHelper.buildEventsFilter(filter)
.withPagination(pagination)
.withSort([{ name: 'timestamp', order: ElasticSortOrder.descending }]);

return await this.elasticService.getList('events', '_id', elasticQuery);
}

async getEvent(txHash: string): Promise<Events> {
return await this.elasticService.getItem('events', '_id', txHash);
}

async getEventsCount(filter: EventsFilter): Promise<number> {
const elasticQuery = this.indexerHelper.buildEventsFilter(filter);

return await this.elasticService.getCount('events', elasticQuery);
}
}
13 changes: 13 additions & 0 deletions src/common/indexer/entities/events.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
export class Events {
_id: string = '';
logAddress: string = '';
identifier: string = '';
address: string = '';
data: string = '';
topics: string[] = [];
shardID: number = 0;
additionalData: string[] = [];
txOrder: number = 0;
order: number = 0;
timestamp: number = 0;
}
10 changes: 9 additions & 1 deletion src/common/indexer/indexer.interface.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,8 @@ import { Account, AccountHistory, AccountTokenHistory, Block, Collection, MiniBl
import { AccountAssets } from "../assets/entities/account.assets";
import { ProviderDelegators } from "./entities/provider.delegators";
import { ApplicationFilter } from "src/endpoints/applications/entities/application.filter";
import { EventsFilter } from "src/endpoints/events/entities/events.filter";
import { Events } from "./entities/events";

export interface IndexerInterface {
getAccountsCount(filter: AccountQueryOptions): Promise<number>
Expand Down Expand Up @@ -108,7 +110,7 @@ export interface IndexerInterface {

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

getAccountContractsCount( address: string): Promise<number>
getAccountContractsCount(address: string): Promise<number>

getAccountHistory(address: string, pagination: QueryPagination, filter: AccountHistoryFilter): Promise<AccountHistory[]>

Expand Down Expand Up @@ -185,4 +187,10 @@ export interface IndexerInterface {
getApplications(filter: ApplicationFilter, pagination: QueryPagination): Promise<any[]>

getApplicationCount(filter: ApplicationFilter): Promise<number>

getEvents(pagination: QueryPagination, filter: EventsFilter): Promise<Events[]>

getEvent(txHash: string): Promise<Events>

getEventsCount(filter: EventsFilter): Promise<number>
}
17 changes: 17 additions & 0 deletions src/common/indexer/indexer.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,8 @@ import { AccountHistoryFilter } from "src/endpoints/accounts/entities/account.hi
import { AccountAssets } from "../assets/entities/account.assets";
import { ProviderDelegators } from "./entities/provider.delegators";
import { ApplicationFilter } from "src/endpoints/applications/entities/application.filter";
import { EventsFilter } from "src/endpoints/events/entities/events.filter";
import { Events } from "./entities/events";

@Injectable()
export class IndexerService implements IndexerInterface {
Expand Down Expand Up @@ -448,4 +450,19 @@ export class IndexerService implements IndexerInterface {
async getApplicationCount(filter: ApplicationFilter): Promise<number> {
return await this.indexerInterface.getApplicationCount(filter);
}

@LogPerformanceAsync(MetricsEvents.SetIndexerDuration)
async getEvents(pagination: QueryPagination, filter: EventsFilter): Promise<Events[]> {
return await this.indexerInterface.getEvents(pagination, filter);
}

@LogPerformanceAsync(MetricsEvents.SetIndexerDuration)
async getEvent(txHash: string): Promise<Events> {
return await this.indexerInterface.getEvent(txHash);
}

@LogPerformanceAsync(MetricsEvents.SetIndexerDuration)
async getEventsCount(filter: EventsFilter): Promise<number> {
return await this.indexerInterface.getEventsCount(filter);
}
}
15 changes: 13 additions & 2 deletions src/common/indexer/postgres/postgres.indexer.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,8 @@ import { PostgresIndexerHelper } from "./postgres.indexer.helper";
import { AccountAssets } from "src/common/assets/entities/account.assets";
import { ProviderDelegators } from "../entities/provider.delegators";
import { ApplicationFilter } from "src/endpoints/applications/entities/application.filter";
import { EventsFilter } from "src/endpoints/events/entities/events.filter";
import { Events } from "../entities/events";

@Injectable()
export class PostgresIndexerService implements IndexerInterface {
Expand Down Expand Up @@ -53,6 +55,15 @@ export class PostgresIndexerService implements IndexerInterface {
private readonly validatorPublicKeysRepository: Repository<ValidatorPublicKeysDb>,
private readonly indexerHelper: PostgresIndexerHelper,
) { }
getEvents(_pagination: QueryPagination, _filter: EventsFilter): Promise<Events[]> {
throw new Error("Method not implemented.");
}
getEvent(_txHash: string): Promise<Events> {
throw new Error("Method not implemented.");
}
getEventsCount(_filter: EventsFilter): Promise<number> {
throw new Error("Method not implemented.");
}

getAccountDeploys(_pagination: QueryPagination, _address: string): Promise<ScDeploy[]> {
throw new Error("Method not implemented.");
Expand Down Expand Up @@ -402,12 +413,12 @@ export class PostgresIndexerService implements IndexerInterface {
return await query.getMany();
}

getAccountContracts(): Promise<any[]> {
getAccountContracts(): Promise<any[]> {
throw new Error("Method not implemented.");
}

getAccountContractsCount(): Promise<number> {
throw new Error("Method not implemented.");
throw new Error("Method not implemented.");
}

async getAccountHistory(address: string, { from, size }: QueryPagination): Promise<any[]> {
Expand Down
3 changes: 2 additions & 1 deletion src/endpoints/endpoints.controllers.module.ts
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@ import { WebsocketController } from "./websocket/websocket.controller";
import { PoolController } from "./pool/pool.controller";
import { TpsController } from "./tps/tps.controller";
import { ApplicationController } from "./applications/application.controller";
import { EventsController } from "./events/events.controller";

@Module({})
export class EndpointsControllersModule {
Expand All @@ -48,7 +49,7 @@ export class EndpointsControllersModule {
ProviderController, GatewayProxyController, RoundController, SmartContractResultController, ShardController, StakeController, StakeController,
TokenController, TransactionController, UsernameController, VmQueryController, WaitingListController,
HealthCheckController, DappConfigController, WebsocketController, TransferController,
ProcessNftsPublicController, TransactionsBatchController, ApplicationController,
ProcessNftsPublicController, TransactionsBatchController, ApplicationController, EventsController,
];

const isMarketplaceFeatureEnabled = configuration().features?.marketplace?.enabled ?? false;
Expand Down
4 changes: 3 additions & 1 deletion src/endpoints/endpoints.services.module.ts
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@ import { WebsocketModule } from "./websocket/websocket.module";
import { PoolModule } from "./pool/pool.module";
import { TpsModule } from "./tps/tps.module";
import { ApplicationModule } from "./applications/application.module";
import { EventsModule } from "./events/events.module";

@Module({
imports: [
Expand Down Expand Up @@ -75,13 +76,14 @@ import { ApplicationModule } from "./applications/application.module";
TransactionsBatchModule,
TpsModule,
ApplicationModule,
EventsModule,
],
exports: [
AccountModule, CollectionModule, BlockModule, DelegationModule, DelegationLegacyModule, IdentitiesModule, KeysModule,
MiniBlockModule, NetworkModule, NftModule, NftMediaModule, TagModule, NodeModule, ProviderModule,
RoundModule, SmartContractResultModule, ShardModule, StakeModule, TokenModule, RoundModule, TransactionModule, UsernameModule, VmQueryModule,
WaitingListModule, EsdtModule, BlsModule, DappConfigModule, TransferModule, PoolModule, TransactionActionModule, WebsocketModule, MexModule,
ProcessNftsModule, NftMarketplaceModule, TransactionsBatchModule, TpsModule, ApplicationModule,
ProcessNftsModule, NftMarketplaceModule, TransactionsBatchModule, TpsModule, ApplicationModule, EventsModule,
],
})
export class EndpointsServicesModule { }
13 changes: 13 additions & 0 deletions src/endpoints/events/entities/events.filter.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@

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

identifier: string = '';
address: string = '';
txHash: string = '';
shard: number = 0;
before: number = 0;
after: number = 0;
}
42 changes: 42 additions & 0 deletions src/endpoints/events/entities/events.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
import { ObjectType } from '@nestjs/graphql';
import { ApiProperty } from '@nestjs/swagger';

@ObjectType("Events", { description: "Events object type." })
export class Events {
constructor(init?: Partial<Events>) {
Object.assign(this, init);
}

@ApiProperty({ description: "Transaction hash." })
txHash: string = '';

@ApiProperty({ description: "Log address." })
logAddress: string = '';

@ApiProperty({ description: "Event identifier." })
identifier: string = '';

@ApiProperty({ description: "Event address." })
address: string = '';

@ApiProperty({ description: "Event data." })
data: string = '';

@ApiProperty({ description: "Event topics." })
topics: string[] = [];

@ApiProperty({ description: "Event shard ID." })
shardID: number = 0;

@ApiProperty({ description: "Event additional data." })
additionalData: string[] = [];

@ApiProperty({ description: "Event tx order." })
txOrder: number = 0;

@ApiProperty({ description: "Event block order." })
order: number = 0;

@ApiProperty({ description: "Event timestamp." })
timestamp: number = 0;
}
78 changes: 78 additions & 0 deletions src/endpoints/events/events.controller.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
import { Controller, DefaultValuePipe, Get, NotFoundException, Param, Query } from '@nestjs/common';
import { ApiOkResponse, ApiOperation, ApiQuery, ApiTags } from '@nestjs/swagger';
import { EventsService } from './events.service';
import { QueryPagination } from '../../common/entities/query.pagination';
import { ParseAddressPipe, ParseIntPipe } from '@multiversx/sdk-nestjs-common';

import { Events } from './entities/events';
import { EventsFilter } from './entities/events.filter';

@Controller()
@ApiTags('events')
export class EventsController {
constructor(
private readonly eventsService: EventsService,
) { }

@Get('/events')
@ApiOperation({ summary: 'Events', description: 'Returns events' })
@ApiOkResponse({ type: [Events] })
@ApiQuery({ name: 'from', description: 'Number of items to skip for the result set', required: false })
@ApiQuery({ name: 'size', description: 'Number of items to retrieve', required: false })
@ApiQuery({ name: 'address', description: 'Event address', required: false })
@ApiQuery({ name: 'identifier', description: 'Event identifier', required: false })
@ApiQuery({ name: 'txHash', description: 'Event transaction hash', required: false })
@ApiQuery({ name: 'shard', description: 'Event shard id', required: false })
@ApiQuery({ name: 'before', description: 'Event before timestamp', required: false })
@ApiQuery({ name: 'after', description: 'Event after timestamp', required: false })
async getEvents(
@Query('from', new DefaultValuePipe(0), ParseIntPipe) from: number,
@Query('size', new DefaultValuePipe(25), ParseIntPipe) size: number,
@Query('address', ParseAddressPipe) address: string,
@Query('identifier') identifier: string,
@Query('txHash') txHash: string,
@Query('shard', ParseIntPipe) shard: number,
@Query('before', ParseIntPipe) before: number,
@Query('after', ParseIntPipe) after: number,
): Promise<Events[]> {
return await this.eventsService.getEvents(
new QueryPagination({ from, size }),
new EventsFilter({ address, identifier, txHash, shard, after, before }));
}

@Get('/events/count')
@ApiOperation({ summary: 'Events count', description: 'Returns events count' })
@ApiOkResponse({ type: Number })
@ApiQuery({ name: 'address', description: 'Event address', required: false })
@ApiQuery({ name: 'identifier', description: 'Event identifier', required: false })
@ApiQuery({ name: 'txHash', description: 'Event transaction hash', required: false })
@ApiQuery({ name: 'shard', description: 'Event shard id', required: false })
@ApiQuery({ name: 'before', description: 'Event before timestamp', required: false })
@ApiQuery({ name: 'after', description: 'Event after timestamp', required: false })
async getEventsCount(
@Query('address', ParseAddressPipe) address: string,
@Query('identifier') identifier: string,
@Query('txHash') txHash: string,
@Query('shard', ParseIntPipe) shard: number,
@Query('before', ParseIntPipe) before: number,
@Query('after', ParseIntPipe) after: number,
): Promise<number> {
return await this.eventsService.getEventsCount(
new EventsFilter({ address, identifier, txHash, shard, after, before }));
}

@Get('/events/:txHash')
@ApiOperation({ summary: 'Event', description: 'Returns event' })
@ApiOkResponse({ type: Events })
async getEvent(
@Param('txHash') txHash: string,
): Promise<Events | undefined> {
const result = await this.eventsService.getEvent(txHash);

if (!result) {
throw new NotFoundException('Event not found');
}

return result;
}
}
8 changes: 8 additions & 0 deletions src/endpoints/events/events.module.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
import { Module } from '@nestjs/common';
import { EventsService } from './events.service';

@Module({
providers: [EventsService],
exports: [EventsService],
})
export class EventsModule { }
Loading

0 comments on commit 62cef99

Please sign in to comment.