pool uses proxy
This commit is contained in:
@@ -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 ""
|
||||
|
@@ -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"]
|
||||
])
|
||||
|
@@ -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']
|
||||
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
|
||||
|
||||
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)
|
||||
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'
|
||||
|
||||
# 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)
|
||||
|
||||
# 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')
|
||||
|
||||
# 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')
|
||||
if witness_commitment:
|
||||
commit_script = bytes.fromhex(witness_commitment)
|
||||
outputs.append(struct.pack('<Q', 0) + self.encode_varint(len(commit_script)) + commit_script)
|
||||
|
||||
# Add outputs
|
||||
coinbase += self.encode_varint(len(outputs))
|
||||
for output in outputs:
|
||||
coinbase += output
|
||||
|
||||
# Witness data (only if segwit format)
|
||||
|
||||
# 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_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
|
||||
|
||||
# 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
|
||||
|
||||
# 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
|
||||
|
||||
# Locktime
|
||||
coinbase += struct.pack('<I', 0)
|
||||
|
||||
return coinbase
|
||||
|
||||
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
|
||||
|
||||
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()
|
Reference in New Issue
Block a user