99 / network_integration.py
Fred808's picture
Upload 3 files
72321c5 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://api.bitaps.com/btc/v1"
self.node = "seed.bitcoin.sipa.be" # Bitcoin mainnet seed node
# 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
logging.debug("Fetching network stats...")
stats_response = requests.get("https://blockchain.info/stats?format=json")
logging.debug(f"Network stats API response status: {stats_response.status_code}")
stats = stats_response.json()
# Convert difficulty to target
difficulty = float(stats.get('difficulty', 1))
max_target = int('00000000FFFF0000000000000000000000000000000000000000000000000000', 16)
current_target = int(max_target / difficulty)
bits = stats.get('bits', '1d00ffff')
if isinstance(bits, str):
bits = int(bits, 16)
# 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, # Use converted bits
'height': int(height), # Ensure height is integer
'target': current_target # Calculate based on current difficulty
}
# 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
return {
'version': 2,
'previousblockhash': '0' * 64,
'merkleroot': '0' * 64,
'time': int(time.time()),
'bits': '1d00ffff',
'height': 0,
'target': int('00000000FFFF0000000000000000000000000000000000000000000000000000', 16)
}
if not stats:
raise Exception("Failed to get network stats")
network_difficulty = float(stats['difficulty'])
bits = stats.get('bits', '1d00ffff') # Default to testnet bits if missing
# Parse bits to target
bits_int = int(bits, 16) # Convert hex bits to int
exp = ((bits_int >> 24) & 0xff)
coeff = bits_int & 0x00ffffff
target = coeff * (2 ** (8 * (exp - 3)))
print(f"Mining at difficulty: {network_difficulty}")
print(f"Network target: {hex(target)}")
# Create proper coinbase input script
block_height_hex = hex(height)[2:].zfill(6) # BIP34: Block height
coinbase_script = (
"03" + # Push 3 bytes
block_height_hex + # BIP34: Block height
"0000000000000000" + # Extra nonce space
"2f4d696e656420627920426974436f696e2d436f70696c6f742f" # /Mined by BitCoin-Copilot/
)
# Create coinbase transaction
coinbase_tx = {
'version': 1,
'vin': [{
'txid': '0' * 64, # Null hash for coinbase
'vout': 0xFFFFFFFF, # -1 (4 bytes) for coinbase
'scriptSig': coinbase_script, # Block height + extra nonce + miner tag
'sequence': 0xFFFFFFFF
}],
'vout': [{
'value': 625000000, # 6.25 BTC reward
'scriptPubKey': '76a914' + hashlib.new("ripemd160", hashlib.sha256(self.wallet_address.encode()).digest()).hexdigest() + '88ac' # P2PKH to miner address
}]
}
# 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': current_block['mrkl_root'], # Changed to match Bitcoin Core naming
'time': int(time.time()), # Changed to match Bitcoin Core naming
'bits': bits_int, # Using parsed bits value
'height': height,
'target': target,
'difficulty': network_difficulty, # Changed to match Bitcoin Core naming
'coinbasetx': coinbase_tx, # Changed to match Bitcoin Core naming
'sizelimit': 4000000, # Changed to match Bitcoin Core naming
'transactions': [] # Pending transactions (empty for now)
}
return template
except Exception as e:
print(f"Error getting block template: {str(e)}")
# Get real latest block as fallback
latest_url = "https://blockchain.info/latestblock"
latest_response = requests.get(latest_url)
if latest_response.status_code == 200:
latest_data = latest_response.json()
block_height = latest_data['height']
prev_block = latest_data['hash']
else:
block_height = 800_000
prev_block = '000000000019d6689c085ae165831e934ff763ae46a2a6c172b3f1b60a8ce26f'
# 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()
# Create block data starting with header including nonce
block_data = bytearray(block_header[:-4] + struct.pack('<I', nonce))
# Add transaction count varint (1 for coinbase only)
block_data.extend(bytes([1]))
# Create proper coinbase transaction
block_height = template['height']
block_height_hex = hex(block_height)[2:].zfill(6) # BIP34: Block height
# Create proper coinbase script
coinbase_script = bytes.fromhex(
"03" + # Push 3 bytes
block_height_hex + # Block height (BIP34)
"0000000000000000" + # Extra nonce
"2f4d696e656420627920426974436f696e2d436f70696c6f742f" # /Mined by BitCoin-Copilot/
)
# 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