git / moonbite-network / gui_shared.py · raw

"""MyCoin Desktop GUI — Using Shared State Server.This version connects to shared_state.py instead of creating its own Node.Run shared_state.py FIRST, then this app."""import sysimport timeimport tracebackfrom threading import Threadfrom typing import Optionalfrom PyQt6.QtWidgets import (    QApplication,    QMainWindow,    QWidget,    QVBoxLayout,    QHBoxLayout,    QTabWidget,    QTextEdit,    QLabel,    QPushButton,    QSpinBox,    QProgressBar,    QLineEdit,    QMessageBox,)from PyQt6.QtCore import Qt, pyqtSignal, QObject, QThreadfrom PyQt6.QtGui import QFontfrom shared_client import SharedStateClient# Setup loggingimport logginglogging.basicConfig(filename='gui_error.log', level=logging.DEBUG,                   format='%(asctime)s - %(levelname)s - %(message)s')logger = logging.getLogger(__name__)# Signal emitter for thread-safe GUI updates from mining threadsclass MiningWorker(QObject):    """Emits progress signals during mining."""    progress = pyqtSignal(int, str, int)  # blocks_mined, block_hash, total_utxo    finished = pyqtSignal()    error = pyqtSignal(str)    def __init__(self, shared_client: SharedStateClient, address: str, num_blocks: int):        super().__init__()        self.shared_client = shared_client        self.address = address        self.num_blocks = num_blocks        self.should_stop = False    def run(self):        """Mine blocks using shared client."""        try:            # Start mining via shared state            self.shared_client.start_mining(self.num_blocks, self.address)            # Poll status until done            while True:                status = self.shared_client.mining_status()                blocks_mined = status.get("blocks_mined", 0)                height = status.get("current_height", 0)                self.progress.emit(blocks_mined, f"height {height}", 0)                if not status.get("is_mining", False):                    break                time.sleep(0.5)            self.finished.emit()        except Exception as e:            self.error.emit(str(e))class MainWindow(QMainWindow):    def __init__(self):        try:            super().__init__()            self.setWindowTitle("MyCoin — Desktop Mining (Shared State)")            self.setGeometry(100, 100, 900, 700)            # Connect to shared state server            logger.info("Connecting to shared_state server...")            self.shared_client = SharedStateClient("127.0.0.1", 9999)            # Test connection            info = self.shared_client.blockchain_info()            logger.info(f"Connected successfully. Blockchain height: {info.get('height')}")        except Exception as e:            logger.error(f"ERROR in MainWindow.__init__: {e}")            logger.error(traceback.format_exc())            error_msg = (                f"Cannot connect to shared_state.py\n\nError: {e}\n\n"                "Make sure shared_state.py is running first!"            )            try:                QMessageBox.critical(None, "Connection Error", error_msg)            except Exception:                print(error_msg)            sys.exit(1)        self.miner_address: Optional[str] = None        self.mining_thread: Optional[Thread] = None        self.mining_worker: Optional[MiningWorker] = None        # Create tabs        self.tabs = QTabWidget()        self.setCentralWidget(self.tabs)        # Tab 1: Wallet        self.wallet_tab = QWidget()        self.tabs.addTab(self.wallet_tab, "Wallet")        self._setup_wallet_tab()        # Tab 2: Mining        self.mining_tab = QWidget()        self.tabs.addTab(self.mining_tab, "Mining")        self._setup_mining_tab()        # Tab 3: Blockchain        self.blockchain_tab = QWidget()        self.tabs.addTab(self.blockchain_tab, "Blockchain")        self._setup_blockchain_tab()        # Tab 4: Settings        self.settings_tab = QWidget()        self.tabs.addTab(self.settings_tab, "Settings")        self._setup_settings_tab()    def _setup_wallet_tab(self):        layout = QVBoxLayout()        # Address display        layout.addWidget(QLabel("Your Address:"))        self.address_label = QLineEdit()        self.address_label.setReadOnly(True)        layout.addWidget(self.address_label)        # Generate key button        btn_new_key = QPushButton("Generate New Key")        btn_new_key.clicked.connect(self._on_new_key)        layout.addWidget(btn_new_key)        # Balance display        layout.addWidget(QLabel("Balance:"))        self.balance_label = QLabel("0 coins (0 cents)")        self.balance_label.setFont(QFont("Arial", 14, QFont.Weight.Bold))        layout.addWidget(self.balance_label)        # Check balance button        btn_balance = QPushButton("Check Balance")        btn_balance.clicked.connect(self._on_check_balance)        layout.addWidget(btn_balance)        layout.addStretch()        self.wallet_tab.setLayout(layout)    def _setup_mining_tab(self):        layout = QVBoxLayout()        # Blocks spinbox        layout.addWidget(QLabel("Number of Blocks:"))        self.blocks_spinbox = QSpinBox()        self.blocks_spinbox.setValue(1)        self.blocks_spinbox.setMinimum(1)        self.blocks_spinbox.setMaximum(1000)        layout.addWidget(self.blocks_spinbox)        # Mining address        layout.addWidget(QLabel("Miner Address:"))        self.mining_address_input = QLineEdit()        layout.addWidget(self.mining_address_input)        # Start mining button        self.btn_start_mining = QPushButton("Start Mining")        self.btn_start_mining.clicked.connect(self._on_start_mining)        layout.addWidget(self.btn_start_mining)        # Progress bar        self.progress_bar = QProgressBar()        self.progress_bar.setValue(0)        layout.addWidget(self.progress_bar)        # Status label        self.mining_status_label = QLabel("Ready")        layout.addWidget(self.mining_status_label)        # Stop mining button        self.btn_stop_mining = QPushButton("Stop Mining")        self.btn_stop_mining.setEnabled(False)        self.btn_stop_mining.clicked.connect(self._on_stop_mining)        layout.addWidget(self.btn_stop_mining)        layout.addStretch()        self.mining_tab.setLayout(layout)    def _setup_blockchain_tab(self):        layout = QVBoxLayout()        self.blockchain_info = QTextEdit()        self.blockchain_info.setReadOnly(True)        layout.addWidget(self.blockchain_info)        btn_refresh = QPushButton("Refresh")        btn_refresh.clicked.connect(self._on_refresh_blockchain)        layout.addWidget(btn_refresh)        self.blockchain_tab.setLayout(layout)        self._on_refresh_blockchain()    def _setup_settings_tab(self):        layout = QVBoxLayout()        layout.addWidget(QLabel("Settings (Read-Only)"))        layout.addWidget(QLabel("Coinbase Maturity: 0 (via shared server)"))        self.settings_log = QTextEdit()        self.settings_log.setReadOnly(True)        self.settings_log.setText("Connected to Shared State Server\nIP: 127.0.0.1\nPort: 9999")        layout.addWidget(self.settings_log)        layout.addStretch()        self.settings_tab.setLayout(layout)    def _on_new_key(self):        try:            result = self.shared_client.new_key()            self.miner_address = result["address"]            self.address_label.setText(self.miner_address)            self.mining_address_input.setText(self.miner_address)        except Exception as e:            self.address_label.setText(f"Error: {e}")    def _on_check_balance(self):        try:            result = self.shared_client.get_balance()            coins = result["balance_coins"]            cents = result["balance_cents"]            self.balance_label.setText(f"{coins} coins ({cents} cents)")        except Exception as e:            self.balance_label.setText(f"Error: {e}")    def _on_start_mining(self):        if not self.miner_address:            self.mining_status_label.setText("Error: Generate address first")            return        blocks = self.blocks_spinbox.value()        address = self.mining_address_input.text()        if not address:            self.mining_status_label.setText("Error: No address specified")            return        # Clean up previous thread if still running        if self.mining_thread is not None and self.mining_thread.isRunning():            self.mining_thread.quit()            self.mining_thread.wait()        self.btn_start_mining.setEnabled(False)        self.btn_stop_mining.setEnabled(True)        self.mining_status_label.setText(f"Mining {blocks} blocks...")        # Create and run worker in thread        self.mining_worker = MiningWorker(self.shared_client, address, blocks)        self.mining_thread = QThread()        self.mining_worker.moveToThread(self.mining_thread)        self.mining_thread.started.connect(self.mining_worker.run)        self.mining_worker.progress.connect(self._on_mining_progress)        self.mining_worker.finished.connect(self._on_mining_finished)        self.mining_worker.error.connect(self._on_mining_error)        self.mining_worker.finished.connect(self.mining_thread.quit)        self.mining_worker.error.connect(self.mining_thread.quit)        self.mining_thread.start()    def _on_mining_progress(self, blocks_mined: int, status: str, utxo: int):        self.progress_bar.setValue(blocks_mined)        self.mining_status_label.setText(f"{status} — mined {blocks_mined} blocks")    def _on_mining_finished(self):        self.btn_start_mining.setEnabled(True)        self.btn_stop_mining.setEnabled(False)        self.mining_status_label.setText("Mining complete!")        # Wait for thread to finish cleanup        if self.mining_thread is not None:            self.mining_thread.wait()        self._on_check_balance()    def _on_mining_error(self, error: str):        self.btn_start_mining.setEnabled(True)        self.btn_stop_mining.setEnabled(False)        self.mining_status_label.setText(f"Error: {error}")    def _on_stop_mining(self):        try:            self.shared_client.stop_mining()            self.mining_status_label.setText("Mining stopped")            self.btn_stop_mining.setEnabled(False)            self.btn_start_mining.setEnabled(True)        except Exception as e:            self.mining_status_label.setText(f"Error: {e}")    def _on_refresh_blockchain(self):        try:            result = self.shared_client.blockchain_info()            info = f"""Blockchain Info:================Height: {result['height']}Tip Hash: {result['tip_hash'][:32]}...Total Money: {result['total_money_coins']} coins ({result['total_money_cents']} cents)Transactions: {result['tx_count']}"""            self.blockchain_info.setText(info)        except Exception as e:            self.blockchain_info.setText(f"Error: {e}")if __name__ == "__main__":    try:        logger.info("Starting MyCoin GUI...")        app = QApplication(sys.argv)        logger.info("QApplication created")        window = MainWindow()        logger.info("MainWindow created")        window.show()        logger.info("Window shown, entering event loop")        sys.exit(app.exec())    except Exception as e:        logger.error(f"Fatal error: {e}")        logger.error(traceback.format_exc())        print(f"Fatal error: {e}")        sys.exit(1)