|
|
""" |
|
|
Integrates the mining system with Bitcoin mainnet |
|
|
""" |
|
|
from typing import Dict, Any, Optional |
|
|
import time |
|
|
import requests |
|
|
import hashlib |
|
|
import json |
|
|
import logging |
|
|
import struct |
|
|
|
|
|
|
|
|
logging.basicConfig( |
|
|
level=logging.DEBUG, |
|
|
format='%(asctime)s - %(levelname)s - %(message)s', |
|
|
handlers=[ |
|
|
logging.FileHandler('network_debug.log'), |
|
|
logging.StreamHandler() |
|
|
] |
|
|
) |
|
|
|
|
|
class NetworkIntegration: |
|
|
def __init__(self, wallet_address: str = None, use_testnet: bool = False): |
|
|
self.is_mainnet = not use_testnet |
|
|
if use_testnet: |
|
|
self.api_base = "https://testnet.blockchain.info" |
|
|
self.node = "testnet-seed.bitcoin.jonasschnelli.ch" |
|
|
else: |
|
|
self.api_base = "https://blockchain.info" |
|
|
self.node = "seed.bitcoin.sipa.be" |
|
|
|
|
|
|
|
|
if wallet_address: |
|
|
self.wallet_address = wallet_address |
|
|
else: |
|
|
try: |
|
|
with open('my_wallet.json', 'r') as f: |
|
|
wallet_data = json.load(f) |
|
|
self.wallet_address = wallet_data['address'] |
|
|
print(f"Using wallet address: {self.wallet_address}") |
|
|
except Exception as e: |
|
|
print(f"Error loading wallet: {e}") |
|
|
self.wallet_address = "1Ks4WtCEK96BaBF7HSuCGt3rEpVKPqcJKf" |
|
|
|
|
|
def connect(self) -> bool: |
|
|
"""Connect to Bitcoin mainnet""" |
|
|
try: |
|
|
|
|
|
response = requests.get(f"{self.api_base}/blockchain/blocks/last") |
|
|
return response.status_code == 200 |
|
|
except Exception as e: |
|
|
print(f"Failed to connect to mainnet: {e}") |
|
|
return False |
|
|
|
|
|
def get_block_template(self) -> Dict[str, Any]: |
|
|
"""Get current block template from mainnet""" |
|
|
try: |
|
|
|
|
|
current_time = time.time() |
|
|
if not hasattr(self, '_template_cache') or current_time - self._last_cache_time > 300: |
|
|
logging.debug("Cache expired, fetching new block template") |
|
|
|
|
|
response = requests.get("https://blockchain.info/latestblock") |
|
|
logging.debug(f"Latest block API response status: {response.status_code}") |
|
|
|
|
|
if response.status_code != 200: |
|
|
logging.error(f"Failed to get latest block. Status code: {response.status_code}") |
|
|
if hasattr(self, '_template_cache'): |
|
|
logging.info("Using cached template") |
|
|
return self._template_cache |
|
|
raise Exception("Failed to get latest block") |
|
|
|
|
|
latest = response.json() |
|
|
logging.debug(f"Latest block response: {latest}") |
|
|
|
|
|
height = latest['height'] |
|
|
current_block = latest['hash'] |
|
|
logging.info(f"Current block height: {height}, hash: {current_block}") |
|
|
|
|
|
|
|
|
logging.debug("Fetching network stats...") |
|
|
diff_response = requests.get("https://blockchain.info/q/getdifficulty") |
|
|
if diff_response.status_code != 200: |
|
|
raise Exception("Failed to get network difficulty") |
|
|
|
|
|
network_difficulty = float(diff_response.text) |
|
|
logging.info(f"Current network difficulty: {network_difficulty}") |
|
|
|
|
|
|
|
|
max_target = 0x00000000FFFF0000000000000000000000000000000000000000000000000000 |
|
|
target = int(max_target / network_difficulty) |
|
|
|
|
|
|
|
|
exp = 0x1d |
|
|
coeff = (target >> (8 * (0x1d - 3))) & 0xffffff |
|
|
bits = (exp << 24) | coeff |
|
|
|
|
|
logging.debug(f"Target calculated from difficulty: {hex(target)}") |
|
|
current_target = target |
|
|
|
|
|
|
|
|
template = { |
|
|
'version': 2, |
|
|
'previousblockhash': current_block, |
|
|
'merkleroot': '0' * 64, |
|
|
'time': int(time.time()), |
|
|
'bits': bits, |
|
|
'target': current_target, |
|
|
'height': height |
|
|
} |
|
|
|
|
|
logging.debug(f"Target calculated from bits {hex(bits)}: {hex(current_target)}") |
|
|
|
|
|
|
|
|
template = { |
|
|
'version': 2, |
|
|
'previousblockhash': current_block, |
|
|
'merkleroot': '0' * 64, |
|
|
'time': int(time.time()), |
|
|
'bits': bits, |
|
|
'height': int(height), |
|
|
'target': current_target |
|
|
} |
|
|
|
|
|
|
|
|
self._template_cache = template |
|
|
self._last_cache_time = current_time |
|
|
|
|
|
return self._template_cache |
|
|
|
|
|
except Exception as e: |
|
|
logging.error(f"Error getting block template: {str(e)}") |
|
|
|
|
|
|
|
|
diff_url = "https://blockchain.info/q/getdifficulty" |
|
|
try: |
|
|
diff_response = requests.get(diff_url) |
|
|
if diff_response.status_code == 200: |
|
|
network_difficulty = float(diff_response.text) |
|
|
bits = hex(int((0xffff * 2**(8*(0x1d - 3))) / network_difficulty))[2:] |
|
|
target = int((0xffff * 2**(8*(0x1d - 3))) / network_difficulty) |
|
|
logging.info(f"Got mainnet difficulty: {network_difficulty}") |
|
|
else: |
|
|
|
|
|
network_difficulty = 137533144484879.19 |
|
|
target = int((0xffff * 2**(8*(0x1d - 3))) / network_difficulty) |
|
|
bits = f"{0x1d:02x}{target & 0xffffff:06x}" |
|
|
except Exception as e: |
|
|
logging.error(f"Error getting mainnet difficulty: {e}") |
|
|
|
|
|
network_difficulty = 137533144484879.19 |
|
|
target = int((0xffff * 2**(8*(0x1d - 3))) / network_difficulty) |
|
|
bits = f"{0x1d:02x}{target & 0xffffff:06x}" |
|
|
|
|
|
return { |
|
|
'version': 2, |
|
|
'previousblockhash': '0' * 64, |
|
|
'merkleroot': '0' * 64, |
|
|
'time': int(time.time()), |
|
|
'bits': bits, |
|
|
'height': 0, |
|
|
'target': target |
|
|
} |
|
|
|
|
|
|
|
|
except Exception as e: |
|
|
logging.error(f"Error getting block template: {str(e)}") |
|
|
|
|
|
network_difficulty = 137533144484879.19 |
|
|
max_target = 0x00000000FFFF0000000000000000000000000000000000000000000000000000 |
|
|
target = int(max_target / network_difficulty) |
|
|
bits = f"{0x1d:02x}{(target >> 208) & 0xffffff:06x}" |
|
|
|
|
|
logging.info(f"Using fallback difficulty: {network_difficulty}") |
|
|
block_height = 917362 |
|
|
prev_block = "00000000000000000000635542f008dfb9ba9f4d25e53539c62918aa5c5b852a" |
|
|
|
|
|
|
|
|
diff_url = "https://blockchain.info/q/getdifficulty" |
|
|
try: |
|
|
diff_response = requests.get(diff_url) |
|
|
if diff_response.status_code == 200: |
|
|
network_difficulty = float(diff_response.text) |
|
|
target = int((0xffff * 2**(8*(0x1d - 3))) / network_difficulty) |
|
|
else: |
|
|
target = 0x00000000ffff0000000000000000000000000000000000000000000000000000 |
|
|
except: |
|
|
target = 0x00000000ffff0000000000000000000000000000000000000000000000000000 |
|
|
|
|
|
template = { |
|
|
'version': 0x20000000, |
|
|
'previous_block': prev_block, |
|
|
'merkle_root': '4a5e1e4baab89f3a32518a88c31bc87f618f76673e2cc77ab2127b7afdeda33b', |
|
|
'timestamp': int(time.time()), |
|
|
'bits': 0x1d00ffff, |
|
|
'target': target, |
|
|
'height': block_height, |
|
|
'network_difficulty': network_difficulty if 'network_difficulty' in locals() else None |
|
|
} |
|
|
return template |
|
|
try: |
|
|
|
|
|
response = requests.get(self.bitcoin_network.latest_block_url) |
|
|
if response.status_code != 200: |
|
|
raise Exception("Failed to get latest block") |
|
|
|
|
|
latest = response.json() |
|
|
|
|
|
template = { |
|
|
'version': 0x20000000, |
|
|
'previousblockhash': prev_block, |
|
|
'merkleroot': '0' * 64, |
|
|
'time': int(time.time()), |
|
|
'bits': bits_int, |
|
|
'height': height, |
|
|
'target': target, |
|
|
'difficulty': network_difficulty, |
|
|
'coinbasetx': coinbase_tx, |
|
|
'sizelimit': 4000000, |
|
|
'transactions': [] |
|
|
} |
|
|
return template |
|
|
except Exception as e: |
|
|
print(f"Error getting block template: {str(e)}") |
|
|
|
|
|
template = { |
|
|
'version': 0x20000000, |
|
|
'previousblockhash': '000000000000000000024bead8df69990852c202db0e0097c1a12ea637d7e96d', |
|
|
'merkleroot': '4a5e1e4baab89f3a32518a88c31bc87f618f76673e2cc77ab2127b7afdeda33b', |
|
|
'time': int(time.time()), |
|
|
'bits': 0x1d00ffff, |
|
|
'target': 0x00000000ffff0000000000000000000000000000000000000000000000000000, |
|
|
'height': 2_500_000, |
|
|
'difficulty': 1.0, |
|
|
'coinbasetx': { |
|
|
'version': 1, |
|
|
'vin': [{ |
|
|
'txid': '0' * 64, |
|
|
'vout': 0xFFFFFFFF, |
|
|
'scriptSig': '03' + hex(2_500_000)[2:].zfill(6) + '0000000000000000', |
|
|
'sequence': 0xFFFFFFFF |
|
|
}], |
|
|
'vout': [{ |
|
|
'value': 625000000, |
|
|
'scriptPubKey': '76a914' + hashlib.new("ripemd160", hashlib.sha256(self.wallet_address.encode()).digest()).hexdigest() + '88ac' |
|
|
}] |
|
|
}, |
|
|
'sizelimit': 4000000, |
|
|
'transactions': [] |
|
|
} |
|
|
return template |
|
|
|
|
|
def submit_block(self, block_header: bytes, nonce: int) -> bool: |
|
|
"""Submit found block to network""" |
|
|
try: |
|
|
|
|
|
template = self.get_block_template() |
|
|
|
|
|
|
|
|
full_header = block_header[:-4] + struct.pack('<I', nonce) |
|
|
block_hash = hashlib.sha256(hashlib.sha256(full_header).digest()).digest() |
|
|
block_hash_hex = block_hash.hex() |
|
|
block_hash_int = int.from_bytes(block_hash, 'little') |
|
|
|
|
|
|
|
|
logging.info(f"Block hash: {block_hash_hex}") |
|
|
logging.info(f"Leading zeros: {len(block_hash_hex) - len(block_hash_hex.lstrip('0'))}") |
|
|
logging.info(f"Hash value: {int(block_hash_hex, 16)}") |
|
|
logging.info(f"Target value: {template['target']}") |
|
|
|
|
|
if block_hash_int >= template['target']: |
|
|
logging.error("Block hash doesn't meet target requirement") |
|
|
logging.error(f"Hash/Target ratio: {block_hash_int / template['target']:.2f}") |
|
|
return False |
|
|
|
|
|
|
|
|
block_data = bytearray(full_header) |
|
|
|
|
|
|
|
|
block_data.extend(bytes([1])) |
|
|
|
|
|
|
|
|
block_height = template['height'] |
|
|
block_height_hex = hex(block_height)[2:].zfill(6) |
|
|
|
|
|
|
|
|
coinbase_script = ( |
|
|
bytes([3]) + |
|
|
bytes.fromhex(block_height_hex) + |
|
|
bytes.fromhex("0000000000000000") + |
|
|
b"/Mined by Elias/" |
|
|
) |
|
|
|
|
|
|
|
|
from base58 import b58decode_check |
|
|
|
|
|
|
|
|
try: |
|
|
decoded = b58decode_check(self.wallet_address) |
|
|
pubkey_hash = decoded[1:] |
|
|
except Exception as e: |
|
|
logging.error(f"Error decoding wallet address: {e}") |
|
|
return False |
|
|
|
|
|
|
|
|
coinbase_tx = ( |
|
|
struct.pack('<I', 1) + |
|
|
bytes([1]) + |
|
|
bytes.fromhex('0' * 64) + |
|
|
struct.pack('<I', 0xFFFFFFFF) + |
|
|
bytes([len(coinbase_script)]) + |
|
|
coinbase_script + |
|
|
struct.pack('<I', 0xFFFFFFFF) + |
|
|
bytes([1]) + |
|
|
struct.pack('<Q', 625000000) + |
|
|
bytes([25]) + |
|
|
bytes([ |
|
|
0x76, |
|
|
0xa9, |
|
|
0x14 |
|
|
]) + |
|
|
pubkey_hash + |
|
|
bytes([ |
|
|
0x88, |
|
|
0xac |
|
|
]) + |
|
|
struct.pack('<I', 0) |
|
|
) |
|
|
|
|
|
|
|
|
block_data.extend(coinbase_tx) |
|
|
|
|
|
|
|
|
block_data.extend(bytes([0])) |
|
|
|
|
|
|
|
|
successful = False |
|
|
nodes = [ |
|
|
"https://btc.getblock.io/mainnet/", |
|
|
"https://blockchain.info/pushtx", |
|
|
"https://api.bitcore.io/api/BTC/mainnet/tx/send", |
|
|
self.api_base + "/submitblock" |
|
|
] |
|
|
|
|
|
for node in nodes: |
|
|
try: |
|
|
logging.info(f"Attempting submission to {node}") |
|
|
response = requests.post( |
|
|
node, |
|
|
data={'block': block_data.hex()}, |
|
|
headers={'Content-Type': 'application/x-www-form-urlencoded'}, |
|
|
timeout=10 |
|
|
) |
|
|
|
|
|
if response.status_code == 200: |
|
|
logging.info(f"Successfully submitted block {block_hash.hex()}") |
|
|
logging.info(f"Block reward sent to {self.wallet_address}") |
|
|
successful = True |
|
|
break |
|
|
else: |
|
|
logging.warning(f"Submission failed for {node}: {response.text}") |
|
|
except Exception as e: |
|
|
logging.warning(f"Error submitting to {node}: {e}") |
|
|
continue |
|
|
|
|
|
return successful |
|
|
|
|
|
except Exception as e: |
|
|
logging.error(f"Error submitting block: {str(e)}") |
|
|
return False |
|
|
|
|
|
|
|
|
tx_data = struct.pack('<I', 1) |
|
|
|
|
|
|
|
|
tx_data += bytes([1]) |
|
|
|
|
|
|
|
|
tx_data += b'\x00' * 32 |
|
|
tx_data += struct.pack('<I', 0xFFFFFFFF) |
|
|
|
|
|
|
|
|
script_len = len(coinbase_script) |
|
|
if script_len < 0xfd: |
|
|
tx_data += bytes([script_len]) |
|
|
elif script_len <= 0xffff: |
|
|
tx_data += bytes([0xfd]) + struct.pack('<H', script_len) |
|
|
elif script_len <= 0xffffffff: |
|
|
tx_data += bytes([0xfe]) + struct.pack('<I', script_len) |
|
|
else: |
|
|
tx_data += bytes([0xff]) + struct.pack('<Q', script_len) |
|
|
|
|
|
tx_data += coinbase_script |
|
|
tx_data += struct.pack('<I', 0xFFFFFFFF) |
|
|
|
|
|
|
|
|
tx_data += bytes([1]) |
|
|
|
|
|
|
|
|
tx_data += struct.pack('<Q', 625000000) |
|
|
|
|
|
|
|
|
|
|
|
from base58 import b58decode_check |
|
|
decoded = b58decode_check(self.wallet_address) |
|
|
pubkey_hash = decoded[1:] |
|
|
|
|
|
|
|
|
script_pubkey = bytes([0x76, 0xa9, 0x14]) + pubkey_hash + bytes([0x88, 0xac]) |
|
|
tx_data += bytes([len(script_pubkey)]) |
|
|
tx_data += script_pubkey |
|
|
|
|
|
|
|
|
tx_data += struct.pack('<I', 0) |
|
|
|
|
|
|
|
|
block_data.extend(tx_data) |
|
|
|
|
|
|
|
|
submit_url = 'https://api.blockchain.info/haskoin-store/btc/block' |
|
|
headers = {'Content-Type': 'application/x-www-form-urlencoded'} |
|
|
response = requests.post(submit_url, data={'block': block_data.hex()}, headers=headers) |
|
|
|
|
|
if response.status_code == 200: |
|
|
print(f"Block successfully submitted!") |
|
|
logging.info("Block submission successful") |
|
|
return True |
|
|
elif response.status_code == 400 and 'bad-txns-vin-empty' in response.text: |
|
|
print("Block rejected: Invalid coinbase transaction structure") |
|
|
logging.error("Block rejected due to invalid coinbase transaction") |
|
|
return False |
|
|
else: |
|
|
error_msg = response.text if response.text else f"Status code: {response.status_code}" |
|
|
print(f"Block submission failed: {error_msg}") |
|
|
logging.error(f"Block submission failed: {error_msg}") |
|
|
return False |
|
|
|
|
|
except Exception as e: |
|
|
print(f"Error submitting block: {str(e)}") |
|
|
return False |
|
|
try: |
|
|
block_hash = hashlib.sha256(hashlib.sha256(block_header).digest()).digest() |
|
|
return self.bitcoin_network.submit_block(block_header, nonce) |
|
|
except Exception as e: |
|
|
print(f"Block submission error: {e}") |
|
|
return False |
|
|
|
|
|
def _bits_to_target(self, bits: str) -> int: |
|
|
"""Convert compact bits to target""" |
|
|
bits = int(bits, 16) |
|
|
shift = (bits >> 24) & 0xff |
|
|
target = (bits & 0x00ffffff) * (2 ** (8 * (shift - 3))) |
|
|
return target |