pool uses proxy
This commit is contained in:
@@ -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"]
|
||||
])
|
||||
|
Reference in New Issue
Block a user