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

@@ -31,22 +31,22 @@ python3 -c "import requests" 2>/dev/null || {
echo "✅ Python dependencies ready"
# Check if port 3333 is already in use
if netstat -tln | grep -q ":3333 "; then
# Check if port 3334 is already in use
if netstat -tln | grep -q ":3334 "; then
echo ""
echo "⚠️ Port 3333 is already in use!"
echo "⚠️ Port 3334 is already in use!"
echo ""
echo "🔍 Process using port 3333:"
sudo netstat -tlnp | grep ":3333 " || echo "Could not determine process"
echo "🔍 Process using port 3334:"
sudo netstat -tlnp | grep ":3334 " || echo "Could not determine process"
echo ""
echo "🛑 To kill existing process:"
echo "sudo lsof -ti:3333 | xargs sudo kill -9"
echo "sudo lsof -ti:3334 | xargs sudo kill -9"
echo ""
read -p "Kill existing process and continue? (y/N): " -n 1 -r
echo
if [[ $REPLY =~ ^[Yy]$ ]]; then
echo "Killing processes using port 3333..."
sudo lsof -ti:3333 | xargs sudo kill -9 2>/dev/null || echo "No processes to kill"
echo "Killing processes using port 3334..."
sudo lsof -ti:3334 | xargs sudo kill -9 2>/dev/null || echo "No processes to kill"
sleep 2
else
echo "Exiting..."
@@ -59,7 +59,7 @@ echo "🚀 Starting Stratum Proxy Server..."
echo "This will bridge cpuminer-opt-rin to your RinCoin node"
echo ""
echo "After it starts, connect your miner with:"
echo "sudo docker exec -it amd-strix-halo-llama-rocm bash -c \"/mnt/dl/rinhash/cpuminer-opt-rin/cpuminer -a rinhash -o stratum+tcp://127.0.0.1:3333 -u user -p pass -t 28\""
echo "sudo docker exec -it amd-strix-halo-llama-rocm bash -c \"/mnt/dl/rinhash/cpuminer-opt-rin/cpuminer -a rinhash -o stratum+tcp://127.0.0.1:3334 -u user -p pass -t 28\""
echo ""
echo "Press Ctrl+C to stop the proxy"
echo ""

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"]}])
def get_pool_block_template(self):
"""Get new block template and create pool-style job"""
template = super().get_block_template()
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)")
# 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
except Exception as e:
print(f"Get block template error: {e}")
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:
@@ -437,96 +357,65 @@ class RinCoinMiningPool:
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
# 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
)
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)
# Calculate instantaneous hashrate based on time between shares
# 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) # Minimum 1ms to avoid division by zero
# H = D * 2^32 / dt
dt = max(now_ts - prev_ts, 1e-3)
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_hashrate = 381000 # Default estimate
miner_info['shares'] += 1
miner_info['last_share'] = now_ts
# Persist miner last_hashrate
# 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']))
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"DB update 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]
# 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)
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"]
])

View File

