diff --git a/src/ORM/_migrations/PoolAccountingDashboardIndexes1780867200000.ts b/src/ORM/_migrations/PoolAccountingDashboardIndexes1780867200000.ts new file mode 100644 index 0000000..1811290 --- /dev/null +++ b/src/ORM/_migrations/PoolAccountingDashboardIndexes1780867200000.ts @@ -0,0 +1,22 @@ +import { MigrationInterface, QueryRunner } from 'typeorm'; + +export class PoolAccountingDashboardIndexes1780867200000 implements MigrationInterface { + public name = 'PoolAccountingDashboardIndexes1780867200000'; + public transaction = false; + + public async up(queryRunner: QueryRunner): Promise { + await queryRunner.query(` + CREATE INDEX IF NOT EXISTS "IDX_accepted_share_accepted_at" + ON "accepted_share_entity" ("acceptedAt" DESC) + `); + await queryRunner.query(` + CREATE INDEX IF NOT EXISTS "IDX_address_settings_best_difficulty" + ON "address_settings_entity" ("bestDifficulty" DESC) + `); + } + + public async down(queryRunner: QueryRunner): Promise { + await queryRunner.query(`DROP INDEX IF EXISTS "IDX_address_settings_best_difficulty"`); + await queryRunner.query(`DROP INDEX IF EXISTS "IDX_accepted_share_accepted_at"`); + } +} diff --git a/src/ORM/share-accounting/share-accounting.module.ts b/src/ORM/share-accounting/share-accounting.module.ts index f2d688d..c752dd4 100644 --- a/src/ORM/share-accounting/share-accounting.module.ts +++ b/src/ORM/share-accounting/share-accounting.module.ts @@ -2,11 +2,12 @@ import { Global, Module } from '@nestjs/common'; import { TypeOrmModule } from '@nestjs/typeorm'; import { AcceptedShareEntity } from '../accepted-share/accepted-share.entity'; +import { AddressSettingsEntity } from '../address-settings/address-settings.entity'; import { ShareAccountingService } from './share-accounting.service'; @Global() @Module({ - imports: [TypeOrmModule.forFeature([AcceptedShareEntity])], + imports: [TypeOrmModule.forFeature([AcceptedShareEntity, AddressSettingsEntity])], providers: [ShareAccountingService], exports: [TypeOrmModule, ShareAccountingService], }) diff --git a/src/ORM/share-accounting/share-accounting.service.ts b/src/ORM/share-accounting/share-accounting.service.ts index 2ccc2cf..6389fe3 100644 --- a/src/ORM/share-accounting/share-accounting.service.ts +++ b/src/ORM/share-accounting/share-accounting.service.ts @@ -1,8 +1,9 @@ -import { Injectable, OnModuleDestroy } from '@nestjs/common'; +import { Injectable, OnModuleDestroy, Optional } from '@nestjs/common'; import { InjectRepository } from '@nestjs/typeorm'; import { Repository } from 'typeorm'; import { AcceptedShareEntity } from '../accepted-share/accepted-share.entity'; +import { AddressSettingsEntity } from '../address-settings/address-settings.entity'; import { RedisMessagingService } from '../../services/redis-messaging.service'; import { timeAsync } from '../../utils/timing.utils'; @@ -85,6 +86,9 @@ export class ShareAccountingService implements OnModuleDestroy { constructor( @InjectRepository(AcceptedShareEntity) private readonly acceptedShareRepository: Repository, + @Optional() + @InjectRepository(AddressSettingsEntity) + private readonly addressSettingsRepository?: Repository, private readonly redisMessagingService?: RedisMessagingService, ) { } @@ -150,7 +154,7 @@ export class ShareAccountingService implements OnModuleDestroy { } public async refreshPoolSummary(): Promise { - const summary = await this.getSummary({}); + const summary = await this.withPoolLiveOverlay(await this.getSummary({})); await this.redisMessagingService ?.setJsonCache(this.poolSummaryCacheKey, summary, 10 * 60 * 1000) .catch(error => { @@ -159,6 +163,41 @@ export class ShareAccountingService implements OnModuleDestroy { return summary; } + private async withPoolLiveOverlay(summary: ShareAccountingSummary): Promise { + if (process.env.API_ONLY === 'true') { + return summary; + } + + const [[liveWindow], [bestDifficultyRow]] = await Promise.all([ + timeAsync('share accounting live 10m pool query', () => this.acceptedShareRepository.query(` + SELECT + COUNT(*)::int AS "acceptedSharesLast10Minutes", + COALESCE(SUM("creditedDifficulty"), 0)::float AS "creditedDifficultyLast10Minutes", + COALESCE((SUM("creditedDifficulty") * ${HASHES_PER_DIFFICULTY}) / 600, 0)::float AS "hashRateLast10Minutes", + MAX("acceptedAt") AS "latestShareAt" + FROM "accepted_share_entity" + WHERE "acceptedAt" > NOW() - INTERVAL '10 minutes' + `)), + this.addressSettingsRepository == null + ? Promise.resolve([{ bestSubmissionDifficulty: 0 }]) + : timeAsync('share accounting best submitted share query', () => this.addressSettingsRepository.query(` + SELECT COALESCE(MAX("bestDifficulty"), 0)::float AS "bestSubmissionDifficulty" + FROM "address_settings_entity" + `)), + ]); + + return { + ...summary, + acceptedSharesLast10Minutes: this.toNumber(liveWindow?.acceptedSharesLast10Minutes), + creditedDifficultyLast10Minutes: this.toNumber(liveWindow?.creditedDifficultyLast10Minutes), + hashRateLast10Minutes: this.toNumber(liveWindow?.hashRateLast10Minutes), + bestSubmissionDifficulty: this.toNumber(bestDifficultyRow?.bestSubmissionDifficulty), + latestShareAt: liveWindow?.latestShareAt == null + ? summary.latestShareAt + : new Date(liveWindow.latestShareAt).toISOString(), + }; + } + public emptySummary(): ShareAccountingSummary { return { totalAcceptedShares: 0, diff --git a/src/database.config.ts b/src/database.config.ts index e0d85c2..5a0e6e6 100644 --- a/src/database.config.ts +++ b/src/database.config.ts @@ -6,6 +6,7 @@ import { ActiveOnlyUserAgentReport1780860200000 } from './ORM/_migrations/Active import { TimescaleOperationalHardening1780861200000 } from './ORM/_migrations/TimescaleOperationalHardening1780861200000'; import { AcceptedShareIndex1780862400000 } from './ORM/_migrations/AcceptedShareIndex1780862400000'; import { AcceptedShareRollupIndexes1780865400000 } from './ORM/_migrations/AcceptedShareRollupIndexes1780865400000'; +import { PoolAccountingDashboardIndexes1780867200000 } from './ORM/_migrations/PoolAccountingDashboardIndexes1780867200000'; import { UserAgentReportView } from './ORM/_views/user-agent-report/user-agent-report.view'; import { AcceptedShareEntity } from './ORM/accepted-share/accepted-share.entity'; import { AddressSettingsEntity } from './ORM/address-settings/address-settings.entity'; @@ -30,6 +31,7 @@ export const databaseMigrations = [ TimescaleOperationalHardening1780861200000, AcceptedShareIndex1780862400000, AcceptedShareRollupIndexes1780865400000, + PoolAccountingDashboardIndexes1780867200000, ]; export function createDatabaseOptions(env: NodeJS.ProcessEnv): TypeOrmModuleOptions & DataSourceOptions {