按显卡世代和创世编号结算回本,机房批量接入需审核网段并用硬件指纹防克隆。

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
dekun
2026-10-11 00:30:46 +08:00
parent 7d29be8b17
commit 0740016ad8
22 changed files with 1554 additions and 96 deletions
+45 -16
View File
@@ -7,9 +7,10 @@
3. 任务响应率 = 校验通过且执行成功的任务数 / 当日内下发任务数。
4. 当天没有下发任务时,表现系数等于在线率,避免调度器故障误伤节点。
有任务时,表现系数 = 在线权重 × 在线率 + 任务权重 × 响应率。
5. 算力产出 = 授权金额 × 表现系数。
当日积分 = 算力产出 / 授权天数。
6. 单节点累计积分不超过授权金额。已存在的当日流水不会被覆盖。
5. 显卡世代决定回本天数:40 系及以上 210 天,30 系 365 天,10 系及以下或核显 540 天。
每日基础释放 = 购买金额 / 回本天数。
当日积分 = 每日基础释放 × 表现系数。表现系数就是当日平台收益加权系数。
6. 积分优先冲减 recoverable_stake_amount,直到质押金返还完毕。已存在的当日流水不会被覆盖。
"""
from __future__ import annotations
@@ -28,6 +29,7 @@ from app.errors import AppError
from app.models import ComputeNode, ComputeTask, NodeYield, TaskStatus, UptimeInterval, YieldStatus
from app.money import money, rate, ratio, to_decimal
from app.services.common import get_node_by_sn
from app.services.hardware import DEFAULT_WEIGHT, RECOVERY_10_DAYS
from app.services.tasks import expire_overdue_tasks
@@ -47,6 +49,9 @@ class YieldComputation:
compute_output: Decimal = Decimal("0")
points_delta: Decimal = Decimal("0")
cycle_days: int = 0
base_release: Decimal = Decimal("0")
hardware_weight: Decimal = Decimal("0")
remaining_points: Decimal = Decimal("0")
@dataclass
@@ -68,9 +73,9 @@ def compute_node_yield(
day_start, day_end = day_bounds(yield_date)
window_start = max(day_start, node.licensed_at)
window_end = min(day_end, node.license_expires_at)
cycle_days = (node.license_expires_at.date() - node.licensed_at.date()).days
if window_end <= window_start or cycle_days <= 0:
return YieldComputation(eligible=False, yield_date=yield_date, cycle_days=max(cycle_days, 0))
recovery_days = int(node.recovery_days or RECOVERY_10_DAYS)
if window_end <= window_start or recovery_days <= 0:
return YieldComputation(eligible=False, yield_date=yield_date, cycle_days=max(recovery_days, 0))
eligible_seconds = int((window_end - window_start).total_seconds())
online_seconds = min(_online_seconds(session, node, window_start, window_end, moment), eligible_seconds)
@@ -94,9 +99,11 @@ def compute_node_yield(
applied_online = to_decimal(settings.online_weight)
applied_task = to_decimal(settings.task_weight)
performance = rate(applied_online * online_rate + applied_task * response_rate)
base_release = to_decimal(node.purchase_amount) / Decimal(recovery_days)
hardware_weight = to_decimal(node.hardware_weight if node.hardware_weight is not None else DEFAULT_WEIGHT)
remaining = _remaining_stake(session, node)
points_delta = min(money(base_release * performance), remaining)
compute_output = money(to_decimal(node.purchase_amount) * performance)
points_delta = money(compute_output / Decimal(cycle_days))
points_delta = min(points_delta, _remaining_points(session, node))
return YieldComputation(
eligible=True,
@@ -112,16 +119,20 @@ def compute_node_yield(
performance=performance,
compute_output=compute_output,
points_delta=points_delta,
cycle_days=cycle_days,
cycle_days=recovery_days,
base_release=base_release,
hardware_weight=hardware_weight,
remaining_points=remaining,
)
def settle_yield_date(session: Session, yield_date: date, now: datetime | None = None) -> SettleSummary:
"""清算指定 UTC 日。每个节点当天已有流水时跳过,保证脚本重复执行不会重复入账。"""
def network_yields(
session: Session,
yield_date: date,
now: datetime | None = None,
) -> list[tuple[ComputeNode, YieldComputation]]:
"""算出授权覆盖该日的全部节点。积分按各节点自己的显卡世代释放。"""
moment = now or utcnow()
if yield_date > moment.date():
raise AppError("不能清算未来日期")
expire_overdue_tasks(session, moment)
day_start, day_end = day_bounds(yield_date)
nodes = session.scalars(
select(ComputeNode)
@@ -131,23 +142,32 @@ def settle_yield_date(session: Session, yield_date: date, now: datetime | None =
)
.order_by(ComputeNode.id)
).all()
return [(node, compute_node_yield(session, node, yield_date, moment)) for node in nodes]
def settle_yield_date(session: Session, yield_date: date, now: datetime | None = None) -> SettleSummary:
"""清算指定 UTC 日。每个节点当天已有流水时跳过,保证脚本重复执行不会重复入账。"""
moment = now or utcnow()
if yield_date > moment.date():
raise AppError("不能清算未来日期")
expire_overdue_tasks(session, moment)
created = 0
skipped = 0
total = Decimal("0")
partial = yield_date == moment.date()
for node in nodes:
for node, computed in network_yields(session, yield_date, moment):
exists = session.scalar(
select(NodeYield.id).where(NodeYield.node_id == node.id, NodeYield.yield_date == yield_date)
)
if exists is not None:
skipped += 1
continue
computed = compute_node_yield(session, node, yield_date, moment)
if not computed.eligible:
continue
try:
with session.begin_nested():
session.add(_to_row(node, computed, partial))
node.recoverable_stake_amount = money(computed.remaining_points - computed.points_delta)
except IntegrityError:
skipped += 1
continue
@@ -204,6 +224,15 @@ def _proven_end(interval: UptimeInterval, node: ComputeNode, now: datetime) -> d
return now
def _remaining_stake(session: Session, node: ComputeNode) -> Decimal:
if node.recoverable_stake_amount is not None:
remaining = to_decimal(node.recoverable_stake_amount)
if remaining <= 0:
return Decimal("0")
return money(remaining)
return _remaining_points(session, node)
def _remaining_points(session: Session, node: ComputeNode) -> Decimal:
paid_rows = session.scalars(select(NodeYield.points_delta).where(NodeYield.node_id == node.id)).all()
paid = sum((to_decimal(item) for item in paid_rows), Decimal("0"))