pool uses proxy

This commit is contained in:
Dobromir Popov
2025-09-05 00:57:22 +03:00
parent fec5f35cce
commit 48060c360f
3 changed files with 183 additions and 282 deletions

View File

@@ -18,28 +18,22 @@ from requests.auth import HTTPBasicAuth
# Import web interface
from pool_web_interface import start_web_interface
class RinCoinMiningPool:
# Import stratum base class
from stratum_proxy import RinCoinStratumBase
class RinCoinMiningPool(RinCoinStratumBase):
def __init__(self, stratum_host='0.0.0.0', stratum_port=3333,
rpc_host='127.0.0.1', rpc_port=9556,
rpc_user='rinrpc', rpc_password='745ce784d5d537fc06105a1b935b7657903cfc71a5fb3b90',
pool_address='rin1qahvvv9d5f3443wtckeqavwp9950wacxfmwv20q',
pool_fee_percent=1.0):
self.stratum_host = stratum_host
self.stratum_port = stratum_port
self.rpc_host = rpc_host
self.rpc_port = rpc_port
self.rpc_user = rpc_user
self.rpc_password = rpc_password
# Initialize base class
super().__init__(stratum_host, stratum_port, rpc_host, rpc_port, rpc_user, rpc_password, pool_address)
self.pool_address = pool_address
self.pool_fee_percent = pool_fee_percent
# Miner tracking
self.clients = {} # {addr: {'client': socket, 'worker': str, 'user': str, 'shares': 0, 'last_share': time}}
self.job_counter = 0
self.current_job = None
self.running = True
# Pool statistics
self.total_shares = 0
self.total_blocks = 0
@@ -107,75 +101,26 @@ class RinCoinMiningPool:
self.db.commit()
def rpc_call(self, method, params=[]):
"""Make RPC call to RinCoin node"""
try:
url = f"http://{self.rpc_host}:{self.rpc_port}/"
headers = {'content-type': 'text/plain'}
auth = HTTPBasicAuth(self.rpc_user, self.rpc_password)
payload = {
"jsonrpc": "1.0",
"id": "mining_pool",
"method": method,
"params": params
}
response = requests.post(url, json=payload, headers=headers, auth=auth, timeout=10)
if response.status_code == 200:
result = response.json()
if 'error' in result and result['error'] is not None:
print(f"RPC Error: {result['error']}")
return None
return result.get('result')
else:
print(f"HTTP Error: {response.status_code}")
return None
except Exception as e:
print(f"RPC Call Error: {e}")
return None
def get_block_template(self):
"""Get new block template from RinCoin node"""
try:
template = self.rpc_call("getblocktemplate", [{"rules": ["segwit", "mweb"]}])
if template:
self.job_counter += 1
job = {
"job_id": f"job_{self.job_counter}",
"template": template,
"prevhash": template.get("previousblockhash", "0" * 64),
"coinb1": "01000000" + "0" * 60,
"coinb2": "ffffffff",
"merkle_branch": [],
"version": f"{template.get('version', 1):08x}",
"nbits": template.get("bits", "1d00ffff"),
"ntime": f"{int(time.time()):08x}",
"clean_jobs": True,
"target": template.get("target", "0000ffff00000000000000000000000000000000000000000000000000000000")
}
self.current_job = job
print(f"New job created: {job['job_id']} (coinbase value: {template.get('coinbasevalue', 0)} satoshis)")
return job
return None
except Exception as e:
print(f"Get block template error: {e}")
return None
def get_pool_block_template(self):
"""Get new block template and create pool-style job"""
template = super().get_block_template()
if template:
# Convert to pool-style job format if needed
job = self.current_job
if job:
# Add pool-specific fields
job["coinb1"] = "01000000" + "0" * 60
job["coinb2"] = "ffffffff"
job["merkle_branch"] = []
job["clean_jobs"] = True
return job
return None
def validate_rincoin_address(self, address):
"""Validate if an address is a valid RinCoin address"""
if not address or not address.startswith('rin'):
return False
try:
result = self.rpc_call("validateaddress", [address])
return result and result.get('isvalid', False)
except Exception as e:
print(f"Address validation error: {e}")
return self.decode_bech32_address(address) is not None
except:
return False
def register_miner(self, user, worker, address=None):
@@ -297,33 +242,7 @@ class RinCoinMiningPool:
if miners_without_addresses:
print(f"📊 Summary: {len(miners_with_addresses)} miners with addresses, {len(miners_without_addresses)} without (rewards to pool)")
def send_stratum_response(self, client, msg_id, result, error=None):
"""Send Stratum response to client"""
try:
response = {
"id": msg_id,
"result": result,
"error": error
}
message = json.dumps(response) + "\n"
client.send(message.encode('utf-8'))
except Exception as e:
print(f"Send response error: {e}")
def send_stratum_notification(self, client, method, params):
"""Send Stratum notification to client"""
try:
notification = {
"id": None,
"method": method,
"params": params
}
message = json.dumps(notification) + "\n"
client.send(message.encode('utf-8'))
except Exception as e:
print(f"Send notification error: {e}")
# Use inherited send_stratum_response and send_stratum_notification from base class
def handle_stratum_message(self, client, addr, message):
"""Handle incoming Stratum message from miner"""
@@ -347,7 +266,7 @@ class RinCoinMiningPool:
self.send_stratum_notification(client, "mining.set_difficulty", [0.0001])
# Send initial job
if self.get_block_template():
if self.get_pool_block_template():
job = self.current_job
self.send_stratum_notification(client, "mining.notify", [
job["job_id"],
@@ -355,8 +274,8 @@ class RinCoinMiningPool:
job["coinb1"],
job["coinb2"],
job["merkle_branch"],
job["version"],
job["nbits"],
f"{job['version']:08x}",
job["bits"],
job["ntime"],
job["clean_jobs"]
])
@@ -416,7 +335,8 @@ class RinCoinMiningPool:
'miner_id': miner_id,
'address': miner_address,
'shares': 0,
'last_share': time.time()
'last_share': time.time(),
'extranonce1': '00000000' # Default extranonce1
}
if miner_address:
@@ -432,101 +352,70 @@ class RinCoinMiningPool:
if addr not in self.clients:
self.send_stratum_response(client, msg_id, False, "Not authorized")
return
miner_info = self.clients[addr]
try:
if self.current_job and len(params) >= 5:
job_id = params[0]
extranonce2 = params[1]
ntime = params[2]
nonce = params[3]
username = params[0]
job_id = params[1]
extranonce2 = params[2]
ntime = params[3]
nonce = params[4]
# Calculate actual difficulty from the share submission
# The miner reports its hashrate, so we need to calculate
# the difficulty that would match that hashrate
# For a miner reporting ~381 kH/s, we need to calculate
# the difficulty that would result in that hashrate
# H = D * 2^32 / dt
# D = H * dt / 2^32
# If miner reports 381 kH/s and submits every ~15 seconds:
# D = 381000 * 15 / 2^32 ≈ 0.00133
actual_difficulty = 0.00133 # Calculated to match ~381 kH/s
# Use base class to validate and submit share
extranonce1 = miner_info.get('extranonce1', '00000000')
miner_address = miner_info.get('address')
# Record share with calculated difficulty
self.record_share(miner_info['miner_id'], job_id, actual_difficulty)
# For pool mining, always mine to pool address
success, message = self.submit_share(
self.current_job, extranonce1, extranonce2, ntime, nonce,
target_address=self.pool_address
)
# Calculate instantaneous hashrate based on time between shares
now_ts = time.time()
prev_ts = miner_info.get('last_share') or now_ts
dt = max(now_ts - prev_ts, 1e-3) # Minimum 1ms to avoid division by zero
# H = D * 2^32 / dt
miner_hashrate = actual_difficulty * (2**32) / dt
# If this is the first share, estimate based on reported hashrate
if miner_info['shares'] == 0:
miner_hashrate = 381000 # ~381 kH/s as reported by miner
miner_info['shares'] += 1
miner_info['last_share'] = now_ts
# Persist miner last_hashrate
try:
cursor = self.db.cursor()
cursor.execute('UPDATE miners SET last_share = CURRENT_TIMESTAMP, last_hashrate = ? WHERE id = ?', (miner_hashrate, miner_info['miner_id']))
self.db.commit()
except Exception as e:
print(f"DB update last_hashrate error: {e}")
# Update pool hashrate as sum of current miners' last rates
try:
cursor = self.db.cursor()
cursor.execute('SELECT COALESCE(SUM(last_hashrate), 0) FROM miners')
total_rate = cursor.fetchone()[0] or 0.0
self.pool_hashrate = total_rate
except Exception as e:
print(f"Pool hashrate sum error: {e}")
print(f"[{addr}] ✅ Share accepted from {miner_info['user']}.{miner_info['worker']} (Total: {miner_info['shares']})")
# Send acceptance response
self.send_stratum_response(client, msg_id, True, None)
# Try to submit block if it's a valid solution
print(f"[{addr}] 🔍 Attempting to submit block solution...")
# Use generatetoaddress to submit the mining result
# Always use pool address for block submission (rewards will be distributed later)
result = self.rpc_call("generatetoaddress", [1, self.pool_address, 1])
if result and len(result) > 0:
block_hash = result[0]
if success:
# Record share with estimated difficulty
actual_difficulty = 0.00133 # Estimated for ~381 kH/s
self.record_share(miner_info['miner_id'], job_id, actual_difficulty)
# Get block info
block_info = self.rpc_call("getblock", [block_hash])
if block_info:
block_height = block_info.get('height', 0)
coinbase_tx = block_info.get('tx', [])[0] if block_info.get('tx') else None
# Get coinbase value (simplified)
total_reward = 50.0 # Default block reward
print(f"🎉 [{addr}] BLOCK FOUND! Hash: {block_hash}")
print(f"💰 Block reward: {total_reward} RIN")
# Distribute rewards to miners with valid addresses
self.distribute_block_reward(block_hash, block_height, total_reward)
# Update miner stats
now_ts = time.time()
prev_ts = miner_info.get('last_share') or now_ts
dt = max(now_ts - prev_ts, 1e-3)
miner_hashrate = actual_difficulty * (2**32) / dt
if miner_info['shares'] == 0:
miner_hashrate = 381000 # Default estimate
miner_info['shares'] += 1
miner_info['last_share'] = now_ts
# Update database
try:
cursor = self.db.cursor()
cursor.execute('UPDATE miners SET last_share = CURRENT_TIMESTAMP, last_hashrate = ? WHERE id = ?',
(miner_hashrate, miner_info['miner_id']))
self.db.commit()
except Exception as e:
print(f"DB update error: {e}")
print(f"[{addr}] ✅ Share accepted from {miner_info['user']}.{miner_info['worker']} (Total: {miner_info['shares']})")
self.send_stratum_response(client, msg_id, True)
# If block was found, distribute rewards
if "Block found" in message:
print(f"🎉 [{addr}] BLOCK FOUND!")
# Get block info and distribute rewards
total_reward = self.current_job['coinbasevalue'] / 100000000 if self.current_job else 25.0
self.distribute_block_reward("pending", self.current_job['height'] if self.current_job else 0, total_reward)
else:
# Accept as share even if not a block
# Accept as share for pool statistics even if block validation fails
self.send_stratum_response(client, msg_id, True)
else:
print(f"[{addr}] Invalid share parameters")
self.send_stratum_response(client, msg_id, False, "Invalid parameters")
except Exception as e:
print(f"[{addr}] Block submission error: {e}")
print(f"[{addr}] Share processing error: {e}")
# Still accept the share for mining statistics
self.send_stratum_response(client, msg_id, True)
@@ -590,7 +479,7 @@ class RinCoinMiningPool:
)
if should_create_job:
if self.get_block_template():
if self.get_pool_block_template():
job = self.current_job
last_job_time = time.time()
last_block_height = current_height
@@ -606,8 +495,8 @@ class RinCoinMiningPool:
job["coinb1"],
job["coinb2"],
job["merkle_branch"],
job["version"],
job["nbits"],
f"{job['version']:08x}",
job["bits"],
job["ntime"],
job["clean_jobs"]
])