@@ -13,7 +13,7 @@ import hashlib
import struct
from requests.auth import HTTPBasicAuth
class RinCoinStratumProxy:
class RinCoinStratumBase:
def __init__(self, stratum_host='0.0.0.0', stratum_port=3334,
rpc_host='127.0.0.1', rpc_port=9556,
rpc_user='rinrpc', rpc_password='745ce784d5d537fc06105a1b935b7657903cfc71a5fb3b90',
@@ -82,93 +82,92 @@ class RinCoinStratumProxy:
def decode_bech32_address(self, address):
"""Decode RinCoin bech32 address to script"""
try:
if not address.startswith('rin1'):
if not address or not address.startswith('rin1'):
raise ValueError("Not a RinCoin bech32 address")
# Use generatetoaddress to get a proper script for this address
# This ensures we create valid outputs that can actually be spent
result = self.rpc_call("validateaddress", [address])
if result and result.get('isvalid') and 'scriptPubKey' in result:
script_hex = result['scriptPubKey']
if not result or not result.get('isvalid'):
raise ValueError("Address not valid per node")
script_hex = result.get('scriptPubKey')
if not script_hex:
raise ValueError("Node did not return scriptPubKey")
return bytes.fromhex(script_hex)
else:
# Fallback: create a basic P2WPKH script if RPC validation fails
# Extract the witness program (assuming it's standard 20-byte)
# This is a simplified approach - in production use a proper bech32 library
print(f"Warning: Using fallback script for {address}")
return bytes([0x00, 0x14]) + bytes(20) # OP_0 + 20 zero bytes
except Exception as e:
print(f"Address decode error: {e}")
# Emergency fallback - this should never happen in production
return bytes([0x00, 0x14]) + bytes(20)
return None
def build_coinbase_transaction(self, template, extranonce1, extranonce2):
"""Build complete coinbase transaction"""
"""Build coinbase transaction variants (with and without witness) for default address"""
return self.build_coinbase_transaction_for_address(template, extranonce1, extranonce2, self.target_address)
def build_coinbase_transaction_for_address(self, template, extranonce1, extranonce2, target_address):
"""Build coinbase transaction variants (with and without witness)"""
try:
coinbase = b''
# Version
coinbase += struct.pack('<I', 1)
# Check if we need segwit format based on template
has_witness_commitment = template.get('default_witness_commitment') is not None
if has_witness_commitment:
# Segwit marker and flag (only if witness commitment present)
coinbase += b'\x00\x01'
# Common parts
value = template.get('coinbasevalue', 0)
script_pubkey = self.decode_bech32_address(target_address)
if not script_pubkey:
return None, None
witness_commitment = template.get('default_witness_commitment')
# Input count
coinbase += b'\x01'
# Coinbase input
coinbase += b'\x00' * 32 # prev hash (null)
coinbase += b'\xff\xff\xff\xff' # prev index
# Script sig (height + extranonces + signature)
# ScriptSig (block height minimal push + tag + extranonces)
height = template.get('height', 0)
height_bytes = struct.pack('<I', height)
height_compact = bytes([len(height_bytes.rstrip(b'\x00'))]) + height_bytes.rstrip(b'\x00')
scriptsig = height_compact + b'/RinCoin/' + extranonce1.encode() + extranonce2.encode()
coinbase += self.encode_varint(len(scriptsig)) + scriptsig
# Sequence
coinbase += b'\xff\xff\xff\xff'
# Prepare outputs
outputs = []
# Main output to mining address
value = template.get('coinbasevalue', 0)
script_pubkey = self.decode_bech32_address(self.target_address)
outputs.append(struct.pack('<Q', value) + self.encode_varint(len(script_pubkey)) + script_pubkey)
# Witness commitment output if present
witness_commitment = template.get('default_witness_commitment')
# Helper to build outputs blob
def build_outputs_blob() -> bytes:
outputs_blob = b''
outputs_list = []
# Main output
outputs_list.append(struct.pack('<Q', value) + self.encode_varint(len(script_pubkey)) + script_pubkey)
# Witness commitment OP_RETURN output if present
if witness_commitment:
commit_script = bytes.fromhex(witness_commitment)
outputs.append(struct.pack('<Q', 0) + self.encode_varint(len(commit_script)) + commit_script)
outputs_list.append(struct.pack('<Q', 0) + self.encode_varint(len(commit_script)) + commit_script)
outputs_blob += self.encode_varint(len(outputs_list))
for out in outputs_list:
outputs_blob += out
return outputs_blob
# Add outputs
coinbase += self.encode_varint(len(outputs))
for output in outputs:
coinbase += output
# Build non-witness serialization (txid serialization)
cb_nowit = b''
cb_nowit += struct.pack('<I', 1) # version
cb_nowit += b'\x01' # input count
cb_nowit += b'\x00' * 32 # prevout hash
cb_nowit += b'\xff\xff\xff\xff' # prevout index
cb_nowit += self.encode_varint(len(scriptsig)) + scriptsig
cb_nowit += b'\xff\xff\xff\xff' # sequence
cb_nowit += build_outputs_blob() # outputs
cb_nowit += struct.pack('<I', 0) # locktime
# Witness data (only if segwit format)
# Build with-witness serialization (block serialization)
if has_witness_commitment:
coinbase += b'\x01' # witness stack count for input
coinbase += b'\x20' # witness item length (32 bytes)
coinbase += b'\x00' * 32 # witness nonce
cb_wit = b''
cb_wit += struct.pack('<I', 1) # version
cb_wit += b'\x00\x01' # segwit marker+flag
cb_wit += b'\x01' # input count
cb_wit += b'\x00' * 32 # prevout hash
cb_wit += b'\xff\xff\xff\xff' # prevout index
cb_wit += self.encode_varint(len(scriptsig)) + scriptsig
cb_wit += b'\xff\xff\xff\xff' # sequence
cb_wit += build_outputs_blob() # outputs
# Witness stack for coinbase (32-byte reserved value)
cb_wit += b'\x01' # witness stack count
cb_wit += b'\x20' # item length
cb_wit += b'\x00' * 32 # reserved value
cb_wit += struct.pack('<I', 0) # locktime
else:
cb_wit = cb_nowit
# Locktime
coinbase += struct.pack('<I', 0)
return coinbase
return cb_wit, cb_nowit
except Exception as e:
print(f"Coinbase construction error: {e}")
return None
return None, None
def calculate_merkle_root(self, coinbase_txid, transactions):
"""Calculate merkle root with coinbase at index 0"""
@@ -237,21 +236,25 @@ class RinCoinStratumProxy:
print(f"Get block template error: {e}")
return None
def submit_share(self, job, extranonce1, extranonce2, ntime, nonce):
def submit_share(self, job, extranonce1, extranonce2, ntime, nonce, target_address=None):
"""Validate share and submit block if valid"""
try:
print(f"Share: job={job['job_id']} nonce={nonce}")
# Build coinbase transaction
coinbase = self.build_coinbase_transaction(job['template'], extranonce1, extranonce2)
if not coinbase:
# Use provided address or default
address = target_address or self.target_address
# Build coinbase (with and without witness)
coinbase_wit, coinbase_nowit = self.build_coinbase_transaction_for_address(
job['template'], extranonce1, extranonce2, address)
if not coinbase_wit or not coinbase_nowit:
return False, "Coinbase construction failed"
# Calculate coinbase transaction hash
coinbase_hash = hashlib.sha256(hashlib.sha256(coinbase).digest()).digest()[::-1]
# Calculate coinbase txid (non-witness serialization)
coinbase_txid = hashlib.sha256(hashlib.sha256(coinbase_nowit).digest()).digest()[::-1]
# Calculate merkle root
merkle_root = self.calculate_merkle_root(coinbase_hash, job['transactions'])
merkle_root = self.calculate_merkle_root(coinbase_txid, job['transactions'])
# Build block header
header = b''
@@ -284,8 +287,8 @@ class RinCoinStratumProxy:
tx_count = 1 + len(job['transactions'])
block += self.encode_varint(tx_count)
# Add coinbase transaction
block += coinbase
# Add coinbase transaction (witness variant for block body)
block += coinbase_wit
# Add other transactions
for tx in job['transactions']:
@@ -299,7 +302,7 @@ class RinCoinStratumProxy:
if result is None:
print(f"✅ Block accepted: {block_hash_hex}")
print(f"💰 Reward: {job['coinbasevalue']/100000000:.2f} RIN -> {self.target_address}")
print(f"💰 Reward: {job['coinbasevalue']/100000000:.2f} RIN -> {address}")
print(f"📊 Block height: {job['height']}")
return True, "Block found and submitted"
else:
@@ -578,6 +581,15 @@ class RinCoinStratumProxy:
finally:
print("Server stopped")
class RinCoinStratumProxy(RinCoinStratumBase):
"""Solo mining stratum proxy implementation"""
def __init__(self, stratum_host='0.0.0.0', stratum_port=3334,
rpc_host='127.0.0.1', rpc_port=9556,
rpc_user='rinrpc', rpc_password='745ce784d5d537fc06105a1b935b7657903cfc71a5fb3b90',
target_address='rin1qahvvv9d5f3443wtckeqavwp9950wacxfmwv20q'):
super().__init__(stratum_host, stratum_port, rpc_host, rpc_port, rpc_user, rpc_password, target_address)
if __name__ == "__main__":
proxy = RinCoinStratumProxy()
proxy.start()