mirror of
https://github.com/benjamin-wilson/public-pool.git
synced 2026-09-29 09:05:06 -07:00
Use current round best share for dashboard
This commit is contained in:
@@ -10,13 +10,13 @@ export class PoolAccountingDashboardIndexes1780867200000 implements MigrationInt
|
||||
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)
|
||||
CREATE INDEX IF NOT EXISTS "IDX_accepted_share_round_best"
|
||||
ON "accepted_share_entity" ("submissionDifficulty" DESC, "acceptedAt" DESC, "blockHeight")
|
||||
`);
|
||||
}
|
||||
|
||||
public async down(queryRunner: QueryRunner): Promise<void> {
|
||||
await queryRunner.query(`DROP INDEX IF EXISTS "IDX_address_settings_best_difficulty"`);
|
||||
await queryRunner.query(`DROP INDEX IF EXISTS "IDX_accepted_share_round_best"`);
|
||||
await queryRunner.query(`DROP INDEX IF EXISTS "IDX_accepted_share_accepted_at"`);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,12 +2,11 @@ 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, AddressSettingsEntity])],
|
||||
imports: [TypeOrmModule.forFeature([AcceptedShareEntity])],
|
||||
providers: [ShareAccountingService],
|
||||
exports: [TypeOrmModule, ShareAccountingService],
|
||||
})
|
||||
|
||||
@@ -152,6 +152,7 @@ describe('ShareAccountingService', () => {
|
||||
hashRateLast10Minutes: 458129844.9,
|
||||
hashRateLastHour: 114532461.2,
|
||||
bestSubmissionDifficulty: 0,
|
||||
bestSubmissionDifficultyAt: null,
|
||||
blockCandidateCount: 0,
|
||||
latestShareAt: '2026-06-07T12:10:00.000Z',
|
||||
protocolBreakdown: [],
|
||||
@@ -164,6 +165,52 @@ describe('ShareAccountingService', () => {
|
||||
expect(repository.query).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
|
||||
it('should overlay live pool data and best share from the current round', async () => {
|
||||
const redis = {
|
||||
setJsonCache: jest.fn().mockResolvedValue(undefined),
|
||||
};
|
||||
const repository = {
|
||||
query: jest.fn()
|
||||
.mockResolvedValueOnce([{
|
||||
totalAcceptedShares: '3',
|
||||
totalCreditedDifficulty: '96',
|
||||
acceptedSharesLast10Minutes: '0',
|
||||
creditedDifficultyLast10Minutes: '0',
|
||||
acceptedSharesLastHour: '3',
|
||||
creditedDifficultyLastHour: '96',
|
||||
acceptedSharesLastDay: '3',
|
||||
creditedDifficultyLastDay: '96',
|
||||
hashRateLast10Minutes: '0',
|
||||
hashRateLastHour: '114532461.2',
|
||||
latestShareAt: new Date('2026-06-07T12:10:00Z'),
|
||||
}])
|
||||
.mockResolvedValueOnce([{
|
||||
acceptedSharesLast10Minutes: '7',
|
||||
creditedDifficultyLast10Minutes: '224',
|
||||
hashRateLast10Minutes: '1603451170.77',
|
||||
latestShareAt: new Date('2026-06-07T12:20:00Z'),
|
||||
}])
|
||||
.mockResolvedValueOnce([{
|
||||
bestSubmissionDifficulty: '4096',
|
||||
bestSubmissionDifficultyAt: new Date('2026-06-07T12:19:00Z'),
|
||||
}]),
|
||||
};
|
||||
const service = new ShareAccountingService(repository as any, redis as any);
|
||||
|
||||
await expect(service.refreshPoolSummary()).resolves.toEqual(expect.objectContaining({
|
||||
acceptedSharesLast10Minutes: 7,
|
||||
creditedDifficultyLast10Minutes: 224,
|
||||
hashRateLast10Minutes: 1603451170.77,
|
||||
bestSubmissionDifficulty: 4096,
|
||||
bestSubmissionDifficultyAt: '2026-06-07T12:19:00.000Z',
|
||||
latestShareAt: '2026-06-07T12:20:00.000Z',
|
||||
}));
|
||||
expect(repository.query).toHaveBeenNthCalledWith(
|
||||
3,
|
||||
expect.stringContaining('WHERE "blockHeight" > latest_found_block."height"'),
|
||||
);
|
||||
});
|
||||
|
||||
it('should cache accounting summaries briefly to protect hot dashboard endpoints', async () => {
|
||||
process.env.SHARE_ACCOUNTING_SUMMARY_CACHE_MS = '1000';
|
||||
const repository = {
|
||||
|
||||
@@ -1,9 +1,8 @@
|
||||
import { Injectable, OnModuleDestroy, Optional } from '@nestjs/common';
|
||||
import { Injectable, OnModuleDestroy } 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';
|
||||
|
||||
@@ -40,6 +39,7 @@ export interface ShareAccountingSummary {
|
||||
hashRateLast10Minutes: number;
|
||||
hashRateLastHour: number;
|
||||
bestSubmissionDifficulty: number;
|
||||
bestSubmissionDifficultyAt: string | null;
|
||||
blockCandidateCount: number;
|
||||
latestShareAt: string | null;
|
||||
protocolBreakdown: {
|
||||
@@ -86,9 +86,6 @@ export class ShareAccountingService implements OnModuleDestroy {
|
||||
constructor(
|
||||
@InjectRepository(AcceptedShareEntity)
|
||||
private readonly acceptedShareRepository: Repository<AcceptedShareEntity>,
|
||||
@Optional()
|
||||
@InjectRepository(AddressSettingsEntity)
|
||||
private readonly addressSettingsRepository?: Repository<AddressSettingsEntity>,
|
||||
private readonly redisMessagingService?: RedisMessagingService,
|
||||
) { }
|
||||
|
||||
@@ -175,15 +172,22 @@ export class ShareAccountingService implements OnModuleDestroy {
|
||||
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"
|
||||
FROM "accepted_share_entity"
|
||||
WHERE "acceptedAt" > NOW() - INTERVAL '10 minutes'
|
||||
`)),
|
||||
timeAsync('share accounting current round best share query', () => this.acceptedShareRepository.query(`
|
||||
WITH latest_found_block AS (
|
||||
SELECT COALESCE(MAX("height"), 0) AS "height"
|
||||
FROM "blocks_entity"
|
||||
)
|
||||
SELECT
|
||||
COALESCE("submissionDifficulty", 0)::float AS "bestSubmissionDifficulty",
|
||||
"acceptedAt" AS "bestSubmissionDifficultyAt"
|
||||
FROM "accepted_share_entity", latest_found_block
|
||||
WHERE "blockHeight" > latest_found_block."height"
|
||||
ORDER BY "submissionDifficulty" DESC, "acceptedAt" DESC
|
||||
LIMIT 1
|
||||
`)),
|
||||
]);
|
||||
|
||||
return {
|
||||
@@ -192,6 +196,9 @@ export class ShareAccountingService implements OnModuleDestroy {
|
||||
creditedDifficultyLast10Minutes: this.toNumber(liveWindow?.creditedDifficultyLast10Minutes),
|
||||
hashRateLast10Minutes: this.toNumber(liveWindow?.hashRateLast10Minutes),
|
||||
bestSubmissionDifficulty: this.toNumber(bestDifficultyRow?.bestSubmissionDifficulty),
|
||||
bestSubmissionDifficultyAt: bestDifficultyRow?.bestSubmissionDifficultyAt == null
|
||||
? null
|
||||
: new Date(bestDifficultyRow.bestSubmissionDifficultyAt).toISOString(),
|
||||
latestShareAt: liveWindow?.latestShareAt == null
|
||||
? summary.latestShareAt
|
||||
: new Date(liveWindow.latestShareAt).toISOString(),
|
||||
@@ -211,6 +218,7 @@ export class ShareAccountingService implements OnModuleDestroy {
|
||||
hashRateLast10Minutes: 0,
|
||||
hashRateLastHour: 0,
|
||||
bestSubmissionDifficulty: 0,
|
||||
bestSubmissionDifficultyAt: null,
|
||||
blockCandidateCount: 0,
|
||||
latestShareAt: null,
|
||||
protocolBreakdown: [],
|
||||
@@ -320,6 +328,7 @@ export class ShareAccountingService implements OnModuleDestroy {
|
||||
hashRateLast10Minutes: this.toNumber(summary?.hashRateLast10Minutes),
|
||||
hashRateLastHour: this.toNumber(summary?.hashRateLastHour),
|
||||
bestSubmissionDifficulty: 0,
|
||||
bestSubmissionDifficultyAt: null,
|
||||
blockCandidateCount: 0,
|
||||
latestShareAt: summary?.latestShareAt == null
|
||||
? null
|
||||
|
||||
@@ -45,6 +45,8 @@ describe('TimescaleDB and Redis integration', () => {
|
||||
await dataSource.query(`DELETE FROM accepted_share_entity`);
|
||||
await dataSource.query(`DELETE FROM client_entity`);
|
||||
await dataSource.query(`REFRESH MATERIALIZED VIEW user_agent_report_view`);
|
||||
await redisMessagingService.clearClientPresence();
|
||||
await (redisMessagingService as any).publisher.del('json-cache:presence:user-agent-report');
|
||||
});
|
||||
|
||||
it('should create Timescale extension, hypertable, continuous aggregates, and operational policies', async () => {
|
||||
|
||||
Reference in New Issue
Block a user