pool uses proxy
This commit is contained in:
@@ -31,22 +31,22 @@ python3 -c "import requests" 2>/dev/null || {
|
|||||||
|
|
||||||
echo "✅ Python dependencies ready"
|
echo "✅ Python dependencies ready"
|
||||||
|
|
||||||
# Check if port 3333 is already in use
|
# Check if port 3334 is already in use
|
||||||
if netstat -tln | grep -q ":3333 "; then
|
if netstat -tln | grep -q ":3334 "; then
|
||||||
echo ""
|
echo ""
|
||||||
echo "⚠️ Port 3333 is already in use!"
|
echo "⚠️ Port 3334 is already in use!"
|
||||||
echo ""
|
echo ""
|
||||||
echo "🔍 Process using port 3333:"
|
echo "🔍 Process using port 3334:"
|
||||||
sudo netstat -tlnp | grep ":3333 " || echo "Could not determine process"
|
sudo netstat -tlnp | grep ":3334 " || echo "Could not determine process"
|
||||||
echo ""
|
echo ""
|
||||||
echo "🛑 To kill existing process:"
|
echo "🛑 To kill existing process:"
|
||||||
echo "sudo lsof -ti:3333 | xargs sudo kill -9"
|
echo "sudo lsof -ti:3334 | xargs sudo kill -9"
|
||||||
echo ""
|
echo ""
|
||||||
read -p "Kill existing process and continue? (y/N): " -n 1 -r
|
read -p "Kill existing process and continue? (y/N): " -n 1 -r
|
||||||
echo
|
echo
|
||||||
if [[ $REPLY =~ ^[Yy]$ ]]; then
|
if [[ $REPLY =~ ^[Yy]$ ]]; then
|
||||||
echo "Killing processes using port 3333..."
|
echo "Killing processes using port 3334..."
|
||||||
sudo lsof -ti:3333 | xargs sudo kill -9 2>/dev/null || echo "No processes to kill"
|
sudo lsof -ti:3334 | xargs sudo kill -9 2>/dev/null || echo "No processes to kill"
|
||||||
sleep 2
|
sleep 2
|
||||||
else
|
else
|
||||||
echo "Exiting..."
|
echo "Exiting..."
|
||||||
@@ -59,7 +59,7 @@ echo "🚀 Starting Stratum Proxy Server..."
|
|||||||
echo "This will bridge cpuminer-opt-rin to your RinCoin node"
|
echo "This will bridge cpuminer-opt-rin to your RinCoin node"
|
||||||
echo ""
|
echo ""
|
||||||
echo "After it starts, connect your miner with:"
|
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 ""
|
||||||
echo "Press Ctrl+C to stop the proxy"
|
echo "Press Ctrl+C to stop the proxy"
|
||||||
echo ""
|
echo ""
|
||||||
|
|||||||
@@ -18,28 +18,22 @@ from requests.auth import HTTPBasicAuth
|
|||||||
# Import web interface
|
# Import web interface
|
||||||
from pool_web_interface import start_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,
|
def __init__(self, stratum_host='0.0.0.0', stratum_port=3333,
|
||||||
rpc_host='127.0.0.1', rpc_port=9556,
|
rpc_host='127.0.0.1', rpc_port=9556,
|
||||||
rpc_user='rinrpc', rpc_password='745ce784d5d537fc06105a1b935b7657903cfc71a5fb3b90',
|
rpc_user='rinrpc', rpc_password='745ce784d5d537fc06105a1b935b7657903cfc71a5fb3b90',
|
||||||
pool_address='rin1qahvvv9d5f3443wtckeqavwp9950wacxfmwv20q',
|
pool_address='rin1qahvvv9d5f3443wtckeqavwp9950wacxfmwv20q',
|
||||||
pool_fee_percent=1.0):
|
pool_fee_percent=1.0):
|
||||||
|
|
||||||
self.stratum_host = stratum_host
|
# Initialize base class
|
||||||
self.stratum_port = stratum_port
|
super().__init__(stratum_host, stratum_port, rpc_host, rpc_port, rpc_user, rpc_password, pool_address)
|
||||||
self.rpc_host = rpc_host
|
|
||||||
self.rpc_port = rpc_port
|
|
||||||
self.rpc_user = rpc_user
|
|
||||||
self.rpc_password = rpc_password
|
|
||||||
self.pool_address = pool_address
|
self.pool_address = pool_address
|
||||||
self.pool_fee_percent = pool_fee_percent
|
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
|
# Pool statistics
|
||||||
self.total_shares = 0
|
self.total_shares = 0
|
||||||
self.total_blocks = 0
|
self.total_blocks = 0
|
||||||
@@ -107,75 +101,26 @@ class RinCoinMiningPool:
|
|||||||
|
|
||||||
self.db.commit()
|
self.db.commit()
|
||||||
|
|
||||||
def rpc_call(self, method, params=[]):
|
def get_pool_block_template(self):
|
||||||
"""Make RPC call to RinCoin node"""
|
"""Get new block template and create pool-style job"""
|
||||||
try:
|
template = super().get_block_template()
|
||||||
url = f"http://{self.rpc_host}:{self.rpc_port}/"
|
if template:
|
||||||
headers = {'content-type': 'text/plain'}
|
# Convert to pool-style job format if needed
|
||||||
auth = HTTPBasicAuth(self.rpc_user, self.rpc_password)
|
job = self.current_job
|
||||||
|
if job:
|
||||||
payload = {
|
# Add pool-specific fields
|
||||||
"jsonrpc": "1.0",
|
job["coinb1"] = "01000000" + "0" * 60
|
||||||
"id": "mining_pool",
|
job["coinb2"] = "ffffffff"
|
||||||
"method": method,
|
job["merkle_branch"] = []
|
||||||
"params": params
|
job["clean_jobs"] = True
|
||||||
}
|
return job
|
||||||
|
return None
|
||||||
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 validate_rincoin_address(self, address):
|
def validate_rincoin_address(self, address):
|
||||||
"""Validate if an address is a valid RinCoin address"""
|
"""Validate if an address is a valid RinCoin address"""
|
||||||
if not address or not address.startswith('rin'):
|
|
||||||
return False
|
|
||||||
|
|
||||||
try:
|
try:
|
||||||
result = self.rpc_call("validateaddress", [address])
|
return self.decode_bech32_address(address) is not None
|
||||||
return result and result.get('isvalid', False)
|
except:
|
||||||
except Exception as e:
|
|
||||||
print(f"Address validation error: {e}")
|
|
||||||
return False
|
return False
|
||||||
|
|
||||||
def register_miner(self, user, worker, address=None):
|
def register_miner(self, user, worker, address=None):
|
||||||
@@ -297,33 +242,7 @@ class RinCoinMiningPool:
|
|||||||
if miners_without_addresses:
|
if miners_without_addresses:
|
||||||
print(f"📊 Summary: {len(miners_with_addresses)} miners with addresses, {len(miners_without_addresses)} without (rewards to pool)")
|
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):
|
# Use inherited send_stratum_response and send_stratum_notification from base class
|
||||||
"""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}")
|
|
||||||
|
|
||||||
def handle_stratum_message(self, client, addr, message):
|
def handle_stratum_message(self, client, addr, message):
|
||||||
"""Handle incoming Stratum message from miner"""
|
"""Handle incoming Stratum message from miner"""
|
||||||
@@ -347,7 +266,7 @@ class RinCoinMiningPool:
|
|||||||
self.send_stratum_notification(client, "mining.set_difficulty", [0.0001])
|
self.send_stratum_notification(client, "mining.set_difficulty", [0.0001])
|
||||||
|
|
||||||
# Send initial job
|
# Send initial job
|
||||||
if self.get_block_template():
|
if self.get_pool_block_template():
|
||||||
job = self.current_job
|
job = self.current_job
|
||||||
self.send_stratum_notification(client, "mining.notify", [
|
self.send_stratum_notification(client, "mining.notify", [
|
||||||
job["job_id"],
|
job["job_id"],
|
||||||
@@ -355,8 +274,8 @@ class RinCoinMiningPool:
|
|||||||
job["coinb1"],
|
job["coinb1"],
|
||||||
job["coinb2"],
|
job["coinb2"],
|
||||||
job["merkle_branch"],
|
job["merkle_branch"],
|
||||||
job["version"],
|
f"{job['version']:08x}",
|
||||||
job["nbits"],
|
job["bits"],
|
||||||
job["ntime"],
|
job["ntime"],
|
||||||
job["clean_jobs"]
|
job["clean_jobs"]
|
||||||
])
|
])
|
||||||
@@ -416,7 +335,8 @@ class RinCoinMiningPool:
|
|||||||
'miner_id': miner_id,
|
'miner_id': miner_id,
|
||||||
'address': miner_address,
|
'address': miner_address,
|
||||||
'shares': 0,
|
'shares': 0,
|
||||||
'last_share': time.time()
|
'last_share': time.time(),
|
||||||
|
'extranonce1': '00000000' # Default extranonce1
|
||||||
}
|
}
|
||||||
|
|
||||||
if miner_address:
|
if miner_address:
|
||||||
@@ -432,101 +352,70 @@ class RinCoinMiningPool:
|
|||||||
if addr not in self.clients:
|
if addr not in self.clients:
|
||||||
self.send_stratum_response(client, msg_id, False, "Not authorized")
|
self.send_stratum_response(client, msg_id, False, "Not authorized")
|
||||||
return
|
return
|
||||||
|
|
||||||
miner_info = self.clients[addr]
|
miner_info = self.clients[addr]
|
||||||
|
|
||||||
try:
|
try:
|
||||||
if self.current_job and len(params) >= 5:
|
if self.current_job and len(params) >= 5:
|
||||||
job_id = params[0]
|
username = params[0]
|
||||||
extranonce2 = params[1]
|
job_id = params[1]
|
||||||
ntime = params[2]
|
extranonce2 = params[2]
|
||||||
nonce = params[3]
|
ntime = params[3]
|
||||||
|
nonce = params[4]
|
||||||
|
|
||||||
# Calculate actual difficulty from the share submission
|
# Use base class to validate and submit share
|
||||||
# The miner reports its hashrate, so we need to calculate
|
extranonce1 = miner_info.get('extranonce1', '00000000')
|
||||||
# the difficulty that would match that hashrate
|
miner_address = miner_info.get('address')
|
||||||
# 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
|
|
||||||
|
|
||||||
# Record share with calculated difficulty
|
# For pool mining, always mine to pool address
|
||||||
self.record_share(miner_info['miner_id'], job_id, actual_difficulty)
|
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
|
if success:
|
||||||
now_ts = time.time()
|
# Record share with estimated difficulty
|
||||||
prev_ts = miner_info.get('last_share') or now_ts
|
actual_difficulty = 0.00133 # Estimated for ~381 kH/s
|
||||||
dt = max(now_ts - prev_ts, 1e-3) # Minimum 1ms to avoid division by zero
|
self.record_share(miner_info['miner_id'], job_id, actual_difficulty)
|
||||||
|
|
||||||
# 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]
|
|
||||||
|
|
||||||
# Get block info
|
# Update miner stats
|
||||||
block_info = self.rpc_call("getblock", [block_hash])
|
now_ts = time.time()
|
||||||
if block_info:
|
prev_ts = miner_info.get('last_share') or now_ts
|
||||||
block_height = block_info.get('height', 0)
|
dt = max(now_ts - prev_ts, 1e-3)
|
||||||
coinbase_tx = block_info.get('tx', [])[0] if block_info.get('tx') else None
|
miner_hashrate = actual_difficulty * (2**32) / dt
|
||||||
|
|
||||||
# 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)
|
|
||||||
|
|
||||||
|
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)
|
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:
|
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)
|
self.send_stratum_response(client, msg_id, True)
|
||||||
else:
|
else:
|
||||||
print(f"[{addr}] Invalid share parameters")
|
print(f"[{addr}] Invalid share parameters")
|
||||||
self.send_stratum_response(client, msg_id, False, "Invalid parameters")
|
self.send_stratum_response(client, msg_id, False, "Invalid parameters")
|
||||||
|
|
||||||
except Exception as e:
|
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
|
# Still accept the share for mining statistics
|
||||||
self.send_stratum_response(client, msg_id, True)
|
self.send_stratum_response(client, msg_id, True)
|
||||||
|
|
||||||
@@ -590,7 +479,7 @@ class RinCoinMiningPool:
|
|||||||
)
|
)
|
||||||
|
|
||||||
if should_create_job:
|
if should_create_job:
|
||||||
if self.get_block_template():
|
if self.get_pool_block_template():
|
||||||
job = self.current_job
|
job = self.current_job
|
||||||
last_job_time = time.time()
|
last_job_time = time.time()
|
||||||
last_block_height = current_height
|
last_block_height = current_height
|
||||||
@@ -606,8 +495,8 @@ class RinCoinMiningPool:
|
|||||||
job["coinb1"],
|
job["coinb1"],
|
||||||
job["coinb2"],
|
job["coinb2"],
|
||||||
job["merkle_branch"],
|
job["merkle_branch"],
|
||||||
job["version"],
|
f"{job['version']:08x}",
|
||||||
job["nbits"],
|
job["bits"],
|
||||||
job["ntime"],
|
job["ntime"],
|
||||||
job["clean_jobs"]
|
job["clean_jobs"]
|
||||||
])
|
])
|
||||||
|
|||||||
@@ -13,7 +13,7 @@ import hashlib
|
|||||||
import struct
|
import struct
|
||||||
from requests.auth import HTTPBasicAuth
|
from requests.auth import HTTPBasicAuth
|
||||||
|
|
||||||
class RinCoinStratumProxy:
|
class RinCoinStratumBase:
|
||||||
def __init__(self, stratum_host='0.0.0.0', stratum_port=3334,
|
def __init__(self, stratum_host='0.0.0.0', stratum_port=3334,
|
||||||
rpc_host='127.0.0.1', rpc_port=9556,
|
rpc_host='127.0.0.1', rpc_port=9556,
|
||||||
rpc_user='rinrpc', rpc_password='745ce784d5d537fc06105a1b935b7657903cfc71a5fb3b90',
|
rpc_user='rinrpc', rpc_password='745ce784d5d537fc06105a1b935b7657903cfc71a5fb3b90',
|
||||||
@@ -82,93 +82,92 @@ class RinCoinStratumProxy:
|
|||||||
def decode_bech32_address(self, address):
|
def decode_bech32_address(self, address):
|
||||||
"""Decode RinCoin bech32 address to script"""
|
"""Decode RinCoin bech32 address to script"""
|
||||||
try:
|
try:
|
||||||
if not address.startswith('rin1'):
|
if not address or not address.startswith('rin1'):
|
||||||
raise ValueError("Not a RinCoin bech32 address")
|
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])
|
result = self.rpc_call("validateaddress", [address])
|
||||||
if result and result.get('isvalid') and 'scriptPubKey' in result:
|
if not result or not result.get('isvalid'):
|
||||||
script_hex = result['scriptPubKey']
|
raise ValueError("Address not valid per node")
|
||||||
return bytes.fromhex(script_hex)
|
script_hex = result.get('scriptPubKey')
|
||||||
else:
|
if not script_hex:
|
||||||
# Fallback: create a basic P2WPKH script if RPC validation fails
|
raise ValueError("Node did not return scriptPubKey")
|
||||||
# Extract the witness program (assuming it's standard 20-byte)
|
return bytes.fromhex(script_hex)
|
||||||
# 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:
|
except Exception as e:
|
||||||
print(f"Address decode error: {e}")
|
print(f"Address decode error: {e}")
|
||||||
# Emergency fallback - this should never happen in production
|
return None
|
||||||
return bytes([0x00, 0x14]) + bytes(20)
|
|
||||||
|
|
||||||
def build_coinbase_transaction(self, template, extranonce1, extranonce2):
|
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:
|
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
|
has_witness_commitment = template.get('default_witness_commitment') is not None
|
||||||
|
|
||||||
if has_witness_commitment:
|
# Common parts
|
||||||
# Segwit marker and flag (only if witness commitment present)
|
value = template.get('coinbasevalue', 0)
|
||||||
coinbase += b'\x00\x01'
|
script_pubkey = self.decode_bech32_address(target_address)
|
||||||
|
if not script_pubkey:
|
||||||
# Input count
|
return None, None
|
||||||
coinbase += b'\x01'
|
witness_commitment = template.get('default_witness_commitment')
|
||||||
|
|
||||||
# Coinbase input
|
# ScriptSig (block height minimal push + tag + extranonces)
|
||||||
coinbase += b'\x00' * 32 # prev hash (null)
|
|
||||||
coinbase += b'\xff\xff\xff\xff' # prev index
|
|
||||||
|
|
||||||
# Script sig (height + extranonces + signature)
|
|
||||||
height = template.get('height', 0)
|
height = template.get('height', 0)
|
||||||
height_bytes = struct.pack('<I', height)
|
height_bytes = struct.pack('<I', height)
|
||||||
height_compact = bytes([len(height_bytes.rstrip(b'\x00'))]) + height_bytes.rstrip(b'\x00')
|
height_compact = bytes([len(height_bytes.rstrip(b'\x00'))]) + height_bytes.rstrip(b'\x00')
|
||||||
|
|
||||||
scriptsig = height_compact + b'/RinCoin/' + extranonce1.encode() + extranonce2.encode()
|
scriptsig = height_compact + b'/RinCoin/' + extranonce1.encode() + extranonce2.encode()
|
||||||
coinbase += self.encode_varint(len(scriptsig)) + scriptsig
|
|
||||||
|
# Helper to build outputs blob
|
||||||
# Sequence
|
def build_outputs_blob() -> bytes:
|
||||||
coinbase += b'\xff\xff\xff\xff'
|
outputs_blob = b''
|
||||||
|
outputs_list = []
|
||||||
# Prepare outputs
|
# Main output
|
||||||
outputs = []
|
outputs_list.append(struct.pack('<Q', value) + self.encode_varint(len(script_pubkey)) + script_pubkey)
|
||||||
|
# Witness commitment OP_RETURN output if present
|
||||||
# Main output to mining address
|
if witness_commitment:
|
||||||
value = template.get('coinbasevalue', 0)
|
commit_script = bytes.fromhex(witness_commitment)
|
||||||
script_pubkey = self.decode_bech32_address(self.target_address)
|
outputs_list.append(struct.pack('<Q', 0) + self.encode_varint(len(commit_script)) + commit_script)
|
||||||
outputs.append(struct.pack('<Q', value) + self.encode_varint(len(script_pubkey)) + script_pubkey)
|
outputs_blob += self.encode_varint(len(outputs_list))
|
||||||
|
for out in outputs_list:
|
||||||
# Witness commitment output if present
|
outputs_blob += out
|
||||||
witness_commitment = template.get('default_witness_commitment')
|
return outputs_blob
|
||||||
if witness_commitment:
|
|
||||||
commit_script = bytes.fromhex(witness_commitment)
|
# Build non-witness serialization (txid serialization)
|
||||||
outputs.append(struct.pack('<Q', 0) + self.encode_varint(len(commit_script)) + commit_script)
|
cb_nowit = b''
|
||||||
|
cb_nowit += struct.pack('<I', 1) # version
|
||||||
# Add outputs
|
cb_nowit += b'\x01' # input count
|
||||||
coinbase += self.encode_varint(len(outputs))
|
cb_nowit += b'\x00' * 32 # prevout hash
|
||||||
for output in outputs:
|
cb_nowit += b'\xff\xff\xff\xff' # prevout index
|
||||||
coinbase += output
|
cb_nowit += self.encode_varint(len(scriptsig)) + scriptsig
|
||||||
|
cb_nowit += b'\xff\xff\xff\xff' # sequence
|
||||||
# Witness data (only if segwit format)
|
cb_nowit += build_outputs_blob() # outputs
|
||||||
|
cb_nowit += struct.pack('<I', 0) # locktime
|
||||||
|
|
||||||
|
# Build with-witness serialization (block serialization)
|
||||||
if has_witness_commitment:
|
if has_witness_commitment:
|
||||||
coinbase += b'\x01' # witness stack count for input
|
cb_wit = b''
|
||||||
coinbase += b'\x20' # witness item length (32 bytes)
|
cb_wit += struct.pack('<I', 1) # version
|
||||||
coinbase += b'\x00' * 32 # witness nonce
|
cb_wit += b'\x00\x01' # segwit marker+flag
|
||||||
|
cb_wit += b'\x01' # input count
|
||||||
# Locktime
|
cb_wit += b'\x00' * 32 # prevout hash
|
||||||
coinbase += struct.pack('<I', 0)
|
cb_wit += b'\xff\xff\xff\xff' # prevout index
|
||||||
|
cb_wit += self.encode_varint(len(scriptsig)) + scriptsig
|
||||||
return coinbase
|
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
|
||||||
|
|
||||||
|
return cb_wit, cb_nowit
|
||||||
|
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
print(f"Coinbase construction error: {e}")
|
print(f"Coinbase construction error: {e}")
|
||||||
return None
|
return None, None
|
||||||
|
|
||||||
def calculate_merkle_root(self, coinbase_txid, transactions):
|
def calculate_merkle_root(self, coinbase_txid, transactions):
|
||||||
"""Calculate merkle root with coinbase at index 0"""
|
"""Calculate merkle root with coinbase at index 0"""
|
||||||
@@ -237,21 +236,25 @@ class RinCoinStratumProxy:
|
|||||||
print(f"Get block template error: {e}")
|
print(f"Get block template error: {e}")
|
||||||
return None
|
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"""
|
"""Validate share and submit block if valid"""
|
||||||
try:
|
try:
|
||||||
print(f"Share: job={job['job_id']} nonce={nonce}")
|
print(f"Share: job={job['job_id']} nonce={nonce}")
|
||||||
|
|
||||||
# Build coinbase transaction
|
# Use provided address or default
|
||||||
coinbase = self.build_coinbase_transaction(job['template'], extranonce1, extranonce2)
|
address = target_address or self.target_address
|
||||||
if not coinbase:
|
|
||||||
|
# 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"
|
return False, "Coinbase construction failed"
|
||||||
|
|
||||||
# Calculate coinbase transaction hash
|
# Calculate coinbase txid (non-witness serialization)
|
||||||
coinbase_hash = hashlib.sha256(hashlib.sha256(coinbase).digest()).digest()[::-1]
|
coinbase_txid = hashlib.sha256(hashlib.sha256(coinbase_nowit).digest()).digest()[::-1]
|
||||||
|
|
||||||
# Calculate merkle root
|
# 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
|
# Build block header
|
||||||
header = b''
|
header = b''
|
||||||
@@ -284,8 +287,8 @@ class RinCoinStratumProxy:
|
|||||||
tx_count = 1 + len(job['transactions'])
|
tx_count = 1 + len(job['transactions'])
|
||||||
block += self.encode_varint(tx_count)
|
block += self.encode_varint(tx_count)
|
||||||
|
|
||||||
# Add coinbase transaction
|
# Add coinbase transaction (witness variant for block body)
|
||||||
block += coinbase
|
block += coinbase_wit
|
||||||
|
|
||||||
# Add other transactions
|
# Add other transactions
|
||||||
for tx in job['transactions']:
|
for tx in job['transactions']:
|
||||||
@@ -299,7 +302,7 @@ class RinCoinStratumProxy:
|
|||||||
|
|
||||||
if result is None:
|
if result is None:
|
||||||
print(f"✅ Block accepted: {block_hash_hex}")
|
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']}")
|
print(f"📊 Block height: {job['height']}")
|
||||||
return True, "Block found and submitted"
|
return True, "Block found and submitted"
|
||||||
else:
|
else:
|
||||||
@@ -578,6 +581,15 @@ class RinCoinStratumProxy:
|
|||||||
finally:
|
finally:
|
||||||
print("Server stopped")
|
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__":
|
if __name__ == "__main__":
|
||||||
proxy = RinCoinStratumProxy()
|
proxy = RinCoinStratumProxy()
|
||||||
proxy.start()
|
proxy.start()
|
||||||
Reference in New Issue
Block a user