js代码改写py
This commit is contained in:
120
m2pool_backend_app/lib/mysql.py
Normal file
120
m2pool_backend_app/lib/mysql.py
Normal file
@@ -0,0 +1,120 @@
|
||||
import aiomysql
|
||||
|
||||
class DBPool:
|
||||
def __init__(self, coin, options):
|
||||
self.coin = coin
|
||||
self.pool = None
|
||||
self.options = options
|
||||
|
||||
async def _get_pool(self):
|
||||
if self.pool is None:
|
||||
self.pool = await aiomysql.create_pool(**self.options)
|
||||
return self.pool
|
||||
|
||||
# -------------------------
|
||||
# 工具:判断是否 SELECT
|
||||
# -------------------------
|
||||
def _is_select(self, sql: str) -> bool:
|
||||
return sql.lstrip().lower().startswith("select")
|
||||
|
||||
# -------------------------
|
||||
# 非事务 SQL
|
||||
# -------------------------
|
||||
async def exec(self, sql, values=None):
|
||||
if values is None:
|
||||
values = []
|
||||
|
||||
pool = await self._get_pool()
|
||||
async with pool.acquire() as conn:
|
||||
async with conn.cursor(aiomysql.DictCursor) as cur:
|
||||
await cur.execute(sql, values)
|
||||
|
||||
if self._is_select(sql):
|
||||
# JS: SELECT 返回 rows
|
||||
return await cur.fetchall()
|
||||
else:
|
||||
# JS: 非 SELECT 返回 OkPacket
|
||||
return {
|
||||
"affectedRows": cur.rowcount,
|
||||
"insertId": cur.lastrowid,
|
||||
}
|
||||
|
||||
# -------------------------
|
||||
# 单 SQL 事务
|
||||
# -------------------------
|
||||
async def exec_transaction(self, sql, values=None):
|
||||
if values is None:
|
||||
values = []
|
||||
|
||||
pool = await self._get_pool()
|
||||
async with pool.acquire() as conn:
|
||||
async with conn.cursor(aiomysql.DictCursor) as cur:
|
||||
await conn.begin()
|
||||
try:
|
||||
await cur.execute(sql, values)
|
||||
|
||||
if self._is_select(sql):
|
||||
result = await cur.fetchall()
|
||||
else:
|
||||
result = {
|
||||
"affectedRows": cur.rowcount,
|
||||
"insertId": cur.lastrowid,
|
||||
}
|
||||
|
||||
await conn.commit()
|
||||
return result
|
||||
except Exception:
|
||||
await conn.rollback()
|
||||
raise
|
||||
|
||||
# -------------------------
|
||||
# 多 SQL 合并事务
|
||||
# -------------------------
|
||||
async def exec_transaction_together(self, params):
|
||||
pool = await self._get_pool()
|
||||
async with pool.acquire() as conn:
|
||||
async with conn.cursor() as cur:
|
||||
await conn.begin()
|
||||
try:
|
||||
for item in params:
|
||||
sql = item["sql"]
|
||||
param = item.get("param", [])
|
||||
await cur.execute(sql, param)
|
||||
await conn.commit()
|
||||
except Exception:
|
||||
await conn.rollback()
|
||||
raise
|
||||
|
||||
# -------------------------
|
||||
# 写锁事务
|
||||
# -------------------------
|
||||
async def exec_write_lock(self, sql, values=None, table_name=None):
|
||||
if values is None:
|
||||
values = []
|
||||
|
||||
pool = await self._get_pool()
|
||||
async with pool.acquire() as conn:
|
||||
async with conn.cursor(aiomysql.DictCursor) as cur:
|
||||
await conn.begin()
|
||||
try:
|
||||
await cur.execute(f"LOCK TABLES {table_name} WRITE")
|
||||
await cur.execute(sql, values)
|
||||
|
||||
if self._is_select(sql):
|
||||
result = await cur.fetchall()
|
||||
else:
|
||||
result = {
|
||||
"affectedRows": cur.rowcount,
|
||||
"insertId": cur.lastrowid,
|
||||
}
|
||||
|
||||
await conn.commit()
|
||||
return result
|
||||
except Exception:
|
||||
await conn.rollback()
|
||||
raise
|
||||
finally:
|
||||
# 对齐 JS:始终解锁
|
||||
await conn.cursor().execute("UNLOCK TABLES")
|
||||
|
||||
__all__ = ["DBPool"]
|
||||
298
m2pool_backend_app/lib/node.py
Normal file
298
m2pool_backend_app/lib/node.py
Normal file
@@ -0,0 +1,298 @@
|
||||
import httpx
|
||||
|
||||
|
||||
class BaseRPCNode:
|
||||
def __init__(self, NODE_OPTION):
|
||||
self.rpcUser = NODE_OPTION.get("rpcUser")
|
||||
self.rpcHost = NODE_OPTION.get("rpcHost")
|
||||
self.rpcPassword = NODE_OPTION.get("rpcPassword")
|
||||
self.rpcPort = NODE_OPTION.get("rpcPort")
|
||||
|
||||
self.base_url = f"http://{self.rpcHost}:{self.rpcPort}"
|
||||
|
||||
self.client = httpx.AsyncClient(
|
||||
base_url=self.base_url,
|
||||
auth=(self.rpcUser, self.rpcPassword),
|
||||
timeout=5.0
|
||||
)
|
||||
|
||||
async def callRpcMethod(self, method, params=None):
|
||||
if params is None:
|
||||
params = []
|
||||
try:
|
||||
response = await self.client.post("/", json={
|
||||
"jsonrpc": "1.0",
|
||||
"id": "testnet",
|
||||
"method": method,
|
||||
"params": params
|
||||
})
|
||||
response.raise_for_status()
|
||||
return response.json()["result"]
|
||||
|
||||
except httpx.HTTPStatusError as e:
|
||||
print("RPC Error:", e.response.text)
|
||||
raise
|
||||
except Exception as e:
|
||||
print("RPC Error:", str(e))
|
||||
raise
|
||||
|
||||
async def getblockcount(self):
|
||||
return await self.callRpcMethod("getblockcount", [])
|
||||
|
||||
async def getblockhash(self, height):
|
||||
return await self.callRpcMethod("getblockhash", [height])
|
||||
|
||||
async def getblock(self, param):
|
||||
if isinstance(param, str):
|
||||
return await self.callRpcMethod("getblock", [param, 2])
|
||||
elif isinstance(param, int):
|
||||
hash_ = await self.getblockhash(param)
|
||||
return await self.callRpcMethod("getblock", [hash_, 2])
|
||||
else:
|
||||
raise ValueError("param must be str or int")
|
||||
|
||||
|
||||
# ================= NEXA =================
|
||||
class NEXARPCNode(BaseRPCNode):
|
||||
|
||||
async def verify_wallet(self, address):
|
||||
try:
|
||||
await self.callRpcMethod("getreceivedbyaddress", [address])
|
||||
return True
|
||||
except Exception:
|
||||
return False
|
||||
|
||||
async def getblock(self, height):
|
||||
return await self.callRpcMethod("getblock", [height, 2])
|
||||
|
||||
async def verify_block(self, height, address):
|
||||
block_data = await self.getblock(height)
|
||||
|
||||
for item in block_data["tx"]:
|
||||
if len(item["vin"]) == 0:
|
||||
addresses = item["vout"][0]["scriptPubKey"]["addresses"]
|
||||
if address == addresses[0]:
|
||||
return block_data
|
||||
else:
|
||||
return False
|
||||
|
||||
def block(self, block_data):
|
||||
tx = block_data["tx"]
|
||||
time = block_data["time"]
|
||||
hash_ = block_data["hash"]
|
||||
height = block_data["height"]
|
||||
|
||||
block_fees = 0
|
||||
block_reward = 0
|
||||
|
||||
for item in tx:
|
||||
if len(item["vin"]) == 0:
|
||||
block_reward = item["sends"]
|
||||
else:
|
||||
block_fees += item.get("fee", 0)
|
||||
|
||||
return {
|
||||
"height": height,
|
||||
"hash": hash_,
|
||||
"time": time,
|
||||
"block_reward": block_reward,
|
||||
"block_fees": block_fees
|
||||
}
|
||||
|
||||
|
||||
# ================= GRS =================
|
||||
class GRSRPCNode(BaseRPCNode):
|
||||
|
||||
async def verify_block(self, height, REPORT_ADDRESS):
|
||||
block_data = await self.getblock(height)
|
||||
|
||||
for item in block_data["tx"]:
|
||||
vin = item["vin"]
|
||||
vout = item["vout"]
|
||||
|
||||
if vin[0].get("coinbase"):
|
||||
for value in vout:
|
||||
addr = value["scriptPubKey"].get("address")
|
||||
if addr:
|
||||
if addr == REPORT_ADDRESS:
|
||||
return block_data
|
||||
else:
|
||||
return False
|
||||
|
||||
return False
|
||||
|
||||
def block(self, data):
|
||||
hash_ = data["hash"]
|
||||
tx = data["tx"]
|
||||
height = data["height"]
|
||||
time = data["time"]
|
||||
|
||||
reward = 0
|
||||
fees = 0
|
||||
|
||||
for item in tx:
|
||||
vin = item["vin"]
|
||||
vout = item["vout"]
|
||||
|
||||
if vin[0].get("coinbase"):
|
||||
for value in vout:
|
||||
reward += value["value"]
|
||||
else:
|
||||
fees += item.get("fee", 0)
|
||||
|
||||
return {
|
||||
"height": height,
|
||||
"hash": hash_,
|
||||
"time": time,
|
||||
"block_reward": reward,
|
||||
"block_fees": fees
|
||||
}
|
||||
|
||||
|
||||
# ================= MONA =================
|
||||
class MONARPCNode(BaseRPCNode):
|
||||
|
||||
async def verify_block(self, height, REPORT_ADDRESS):
|
||||
block_data = await self.getblock(height)
|
||||
|
||||
for item in block_data["tx"]:
|
||||
vin = item["vin"]
|
||||
vout = item["vout"]
|
||||
|
||||
if vin[0].get("coinbase"):
|
||||
for value in vout:
|
||||
addresses = value["scriptPubKey"].get("addresses")
|
||||
if addresses:
|
||||
first = addresses.get("0") if isinstance(addresses, dict) else addresses[0]
|
||||
|
||||
if first == REPORT_ADDRESS:
|
||||
return block_data
|
||||
else:
|
||||
return False
|
||||
|
||||
return False
|
||||
|
||||
def block(self, data):
|
||||
hash_ = data["hash"]
|
||||
tx = data["tx"]
|
||||
height = data["height"]
|
||||
time = data["time"]
|
||||
|
||||
reward = 0
|
||||
|
||||
for item in tx:
|
||||
vin = item["vin"]
|
||||
vout = item["vout"]
|
||||
|
||||
if vin[0].get("coinbase"):
|
||||
for value in vout:
|
||||
if value["scriptPubKey"].get("addresses"):
|
||||
reward += value["value"]
|
||||
|
||||
return {
|
||||
"height": height,
|
||||
"hash": hash_,
|
||||
"time": time,
|
||||
"block_reward": reward,
|
||||
"block_fees": None
|
||||
}
|
||||
|
||||
|
||||
# ================= DGB =================
|
||||
class DGBRPCNode(BaseRPCNode):
|
||||
|
||||
async def verify_block(self, height, REPORT_ADDRESS):
|
||||
block_data = await self.getblock(height)
|
||||
|
||||
for item in block_data["tx"]:
|
||||
vin = item["vin"]
|
||||
vout = item["vout"]
|
||||
|
||||
if vin[0].get("coinbase"):
|
||||
for value in vout:
|
||||
addresses = value["scriptPubKey"].get("addresses")
|
||||
if addresses and addresses[0] == REPORT_ADDRESS:
|
||||
return block_data
|
||||
|
||||
return False
|
||||
|
||||
async def verify_block_with_algo(self, height, REPORT_ADDRESS, algorithm):
|
||||
block_data = await self.getblock(height)
|
||||
|
||||
if block_data.get("pow_algo") == algorithm:
|
||||
for item in block_data["tx"]:
|
||||
vin = item["vin"]
|
||||
vout = item["vout"]
|
||||
|
||||
if vin[0].get("coinbase"):
|
||||
for value in vout:
|
||||
addresses = value["scriptPubKey"].get("addresses")
|
||||
if addresses and addresses[0] == REPORT_ADDRESS:
|
||||
return block_data
|
||||
|
||||
return False
|
||||
|
||||
def block(self, data):
|
||||
hash_ = data["hash"]
|
||||
tx = data["tx"]
|
||||
height = data["height"]
|
||||
time = data["time"]
|
||||
|
||||
reward = 0
|
||||
|
||||
for item in tx:
|
||||
vin = item["vin"]
|
||||
vout = item["vout"]
|
||||
|
||||
if vin[0].get("coinbase"):
|
||||
for value in vout:
|
||||
if value["scriptPubKey"].get("addresses"):
|
||||
reward += value["value"]
|
||||
|
||||
return {
|
||||
"height": height,
|
||||
"hash": hash_,
|
||||
"time": time,
|
||||
"block_reward": reward,
|
||||
"block_fees": None
|
||||
}
|
||||
|
||||
|
||||
# ================= RXD =================
|
||||
class RXDRPCNode(BaseRPCNode):
|
||||
|
||||
async def verify_block(self, height, REPORT_ADDRESS):
|
||||
block_data = await self.getblock(height)
|
||||
|
||||
for item in block_data["tx"]:
|
||||
vin = item["vin"]
|
||||
vout = item["vout"]
|
||||
|
||||
if vin[0].get("coinbase"):
|
||||
for value in vout:
|
||||
addresses = value["scriptPubKey"].get("addresses")
|
||||
if addresses and addresses[0] == REPORT_ADDRESS:
|
||||
hash_ = await self.getblockhash(height)
|
||||
blockstats = await self.callRpcMethod("getblockstats", [hash_])
|
||||
|
||||
return {
|
||||
"height": blockstats["height"],
|
||||
"hash": blockstats["blockhash"],
|
||||
"time": blockstats["time"],
|
||||
"block_reward": blockstats["subsidy"] + blockstats["totalfee"],
|
||||
"block_fees": blockstats["totalfee"]
|
||||
}
|
||||
|
||||
return False
|
||||
|
||||
def block(self, data):
|
||||
return data
|
||||
|
||||
|
||||
__all__ = [
|
||||
"NEXARPCNode",
|
||||
"GRSRPCNode",
|
||||
"MONARPCNode",
|
||||
"DGBRPCNode",
|
||||
"RXDRPCNode"
|
||||
]
|
||||
40
m2pool_backend_app/lib/redis.py
Normal file
40
m2pool_backend_app/lib/redis.py
Normal file
@@ -0,0 +1,40 @@
|
||||
import json
|
||||
import redis.asyncio as redis_async
|
||||
|
||||
|
||||
class Cache:
|
||||
def __init__(self, options):
|
||||
if isinstance(options, str):
|
||||
self.redis = redis_async.from_url(options, decode_responses=True)
|
||||
|
||||
elif isinstance(options, dict):
|
||||
host = options.get("host", "localhost")
|
||||
port = options.get("port", 6379)
|
||||
db = options.get("db", 0)
|
||||
password = options.get("password")
|
||||
|
||||
if password:
|
||||
url = f"redis://:{password}@{host}:{port}/{db}"
|
||||
else:
|
||||
url = f"redis://{host}:{port}/{db}"
|
||||
|
||||
self.redis = redis_async.from_url(url, decode_responses=True)
|
||||
|
||||
else:
|
||||
raise TypeError("options must be dict or redis url string")
|
||||
|
||||
async def set(self, key, value):
|
||||
try:
|
||||
await self.redis.set(key, value)
|
||||
except Exception as err:
|
||||
raise err
|
||||
|
||||
async def get(self, key):
|
||||
try:
|
||||
data = await self.redis.get(key)
|
||||
return json.loads(data)
|
||||
except Exception as err:
|
||||
raise err
|
||||
|
||||
|
||||
__all__ = ["Cache"]
|
||||
Reference in New Issue
Block a user