fix(batch-mining): 修正补发计算逻辑

- 去掉虚构的'全网算力'概念
- 每天固定分配70%产出给参与用户
- 用户收益 = 每日产出 × 70% × 天数 × (用户算力/当前参与总算力)
- 总补发金额固定为: 日产出 × 70% × 总天数

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
hailin 2026-01-21 23:08:55 -08:00
parent 702fa937e8
commit f14ad0b7ad
1 changed files with 202 additions and 167 deletions

View File

@ -89,8 +89,8 @@ export interface BatchMiningPreviewResult {
// 常量 // 常量
const BASE_CONTRIBUTION_PER_TREE = new Decimal('22617'); // 每棵树的基础算力 const BASE_CONTRIBUTION_PER_TREE = new Decimal('22617'); // 每棵树的基础算力
const SECONDS_PER_DAY = 86400; const SECONDS_PER_DAY = 86400;
// 用户算力占全网算力的比例用户占70%30%是系统、运营、层级、团队的) // 每天产出的70%分给补发用户
const USER_NETWORK_RATIO = new Decimal('0.70'); const DAILY_DISTRIBUTION_RATIO = new Decimal('0.70');
/** /**
* *
@ -100,7 +100,7 @@ interface MiningPhase {
startDate: Date; startDate: Date;
endDate: Date; endDate: Date;
daysInPhase: number; daysInPhase: number;
networkContribution: Decimal; // 该阶段的全网算力 participatingContribution: Decimal; // 该阶段参与用户的总算力
participatingBatches: number[]; // 参与该阶段的批次号 participatingBatches: number[]; // 参与该阶段的批次号
} }
@ -109,9 +109,9 @@ interface MiningPhase {
* *
* : * :
* 1. * 1.
* 2. * 2. 70%
* 3. = × / × 70% * 3. = × /
* 4. = ( / ) × × × 86400 * 4. = × 70% × × ( / )
* 5. = * 5. =
*/ */
@Injectable() @Injectable()
@ -196,18 +196,23 @@ export class BatchMiningService {
} }
// 定义挖矿阶段 // 定义挖矿阶段
// 阶段1: 批次1独挖3 // 阶段1: 批次1独挖 preMineDays
// 阶段2: 批次1+2共挖2 // 阶段2: 批次1+2共挖 preMineDays
// 阶段3: 批次1+2+3共挖1天 // ...依次类推
// 阶段4: 所有批次共挖(剩余天数) // 最后阶段: 所有批次共挖(剩余天数)
const phases = this.buildMiningPhases(items, sortedBatches, batchContributions); const phases = this.buildMiningPhases(items, sortedBatches, batchContributions);
this.logger.log(`[preview] 挖矿阶段: ${JSON.stringify(phases.map(p => ({ this.logger.log(`[preview] 挖矿阶段: ${JSON.stringify(phases.map(p => ({
phase: p.phaseNumber, phase: p.phaseNumber,
days: p.daysInPhase, days: p.daysInPhase,
batches: p.participatingBatches, batches: p.participatingBatches,
networkContribution: p.networkContribution.toFixed(2) participatingContribution: p.participatingContribution.toFixed(2)
})))}`); })))}`);
// 每天补发额度 = 日产出 × 70%
const dailyDistribution = secondDistribution.times(SECONDS_PER_DAY);
const dailyAllocation = dailyDistribution.times(DAILY_DISTRIBUTION_RATIO);
this.logger.log(`[preview] 每日产出: ${dailyDistribution.toFixed(8)}, 每日补发额度(70%): ${dailyAllocation.toFixed(8)}`);
// 计算每个用户在各阶段的收益 // 计算每个用户在各阶段的收益
const userAmounts = new Map<string, Decimal>(); const userAmounts = new Map<string, Decimal>();
for (const item of items) { for (const item of items) {
@ -215,15 +220,16 @@ export class BatchMiningService {
} }
for (const phase of phases) { for (const phase of phases) {
const dailyDistribution = secondDistribution.times(SECONDS_PER_DAY); // 该阶段的总补发额 = 每日补发额度 × 阶段天数
const phaseDistribution = dailyDistribution.times(phase.daysInPhase); const phaseAllocation = dailyAllocation.times(phase.daysInPhase);
for (const item of items) { for (const item of items) {
// 检查该用户的批次是否参与此阶段 // 检查该用户的批次是否参与此阶段
if (phase.participatingBatches.includes(item.batch)) { if (phase.participatingBatches.includes(item.batch)) {
const userContribution = userContributions.get(item.accountSequence)!; const userContribution = userContributions.get(item.accountSequence)!;
const ratio = userContribution.dividedBy(phase.networkContribution); // 用户收益 = 阶段补发额 × (用户算力 / 当前参与总算力)
const phaseAmount = phaseDistribution.times(ratio); const ratio = userContribution.dividedBy(phase.participatingContribution);
const phaseAmount = phaseAllocation.times(ratio);
const currentAmount = userAmounts.get(item.accountSequence)!; const currentAmount = userAmounts.get(item.accountSequence)!;
userAmounts.set(item.accountSequence, currentAmount.plus(phaseAmount)); userAmounts.set(item.accountSequence, currentAmount.plus(phaseAmount));
@ -235,14 +241,12 @@ export class BatchMiningService {
let grandTotalAmount = new Decimal(0); let grandTotalAmount = new Decimal(0);
const batchResults: BatchMiningPreviewResult['batches'] = []; const batchResults: BatchMiningPreviewResult['batches'] = [];
// 计算累计全网算力(最终状态) // 计算用户总算力(用于显示)
// 用户只占全网的70%30%是系统、运营、层级、团队),所以全网算力 = 用户算力 / 0.7
let totalUserContribution = new Decimal(0); let totalUserContribution = new Decimal(0);
for (const contribution of batchContributions.values()) { for (const contribution of batchContributions.values()) {
totalUserContribution = totalUserContribution.plus(contribution); totalUserContribution = totalUserContribution.plus(contribution);
} }
const finalNetworkContribution = totalUserContribution.dividedBy(USER_NETWORK_RATIO); this.logger.log(`[preview] 用户总算力: ${totalUserContribution.toFixed(2)}`);
this.logger.log(`[preview] 用户总算力: ${totalUserContribution.toFixed(2)}, 全网算力(用户占70%): ${finalNetworkContribution.toFixed(2)}`);
for (const batchNum of sortedBatches) { for (const batchNum of sortedBatches) {
const batchItems = batchGroups.get(batchNum)!; const batchItems = batchGroups.get(batchNum)!;
@ -254,14 +258,15 @@ export class BatchMiningService {
for (const item of batchItems) { for (const item of batchItems) {
const userContribution = userContributions.get(item.accountSequence)!; const userContribution = userContributions.get(item.accountSequence)!;
const amount = userAmounts.get(item.accountSequence)!; const amount = userAmounts.get(item.accountSequence)!;
const ratio = userContribution.dividedBy(finalNetworkContribution); // 显示用:用户算力占总用户算力的比例
const ratio = userContribution.dividedBy(totalUserContribution);
users.push({ users.push({
accountSequence: item.accountSequence, accountSequence: item.accountSequence,
treeCount: item.treeCount, treeCount: item.treeCount,
preMineDays: item.preMineDays, preMineDays: item.preMineDays,
userContribution: userContribution.toFixed(10), userContribution: userContribution.toFixed(10),
networkContribution: finalNetworkContribution.toFixed(10), networkContribution: totalUserContribution.toFixed(10), // 显示总用户算力
contributionRatio: ratio.toFixed(18), contributionRatio: ratio.toFixed(18),
totalSeconds: (item.preMineDays * SECONDS_PER_DAY).toString(), totalSeconds: (item.preMineDays * SECONDS_PER_DAY).toString(),
estimatedAmount: amount.toFixed(8), estimatedAmount: amount.toFixed(8),
@ -367,25 +372,22 @@ export class BatchMiningService {
const phaseDays = currentPreMineDays; const phaseDays = currentPreMineDays;
if (phaseDays > 0) { if (phaseDays > 0) {
// 计算该阶段参与用户的算力 // 计算该阶段参与用户的算力
let participatingContribution = new Decimal(0); let participatingContribution = new Decimal(0);
for (const batch of participatingBatches) { for (const batch of participatingBatches) {
participatingContribution = participatingContribution.plus(batchContributions.get(batch) || 0); participatingContribution = participatingContribution.plus(batchContributions.get(batch) || 0);
} }
// 实际全网算力 = 参与用户算力 / 用户占比
// 因为用户只占全网的70%30%是系统、运营、层级、团队),所以全网算力 = 用户算力 / 0.7
const networkContribution = participatingContribution.dividedBy(USER_NETWORK_RATIO);
phases.push({ phases.push({
phaseNumber: currentPhase, phaseNumber: currentPhase,
startDate: new Date(), startDate: new Date(),
endDate: new Date(), endDate: new Date(),
daysInPhase: phaseDays, daysInPhase: phaseDays,
networkContribution, participatingContribution,
participatingBatches: [...participatingBatches], participatingBatches: [...participatingBatches],
}); });
this.logger.log(`[buildMiningPhases] 阶段${currentPhase}: ${phaseDays}天, 批次[${participatingBatches.join(',')}], 参与算力=${participatingContribution.toFixed(2)}, 全网算力=${networkContribution.toFixed(2)}`); this.logger.log(`[buildMiningPhases] 阶段${currentPhase}: ${phaseDays}天, 批次[${participatingBatches.join(',')}], 参与算力=${participatingContribution.toFixed(2)}`);
currentPhase++; currentPhase++;
usedDays += phaseDays; usedDays += phaseDays;
} }
@ -399,19 +401,17 @@ export class BatchMiningService {
for (const batch of sortedBatches) { for (const batch of sortedBatches) {
participatingContribution = participatingContribution.plus(batchContributions.get(batch) || 0); participatingContribution = participatingContribution.plus(batchContributions.get(batch) || 0);
} }
// 实际全网算力 = 参与用户算力 / 用户占比
const networkContribution = participatingContribution.dividedBy(USER_NETWORK_RATIO);
phases.push({ phases.push({
phaseNumber: currentPhase, phaseNumber: currentPhase,
startDate: new Date(), startDate: new Date(),
endDate: new Date(), endDate: new Date(),
daysInPhase: remainingDays, daysInPhase: remainingDays,
networkContribution, participatingContribution,
participatingBatches: [...sortedBatches], participatingBatches: [...sortedBatches],
}); });
this.logger.log(`[buildMiningPhases] 阶段${currentPhase}(最终): ${remainingDays}天, 所有批次[${sortedBatches.join(',')}], 参与算力=${participatingContribution.toFixed(2)}, 全网算力=${networkContribution.toFixed(2)}`); this.logger.log(`[buildMiningPhases] 阶段${currentPhase}(最终): ${remainingDays}天, 所有批次[${sortedBatches.join(',')}], 参与算力=${participatingContribution.toFixed(2)}`);
} }
return phases; return phases;
@ -447,7 +447,57 @@ export class BatchMiningService {
const batchGroups = this.groupByBatch(items); const batchGroups = this.groupByBatch(items);
const sortedBatches = Array.from(batchGroups.keys()).sort((a, b) => a - b); const sortedBatches = Array.from(batchGroups.keys()).sort((a, b) => a - b);
let cumulativeContribution = new Decimal(0); // 计算每个批次的算力
const batchContributions = new Map<number, Decimal>();
for (const batchNum of sortedBatches) {
const batchItems = batchGroups.get(batchNum)!;
let batchTotal = new Decimal(0);
for (const item of batchItems) {
batchTotal = batchTotal.plus(this.calculateUserContribution(item.treeCount));
}
batchContributions.set(batchNum, batchTotal);
}
// 计算每个用户的算力
const userContributions = new Map<string, Decimal>();
for (const item of items) {
userContributions.set(item.accountSequence, this.calculateUserContribution(item.treeCount));
}
// 构建挖矿阶段
const phases = this.buildMiningPhases(items, sortedBatches, batchContributions);
// 每天补发额度 = 日产出 × 70%
const dailyDistribution = secondDistribution.times(SECONDS_PER_DAY);
const dailyAllocation = dailyDistribution.times(DAILY_DISTRIBUTION_RATIO);
// 计算每个用户在各阶段的收益
const userAmounts = new Map<string, Decimal>();
for (const item of items) {
userAmounts.set(item.accountSequence, new Decimal(0));
}
for (const phase of phases) {
const phaseAllocation = dailyAllocation.times(phase.daysInPhase);
for (const item of items) {
if (phase.participatingBatches.includes(item.batch)) {
const userContribution = userContributions.get(item.accountSequence)!;
const ratio = userContribution.dividedBy(phase.participatingContribution);
const phaseAmount = phaseAllocation.times(ratio);
const currentAmount = userAmounts.get(item.accountSequence)!;
userAmounts.set(item.accountSequence, currentAmount.plus(phaseAmount));
}
}
}
// 计算总用户算力(用于记录)
let totalUserContribution = new Decimal(0);
for (const contribution of batchContributions.values()) {
totalUserContribution = totalUserContribution.plus(contribution);
}
const results: BatchMiningItemResult[] = []; const results: BatchMiningItemResult[] = [];
let successCount = 0; let successCount = 0;
let failedCount = 0; let failedCount = 0;
@ -467,27 +517,13 @@ export class BatchMiningService {
}, },
}); });
// 2. 按批次处理 // 2. 处理每个用户
for (const batchNum of sortedBatches) { for (const item of items) {
const batchItems = batchGroups.get(batchNum)!;
// 计算当前批次的总算力
let batchTotalContribution = new Decimal(0);
for (const item of batchItems) {
const userContribution = this.calculateUserContribution(item.treeCount);
batchTotalContribution = batchTotalContribution.plus(userContribution);
}
// 当前批次的全网算力
cumulativeContribution = cumulativeContribution.plus(batchTotalContribution);
// 处理当前批次的每个用户
for (const item of batchItems) {
try { try {
const userContribution = this.calculateUserContribution(item.treeCount); const userContribution = userContributions.get(item.accountSequence)!;
const ratio = userContribution.dividedBy(cumulativeContribution); const amount = userAmounts.get(item.accountSequence)!;
const ratio = userContribution.dividedBy(totalUserContribution);
const totalSeconds = BigInt(item.preMineDays * SECONDS_PER_DAY); const totalSeconds = BigInt(item.preMineDays * SECONDS_PER_DAY);
const amount = secondDistribution.times(totalSeconds.toString()).times(ratio);
const manualAmount = new ShareAmount(amount); const manualAmount = new ShareAmount(amount);
// 查找或创建挖矿账户 // 查找或创建挖矿账户
@ -524,7 +560,7 @@ export class BatchMiningService {
}); });
// 创建明细记录 // 创建明细记录
const description = `批量补发挖矿收益 - 批次:${batchNum} - 认种棵数:${item.treeCount} - 提前挖${item.preMineDays}天 - 操作人:${operatorName} - ${reason}`; const description = `批量补发挖矿收益 - 批次:${item.batch} - 认种棵数:${item.treeCount} - 提前挖${item.preMineDays}天 - 操作人:${operatorName} - ${reason}`;
await tx.miningTransaction.create({ await tx.miningTransaction.create({
data: { data: {
@ -544,11 +580,11 @@ export class BatchMiningService {
data: { data: {
executionId: execution.id, executionId: execution.id,
accountSequence: item.accountSequence, accountSequence: item.accountSequence,
batch: batchNum, batch: item.batch,
treeCount: item.treeCount, treeCount: item.treeCount,
preMineDays: item.preMineDays, preMineDays: item.preMineDays,
userContribution, userContribution,
networkContribution: cumulativeContribution, networkContribution: totalUserContribution,
contributionRatio: ratio, contributionRatio: ratio,
totalSeconds, totalSeconds,
amount: manualAmount.value, amount: manualAmount.value,
@ -568,12 +604,12 @@ export class BatchMiningService {
eventId: `${execution.id}-${item.accountSequence}`, eventId: `${execution.id}-${item.accountSequence}`,
executionId: execution.id, executionId: execution.id,
accountSequence: item.accountSequence, accountSequence: item.accountSequence,
batch: batchNum, batch: item.batch,
amount: manualAmount.value.toString(), amount: manualAmount.value.toString(),
treeCount: item.treeCount, treeCount: item.treeCount,
preMineDays: item.preMineDays, preMineDays: item.preMineDays,
userContribution: userContribution.toString(), userContribution: userContribution.toString(),
networkContribution: cumulativeContribution.toString(), networkContribution: totalUserContribution.toString(),
contributionRatio: ratio.toString(), contributionRatio: ratio.toString(),
totalSeconds: totalSeconds.toString(), totalSeconds: totalSeconds.toString(),
operatorId, operatorId,
@ -586,10 +622,10 @@ export class BatchMiningService {
results.push({ results.push({
accountSequence: item.accountSequence, accountSequence: item.accountSequence,
batch: batchNum, batch: item.batch,
treeCount: item.treeCount, treeCount: item.treeCount,
userContribution: userContribution.toFixed(10), userContribution: userContribution.toFixed(10),
networkContribution: cumulativeContribution.toFixed(10), networkContribution: totalUserContribution.toFixed(10),
contributionRatio: ratio.toFixed(18), contributionRatio: ratio.toFixed(18),
preMineDays: item.preMineDays, preMineDays: item.preMineDays,
totalSeconds: totalSeconds.toString(), totalSeconds: totalSeconds.toString(),
@ -604,7 +640,7 @@ export class BatchMiningService {
this.logger.error(`Failed to process ${item.accountSequence}: ${error.message}`); this.logger.error(`Failed to process ${item.accountSequence}: ${error.message}`);
results.push({ results.push({
accountSequence: item.accountSequence, accountSequence: item.accountSequence,
batch: batchNum, batch: item.batch,
treeCount: item.treeCount, treeCount: item.treeCount,
userContribution: '0', userContribution: '0',
networkContribution: '0', networkContribution: '0',
@ -618,7 +654,6 @@ export class BatchMiningService {
failedCount++; failedCount++;
} }
} }
}
// 更新执行记录 // 更新执行记录
await tx.batchMiningExecution.update({ await tx.batchMiningExecution.update({