|
|
"""
|
|
|
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):
|
|
|
self.api_base = "https://api.bitaps.com/btc/v1"
|
|
|
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...")
|
|
|
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()
|
|
|
|
|
|
|
|
|
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)
|
|
|
|
|
|
|
|
|
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)}")
|
|
|
|
|
|
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')
|
|
|
|
|
|
|
|
|
bits_int = int(bits, 16)
|
|
|
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)}")
|
|
|
|
|
|
|
|
|
block_height_hex = hex(height)[2:].zfill(6)
|
|
|
coinbase_script = (
|
|
|
"03" +
|
|
|
block_height_hex +
|
|
|
"0000000000000000" +
|
|
|
"2f4d696e656420627920426974436f696e2d436f70696c6f742f"
|
|
|
)
|
|
|
|
|
|
|
|
|
coinbase_tx = {
|
|
|
'version': 1,
|
|
|
'vin': [{
|
|
|
'txid': '0' * 64,
|
|
|
'vout': 0xFFFFFFFF,
|
|
|
'scriptSig': coinbase_script,
|
|
|
'sequence': 0xFFFFFFFF
|
|
|
}],
|
|
|
'vout': [{
|
|
|
'value': 625000000,
|
|
|
'scriptPubKey': '76a914' + hashlib.new("ripemd160", hashlib.sha256(self.wallet_address.encode()).digest()).hexdigest() + '88ac'
|
|
|
}]
|
|
|
}
|
|
|
|
|
|
|
|
|
template = {
|
|
|
'version': 0x20000000,
|
|
|
'previousblockhash': prev_block,
|
|
|
'merkleroot': current_block['mrkl_root'],
|
|
|
'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)}")
|
|
|
|
|
|
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'
|
|
|
|
|
|
|
|
|
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()
|
|
|
|
|
|
|
|
|
block_data = bytearray(block_header[:-4] + struct.pack('<I', nonce))
|
|
|
|
|
|
|
|
|
block_data.extend(bytes([1]))
|
|
|
|
|
|
|
|
|
block_height = template['height']
|
|
|
block_height_hex = hex(block_height)[2:].zfill(6)
|
|
|
|
|
|
|
|
|
coinbase_script = bytes.fromhex(
|
|
|
"03" +
|
|
|
block_height_hex +
|
|
|
"0000000000000000" +
|
|
|
"2f4d696e656420627920426974436f696e2d436f70696c6f742f"
|
|
|
)
|
|
|
|
|
|
|
|
|
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 |