CHLOG / network_integration.py
favoredone's picture
Update network_integration.py
3ee386e verified
"""
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
# Configure detailed logging
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):
self.api_base = "https://blockchain.info" # Changed to more reliable API
self.node = "seed.bitcoin.sipa.be" # Bitcoin mainnet seed node
self.is_mainnet = True # Force mainnet mode
# Use the provided wallet address or load from my_wallet.json
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" # Your default address
def connect(self) -> bool:
"""Connect to Bitcoin mainnet"""
try:
# Test connection by getting latest block
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:
# Cache the blockchain API response for 5 minutes
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")
# Get latest block info from a more reliable API
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}")
# Get current network stats and difficulty
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}")
# Calculate target from difficulty
max_target = 0x00000000FFFF0000000000000000000000000000000000000000000000000000
target = int(max_target / network_difficulty)
bits = f"{0x1d:02x}{(target >> 208) & 0xffffff:06x}"
logging.debug(f"Target calculated from difficulty: {hex(target)}")
# Use fixed bits for target calculation
bits = 0x1d00ffff # Standard Bitcoin difficulty 1 target
# Calculate target from bits
exp = ((bits >> 24) & 0xff)
coeff = bits & 0x00ffffff
current_target = coeff * (2 ** (8 * (exp - 3)))
# Create block template
template = {
'version': 2,
'previousblockhash': current_block,
'merkleroot': '0' * 64, # Placeholder merkle root
'time': int(time.time()),
'bits': bits,
'target': current_target,
'height': height
}
logging.debug(f"Target calculated from bits {hex(bits)}: {hex(current_target)}")
# Construct template with required fields
template = {
'version': 2, # Current Bitcoin version
'previousblockhash': current_block, # Use current block as previous for next block
'merkleroot': '0' * 64, # Placeholder merkle root
'time': int(time.time()),
'bits': bits, # Original bits value
'height': int(height), # Ensure height is integer
'target': current_target # Correctly calculated target from bits
}
# Update cache
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)}")
# Return fallback template
# Get real mainnet difficulty from blockchain.info
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:
# Use more reasonable fallback difficulty for mainnet
network_difficulty = 137533144484879.19 # Recent mainnet difficulty
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}")
# Use more reasonable fallback difficulty for mainnet
network_difficulty = 137533144484879.19 # Recent mainnet difficulty
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)}")
# Use fallback difficulty and target
network_difficulty = 137533144484879.19 # Recent mainnet difficulty
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 # Recent block height
prev_block = "00000000000000000000635542f008dfb9ba9f4d25e53539c62918aa5c5b852a" # Recent block hash
# Get real network difficulty even in fallback
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:
# Get latest block info
response = requests.get(self.bitcoin_network.latest_block_url)
if response.status_code != 200:
raise Exception("Failed to get latest block")
latest = response.json()
# Construct proper block template with real network data
template = {
'version': 0x20000000, # Version 2 with BIP9 bits
'previousblockhash': prev_block, # Changed to match Bitcoin Core naming
'merkleroot': '0' * 64, # Will be calculated from transactions
'time': int(time.time()), # Current time
'bits': bits_int, # Using parsed bits value
'height': height,
'target': target,
'difficulty': network_difficulty,
'coinbasetx': coinbase_tx,
'sizelimit': 4000000, # 4MB block size limit
'transactions': [] # Pending transactions (empty for now)
}
return template
except Exception as e:
print(f"Error getting block template: {str(e)}")
# Create fallback template
template = {
'version': 0x20000000, # Version 2 with BIP9 bits
'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:
# Get current template
template = self.get_block_template()
# Verify the block hash first
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')
# Log detailed hash information
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
# Create block data starting with header including nonce
block_data = bytearray(full_header)
# Add transaction count varint (1 for coinbase only)
block_data.extend(bytes([1]))
# Create proper coinbase transaction with our wallet address
block_height = template['height']
block_height_hex = hex(block_height)[2:].zfill(6) # BIP34: Block height
# Create proper coinbase script with required elements
coinbase_script = (
bytes([3]) + # Push 3 bytes (block height)
bytes.fromhex(block_height_hex) + # Block height (BIP34)
bytes.fromhex("0000000000000000") + # Extra nonce space
b"/Mined by Elias/" # Miner tag
)
# Import required for base58 decoding
from base58 import b58decode_check
# Decode wallet address to get public key hash
try:
decoded = b58decode_check(self.wallet_address)
pubkey_hash = decoded[1:] # Remove version byte
except Exception as e:
logging.error(f"Error decoding wallet address: {e}")
return False
# Create complete coinbase transaction
coinbase_tx = (
struct.pack('<I', 1) + # Version
bytes([1]) + # Input count
bytes.fromhex('0' * 64) + # Previous tx hash (null for coinbase)
struct.pack('<I', 0xFFFFFFFF) + # Previous output index
bytes([len(coinbase_script)]) + # Script length
coinbase_script + # Coinbase script
struct.pack('<I', 0xFFFFFFFF) + # Sequence
bytes([1]) + # Output count
struct.pack('<Q', 625000000) + # 6.25 BTC reward in satoshis
bytes([25]) + # Output script length (25 bytes for P2PKH)
bytes([
0x76, # OP_DUP
0xa9, # OP_HASH160
0x14 # Push 20 bytes
]) +
pubkey_hash + # Public key hash from decoded address
bytes([
0x88, # OP_EQUALVERIFY
0xac # OP_CHECKSIG
]) +
struct.pack('<I', 0) # Locktime
)
# Add coinbase transaction to block
block_data.extend(coinbase_tx)
# Add empty witness commitment
block_data.extend(bytes([0])) # No witness data
# Submit to more reliable nodes
successful = False
nodes = [
"https://btc.getblock.io/mainnet/", # Primary
"https://blockchain.info/pushtx", # Backup 1
"https://api.bitcore.io/api/BTC/mainnet/tx/send", # Backup 2
self.api_base + "/submitblock" # Original endpoint as fallback
]
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
# Start serializing the coinbase transaction
tx_data = struct.pack('<I', 1) # Version 1
# Input count (always 1 for coinbase)
tx_data += bytes([1])
# Coinbase input with proper null txid (must be exactly 32 bytes)
tx_data += b'\x00' * 32 # Previous txid (null for coinbase)
tx_data += struct.pack('<I', 0xFFFFFFFF) # Previous output index (-1 for coinbase)
# Create proper coinbase input script with proper length prefix
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 # Coinbase script
tx_data += struct.pack('<I', 0xFFFFFFFF) # Sequence
# Output count (1 output paying the miner)
tx_data += bytes([1])
# Miner's reward output (6.25 BTC)
tx_data += struct.pack('<Q', 625000000) # Value in satoshis
# Create proper P2PKH script for payout
# First decode the base58 address to get the pubkey hash
from base58 import b58decode_check
decoded = b58decode_check(self.wallet_address)
pubkey_hash = decoded[1:] # Skip version byte
# Build P2PKH script
script_pubkey = bytes([0x76, 0xa9, 0x14]) + pubkey_hash + bytes([0x88, 0xac])
tx_data += bytes([len(script_pubkey)]) # Script length
tx_data += script_pubkey # P2PKH script
# Add locktime
tx_data += struct.pack('<I', 0) # nLockTime
# Add serialized coinbase transaction to block
block_data.extend(tx_data)
# Submit block using blockchain.info API
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