Files
m2pool_docs/m2pool_backend_app/src/distribution.py

265 lines
11 KiB
Python
Raw Normal View History

2026-01-30 17:47:21 +08:00
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']