fix(mining): remove duplicate burn mechanism from mining-service

Mining-service incorrectly implemented its own burn mechanism (10-year
cycle) which was not in the requirements. Per requirements, only
trading-service should handle per-minute burn (4756.47/minute).

Removed:
- BlackHoleRepository and all burn-related methods
- executeBurn() from mining distribution service
- Burn stats from admin API and queries
- Burn progress UI from mining admin web

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
hailin 2026-01-16 07:31:13 -08:00
parent f22c3efb11
commit 2534068f70
8 changed files with 7 additions and 244 deletions

View File

@ -49,7 +49,6 @@ export class AdminController {
@ApiOperation({ summary: '获取挖矿系统状态' })
async getStatus() {
const config = await this.prisma.miningConfig.findFirst();
const blackHole = await this.prisma.blackHole.findFirst();
const accountCount = await this.prisma.miningAccount.count();
// 用户有效算力
@ -75,13 +74,6 @@ export class AdminController {
currentEra: config?.currentEra || 0,
remainingDistribution: config?.remainingDistribution?.toString() || '0',
secondDistribution: config?.secondDistribution?.toString() || '0',
blackHole: blackHole
? {
totalBurned: blackHole.totalBurned.toString(),
targetBurn: blackHole.targetBurn.toString(),
remainingBurn: blackHole.remainingBurn.toString(),
}
: null,
accountCount,
// 用户有效算力
totalContribution: userContribution._sum.totalContribution?.toString() || '0',

View File

@ -1,7 +1,6 @@
import { Injectable } from '@nestjs/common';
import { MiningAccountRepository } from '../../infrastructure/persistence/repositories/mining-account.repository';
import { MiningConfigRepository } from '../../infrastructure/persistence/repositories/mining-config.repository';
import { BlackHoleRepository } from '../../infrastructure/persistence/repositories/black-hole.repository';
import { PriceSnapshotRepository } from '../../infrastructure/persistence/repositories/price-snapshot.repository';
import { PrismaService } from '../../infrastructure/persistence/prisma/prisma.service';
@ -23,11 +22,6 @@ export interface MiningStatsDto {
participantCount: number;
totalMined: string;
// 销毁信息
burnTarget: string;
totalBurned: string;
remainingBurn: string;
// 价格信息
currentPrice: string;
priceChangePercent24h: number | null;
@ -45,16 +39,14 @@ export class GetMiningStatsQuery {
constructor(
private readonly accountRepository: MiningAccountRepository,
private readonly configRepository: MiningConfigRepository,
private readonly blackHoleRepository: BlackHoleRepository,
private readonly priceRepository: PriceSnapshotRepository,
private readonly prisma: PrismaService,
) {}
async execute(): Promise<MiningStatsDto> {
const [config, blackHole, latestPrice, price24hAgo, totalMined, participantCount, totalContribution] =
const [config, latestPrice, price24hAgo, totalMined, participantCount, totalContribution] =
await Promise.all([
this.configRepository.getConfig(),
this.blackHoleRepository.getBlackHole(),
this.priceRepository.getLatestSnapshot(),
this.get24hAgoPrice(),
this.getTotalMined(),
@ -83,9 +75,6 @@ export class GetMiningStatsQuery {
totalContribution: totalContribution.toString(),
participantCount,
totalMined: totalMined.toString(),
burnTarget: blackHole?.targetBurn.toString() || '0',
totalBurned: blackHole?.totalBurned.toString() || '0',
remainingBurn: blackHole?.remainingBurn.toString() || '0',
currentPrice: latestPrice?.price.toString() || '1',
priceChangePercent24h,
};

View File

@ -2,7 +2,6 @@ import { Injectable, Logger } from '@nestjs/common';
import { ConfigService } from '@nestjs/config';
import { MiningAccountRepository } from '../../infrastructure/persistence/repositories/mining-account.repository';
import { MiningConfigRepository } from '../../infrastructure/persistence/repositories/mining-config.repository';
import { BlackHoleRepository } from '../../infrastructure/persistence/repositories/black-hole.repository';
import { PriceSnapshotRepository } from '../../infrastructure/persistence/repositories/price-snapshot.repository';
import { OutboxRepository } from '../../infrastructure/persistence/repositories/outbox.repository';
import { SystemMiningAccountRepository } from '../../infrastructure/persistence/repositories/system-mining-account.repository';
@ -15,11 +14,13 @@ import Decimal from 'decimal.js';
/**
*
*
*
*
*
* -
* - MiningRecord记录
*
* trading-service mining-service
*/
@Injectable()
export class MiningDistributionService {
@ -31,7 +32,6 @@ export class MiningDistributionService {
constructor(
private readonly miningAccountRepository: MiningAccountRepository,
private readonly miningConfigRepository: MiningConfigRepository,
private readonly blackHoleRepository: BlackHoleRepository,
private readonly priceSnapshotRepository: PriceSnapshotRepository,
private readonly outboxRepository: OutboxRepository,
private readonly systemMiningAccountRepository: SystemMiningAccountRepository,
@ -196,9 +196,6 @@ export class MiningDistributionService {
await this.writePendingMinuteRecords(currentMinute);
}
// 执行销毁
const burnAmount = await this.executeBurn(currentSecond);
// 更新配置
const newRemaining = config.remainingDistribution.subtract(totalDistributed);
await this.miningConfigRepository.updateRemainingDistribution(newRemaining);
@ -272,8 +269,6 @@ export class MiningDistributionService {
await this.writeMinuteRecords(currentMinute);
}
await this.executeBurn(currentSecond);
const newRemaining = config.remainingDistribution.subtract(totalDistributed);
await this.miningConfigRepository.updateRemainingDistribution(newRemaining);
@ -628,44 +623,6 @@ export class MiningDistributionService {
}
}
/**
*
*/
private async executeBurn(burnSecond: Date): Promise<ShareAmount> {
const blackHole = await this.blackHoleRepository.getBlackHole();
if (!blackHole) {
return ShareAmount.zero();
}
if (blackHole.remainingBurn.isZero()) {
return ShareAmount.zero();
}
const config = await this.miningConfigRepository.getConfig();
if (!config) {
return ShareAmount.zero();
}
// 计算剩余销毁秒数10年
const totalBurnSeconds = 10 * 365 * 24 * 60 * 60;
const remainingSeconds = this.calculator.calculateRemainingSeconds(
config.activatedAt || new Date(),
totalBurnSeconds,
);
const burnAmount = this.calculator.calculateSecondBurn(
blackHole.targetBurn,
blackHole.totalBurned,
remainingSeconds,
);
if (!burnAmount.isZero()) {
await this.blackHoleRepository.recordBurn(burnSecond, burnAmount);
}
return burnAmount;
}
/**
*
*/

View File

@ -4,6 +4,8 @@ import { Price } from '../value-objects/price.vo';
/**
*
*
* trading-service mining-service
*/
export class MiningCalculatorService {
// 总积分股数量: 100.02亿 (SHARE_POOL_A: 100亿 + SHARE_POOL_B: 200万)
@ -12,9 +14,6 @@ export class MiningCalculatorService {
// 初始分配池: 200M
static readonly INITIAL_DISTRIBUTION_POOL = new ShareAmount('200000000');
// 目标销毁量: 10B
static readonly BURN_TARGET = new ShareAmount('10000000000');
// 减半周期: 2年
static readonly HALVING_PERIOD_SECONDS = 2 * 365 * 24 * 60 * 60; // 63,072,000秒
@ -49,30 +48,10 @@ export class MiningCalculatorService {
return secondDistribution.multiply(ratio);
}
/**
*
* secondBurn = (burnTarget - currentBurned) / remainingSeconds
*/
calculateSecondBurn(
burnTarget: ShareAmount,
currentBurned: ShareAmount,
remainingSeconds: number,
): ShareAmount {
if (remainingSeconds <= 0) {
return ShareAmount.zero();
}
const remaining = burnTarget.subtract(currentBurned);
if (remaining.isZero()) {
return ShareAmount.zero();
}
return remaining.divide(remainingSeconds);
}
/**
*
* price = sharePool / (totalShares - blackHole - circulationPool)
* blackHole trading-service
*/
calculatePrice(
sharePool: ShareAmount,
@ -87,21 +66,6 @@ export class MiningCalculatorService {
);
}
/**
* ,
*/
calculateTheoreticalPrice(
sharePool: ShareAmount,
blackHole: ShareAmount,
): Price {
return Price.calculate(
sharePool,
MiningCalculatorService.TOTAL_SHARES,
blackHole,
ShareAmount.zero(),
);
}
/**
*
*/

View File

@ -4,7 +4,6 @@ import { ClientsModule, Transport } from '@nestjs/microservices';
import { PrismaModule } from './persistence/prisma/prisma.module';
import { MiningAccountRepository } from './persistence/repositories/mining-account.repository';
import { MiningConfigRepository } from './persistence/repositories/mining-config.repository';
import { BlackHoleRepository } from './persistence/repositories/black-hole.repository';
import { PriceSnapshotRepository } from './persistence/repositories/price-snapshot.repository';
import { OutboxRepository } from './persistence/repositories/outbox.repository';
import { SystemMiningAccountRepository } from './persistence/repositories/system-mining-account.repository';
@ -40,7 +39,6 @@ import { KafkaProducerService } from './kafka/kafka-producer.service';
providers: [
MiningAccountRepository,
MiningConfigRepository,
BlackHoleRepository,
PriceSnapshotRepository,
OutboxRepository,
SystemMiningAccountRepository,
@ -60,7 +58,6 @@ import { KafkaProducerService } from './kafka/kafka-producer.service';
exports: [
MiningAccountRepository,
MiningConfigRepository,
BlackHoleRepository,
PriceSnapshotRepository,
OutboxRepository,
SystemMiningAccountRepository,

View File

@ -1,111 +0,0 @@
import { Injectable } from '@nestjs/common';
import { PrismaService } from '../prisma/prisma.service';
import { ShareAmount } from '../../../domain/value-objects/share-amount.vo';
export interface BlackHoleEntity {
id: string;
totalBurned: ShareAmount;
targetBurn: ShareAmount;
remainingBurn: ShareAmount;
lastBurnMinute: Date | null;
}
export interface BurnRecordEntity {
id: string;
blackHoleId: string;
burnMinute: Date;
burnAmount: ShareAmount;
remainingTarget: ShareAmount;
}
@Injectable()
export class BlackHoleRepository {
constructor(private readonly prisma: PrismaService) {}
async getBlackHole(): Promise<BlackHoleEntity | null> {
const record = await this.prisma.blackHole.findFirst();
if (!record) {
return null;
}
return this.toDomain(record);
}
async initializeBlackHole(targetBurn: ShareAmount): Promise<void> {
const existing = await this.prisma.blackHole.findFirst();
if (existing) {
return;
}
await this.prisma.blackHole.create({
data: {
totalBurned: 0,
targetBurn: targetBurn.value,
remainingBurn: targetBurn.value,
},
});
}
async recordBurn(burnMinute: Date, burnAmount: ShareAmount): Promise<void> {
const blackHole = await this.prisma.blackHole.findFirst();
if (!blackHole) {
throw new Error('Black hole not initialized');
}
const newTotalBurned = new ShareAmount(blackHole.totalBurned).add(burnAmount);
const newRemainingBurn = new ShareAmount(blackHole.targetBurn).subtract(newTotalBurned);
await this.prisma.$transaction([
this.prisma.blackHole.update({
where: { id: blackHole.id },
data: {
totalBurned: newTotalBurned.value,
remainingBurn: newRemainingBurn.value,
lastBurnMinute: burnMinute,
},
}),
this.prisma.burnRecord.create({
data: {
blackHoleId: blackHole.id,
burnMinute,
burnAmount: burnAmount.value,
remainingTarget: newRemainingBurn.value,
},
}),
]);
}
async getBurnRecords(page: number, pageSize: number): Promise<{
data: BurnRecordEntity[];
total: number;
}> {
const [records, total] = await Promise.all([
this.prisma.burnRecord.findMany({
orderBy: { burnMinute: 'desc' },
skip: (page - 1) * pageSize,
take: pageSize,
}),
this.prisma.burnRecord.count(),
]);
return {
data: records.map((r) => ({
id: r.id,
blackHoleId: r.blackHoleId,
burnMinute: r.burnMinute,
burnAmount: new ShareAmount(r.burnAmount),
remainingTarget: new ShareAmount(r.remainingTarget),
})),
total,
};
}
private toDomain(record: any): BlackHoleEntity {
return {
id: record.id,
totalBurned: new ShareAmount(record.totalBurned),
targetBurn: new ShareAmount(record.targetBurn),
remainingBurn: new ShareAmount(record.remainingBurn),
lastBurnMinute: record.lastBurnMinute,
};
}
}

View File

@ -134,26 +134,6 @@ export default function ConfigsPage() {
</div>
</div>
{miningStatus.blackHole && (
<div className="pt-4 border-t">
<p className="text-sm font-medium mb-2"></p>
<div className="grid grid-cols-3 gap-4">
<div className="space-y-1">
<p className="text-sm text-muted-foreground"></p>
<p className="font-semibold">{formatNumber(miningStatus.blackHole.totalBurned)}</p>
</div>
<div className="space-y-1">
<p className="text-sm text-muted-foreground"></p>
<p className="font-semibold">{formatNumber(miningStatus.blackHole.targetBurn)}</p>
</div>
<div className="space-y-1">
<p className="text-sm text-muted-foreground"></p>
<p className="font-semibold">{formatNumber(miningStatus.blackHole.remainingBurn)}</p>
</div>
</div>
</div>
)}
{/* 算力同步状态提示 */}
{miningStatus.contributionSyncStatus && !miningStatus.contributionSyncStatus.isSynced && (
<div className="flex items-center gap-2 p-3 bg-yellow-50 dark:bg-yellow-900/20 rounded-lg border border-yellow-200 dark:border-yellow-800">

View File

@ -14,11 +14,6 @@ export interface MiningStatus {
currentEra: number;
remainingDistribution: string;
secondDistribution: string;
blackHole?: {
totalBurned: string;
targetBurn: string;
remainingBurn: string;
};
accountCount: number;
totalContribution: string;
contributionSyncStatus?: ContributionSyncStatus;