265 lines
11 KiB
Python
265 lines
11 KiB
Python
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'] |