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:
parent
f22c3efb11
commit
2534068f70
|
|
@ -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',
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
};
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取当前秒(向下取整,去掉毫秒)
|
||||
*/
|
||||
|
|
|
|||
|
|
@ -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(),
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* 计算剩余挖矿秒数
|
||||
*/
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
|
@ -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">
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
Loading…
Reference in New Issue