Files
m2pool_docs/m2pool_backend_app/src/distribution.py
2026-01-30 17:47:21 +08:00

265 lines
11 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

import asyncio
from datetime import datetime
from decimal import Decimal, ROUND_DOWN
from public.times import Times
from init import Init
class Distribution(Init):
def __init__(self, coin):
method = "distribution"
super().__init__(coin, method)
# ------------------ 核心方法 ------------------
def score(self, alluser_mhs24h, hash_percent=1):
total_hashrate = sum(obj['mhs24h'] for obj in alluser_mhs24h)
result = {}
for item in alluser_mhs24h:
user = item['user']
mhs24h = item['mhs24h']
result[user] = round((mhs24h / total_hashrate) * hash_percent, 4)
return result
async def query_last_day_hashrate(self, date):
try:
sql = f"SELECT user, SUM(mhs24h) AS mhs24h FROM {self.coin}_mhsv2 WHERE date = ? GROUP BY user;"
data = await self.hashratedb.exec(sql, [date + " 00:00:00"])
if not data:
return False
return [{'user': item['user'], 'mhs24h': float(item['mhs24h'])} for item in data]
except Exception as err:
raise err
async def query_last_day_reward(self, start_time, end_time):
try:
sql = f"""
SELECT MAX(height) AS max_height, SUM(reward) AS reward
FROM {self.coin}_blkreportprofitv2
WHERE date >= ? AND date < ? AND state = ?;
"""
data = await self.distributiondb.exec(sql, [start_time, end_time, 1])
if not data or not data[0]['reward']:
return 0
# 返回整数,完全对齐 JS BigInt
data[0]['reward'] = int(data[0]['reward'])
return data
except Exception as err:
raise err
async def query_last_day_if_mature(self, start_time, end_time):
try:
sql = f"SELECT count(*) AS count FROM {self.coin}_blkreportprofitv2 WHERE date >= ? AND date < ? AND state = ?;"
while True:
await asyncio.sleep(60 * 15)
data = await self.distributiondb.exec(sql, [start_time, end_time, 0])
current_hour = int(Times.times()[4])
if (self.coin == "rxd" and current_hour >= 9) or (self.coin != "rxd" and current_hour >= 4):
return False
if data[0]['count'] == 0:
break
return True
except Exception as err:
raise err
async def query_users_address(self):
try:
sql = """
SELECT
a.miner_user AS 'user',
b.balance AS 'address',
b.amount AS 'amount',
b.active AS 'state',
b.min_amount AS 'min_amount'
FROM user_account_balance b
LEFT JOIN user_miner_account a ON b.ma_id = a.id
WHERE a.coin = ? AND b.status = 0;
"""
data = await self.users_addresses.exec(sql, [self.coin])
return data if data else False
except Exception as err:
raise err
async def verify_block(self, height):
try:
data = await self.node.verify_block(height, self.REPORT_ADDRESS)
if data and isinstance(data, dict):
return await self.node.block(data)
return False
except Exception as err:
raise err
async def insert_blkreportprofit(self, data):
try:
sql = f"INSERT INTO {self.coin}_blkreportprofitv2 (date, height, hash, reward, fees, state) VALUES "
values = []
for item in data:
sql += "(?,?,?,?,?,?), "
values.extend([
Times.utcTime(item['time'] * 1000),
item['height'],
item['hash'],
item['block_reward'],
item['block_fees'],
1
])
sql = sql[:-2]
await self.distributiondb.exec_transaction(sql, values)
except Exception as err:
raise err
async def check_last_data_blk(self, date):
try:
ts = int(datetime.fromisoformat(date).timestamp() * 1000) - 86400000
yMd = Times.utcTime(ts).split(" ")[0]
ymd = yMd.split("-")
table_name = f"{self.coin}_pool_blkstats_{ymd[0]}{ymd[1]}{ymd[2]}"
confirm_result = await self.pooldb.exec(f"SHOW TABLES LIKE '{table_name}';")
if not confirm_result:
print("pool_blkstats表未更新退出本次执行请手动校验")
return False
pool_data = await self.pooldb.exec(f"SELECT height FROM {table_name} WHERE DATE(date) >= ?;", [yMd])
heights = [item['height'] for item in pool_data]
blkreport_data = await self.distributiondb.exec(f"SELECT height FROM {self.coin}_blkreportprofitv2 WHERE DATE(date)=? AND state=?;", [yMd, 1])
blkreport_heights = [item['height'] for item in blkreport_data]
need_check_heights = [h for h in heights if h not in set(blkreport_heights)]
if not need_check_heights:
print(f"{self.coin}check 完成,没有需要重新校验的区块")
return True
need_insert_data = []
for height in need_check_heights:
result = await self.verify_block(height)
if result:
need_insert_data.append(result)
if need_insert_data:
await self.insert_blkreportprofit(need_insert_data)
print(f"{self.coin}check 完成,已将{self.coin}漏掉的报块全部插入blk表中")
else:
print(f"{self.coin}check 完成没有需要insert的区块")
return True
except Exception as err:
raise err
async def update_state(self, min_amount):
try:
data = await self.distributiondb.exec("SELECT user, SUM(amount) AS profit FROM wallet_in WHERE coin=? AND state=? GROUP BY user;", [self.coin, 2])
if not data:
return
for item in data:
user = item['user']
if item['profit'] >= min_amount[user]:
await self.distributiondb.exec("UPDATE wallet_in SET state=? WHERE coin=? AND user=?", [0, self.coin, user])
except Exception as err:
raise err
async def insert_wallet_in(self, data):
try:
sql = "INSERT INTO wallet_in(coin, user, address, create_date, should_out_date, max_height, amount, state) VALUES "
values = []
for item in data:
sql += "(?,?,?,?,?,?,?,?), "
values.extend([
item['coin'],
item['user'],
item['address'],
item['create_date'],
item['should_out_date'],
item['max_height'],
item['amount'],
item['state']
])
sql = sql[:-2]
await self.distributiondb.exec_transaction(sql, values)
except Exception as err:
raise err
async def main(self, start_time, end_time):
try:
if not await self.query_last_day_if_mature(start_time, end_time):
return
if not await self.check_last_data_blk(end_time):
return
last_day_mhs24h, last_day_reward, users_address = await asyncio.gather(
self.query_last_day_hashrate(end_time),
self.query_last_day_reward(start_time, end_time),
self.query_users_address()
)
if not last_day_mhs24h or not last_day_reward or not users_address:
print("查询错误", last_day_mhs24h, last_day_reward, users_address)
return
reward = (int(last_day_reward[0]['reward']) * int((1 - self.POOL_FEE) * 10000)) // 10000
score_ratio = self.score(last_day_mhs24h, 1)
max_height = last_day_reward[0]['max_height']
should_out_date = end_time
accuracy = 100000000
count = 8
if self.coin == "nexa":
should_out_date = Times.utcTime(int(datetime.fromisoformat(end_time).timestamp() * 1000) + 1000 * 60 * 60 * 24 * 7)
accuracy = 100
count = 2
elif self.coin == "rxd":
accuracy = 100
count = 2
elif self.coin == "alph":
accuracy = 0
count = 0
user_profit = 0
result = []
pool_account_address = None
min_amount = {}
for user, ratio in score_ratio.items():
ratio_int = round(ratio * 10000)
profit = reward * ratio_int // 10000
if profit == 0:
continue
user_profit += profit
for item in users_address:
if item['user'] == "pool_account":
pool_account_address = item['address']
if item['user'] == user:
min_amount[user] = item['min_amount']
state = 0
if profit >= item['amount'] and item['state'] == 0:
state = 0
elif profit < item['amount'] and item['state'] == 0:
state = 2
elif profit >= item['amount'] and item['state'] == 1:
state = 3
else:
state = 4
result.append({
'coin': self.coin,
'user': user,
'address': item['address'],
'create_date': end_time,
'should_out_date': should_out_date,
'max_height': max_height,
'amount': profit,
'state': state
})
pool_account_amount = Decimal(last_day_reward[0]['reward']) - Decimal(user_profit)
pool_account_amount = pool_account_amount.quantize(Decimal(f'1e-{count}'), rounding=ROUND_DOWN)
result.append({
'coin': self.coin,
'user': "pool_account",
'address': pool_account_address,
'create_date': end_time,
'should_out_date': should_out_date,
'max_height': max_height,
'amount': float(pool_account_amount),
'state': 0
})
print(result)
await self.insert_wallet_in(result)
await self.update_state(min_amount)
except Exception as err:
raise err
__all__ = ['Distribution']