Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

add events support #1367

Merged
merged 2 commits into from
Oct 30, 2024
Merged
Show file tree
Hide file tree
Changes from all 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
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
Loading