Skip to content

Commit 46f30c7

Browse files
committed
multi-chain-process
1 parent 8407571 commit 46f30c7

4 files changed

Lines changed: 150 additions & 11 deletions

File tree

src/qt/CMakeLists.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -216,6 +216,7 @@ if(ENABLE_WALLET)
216216
PRIVATE
217217
bitcoin_wallet
218218
Qt6::Network
219+
Qt6::Network
219220
)
220221
endif()
221222

src/qt/bitcoingui.cpp

Lines changed: 145 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -43,12 +43,18 @@
4343

4444
#include <functional>
4545

46+
47+
#include <QTcpSocket>
48+
#include <QHostAddress>
49+
#include <QTimer>
50+
4651
#include <QAction>
4752
#include <QActionGroup>
4853
#include <QApplication>
4954
#include <QComboBox>
5055
#include <QCursor>
5156
#include <QDateTime>
57+
#include <QDebug>
5258
#include <QDragEnterEvent>
5359
#include <QInputDialog>
5460
#include <QKeySequence>
@@ -57,6 +63,7 @@
5763
#include <QMenuBar>
5864
#include <QMessageBox>
5965
#include <QMimeData>
66+
#include <QProcess>
6067
#include <QProgressDialog>
6168
#include <QScreen>
6269
#include <QSettings>
@@ -244,6 +251,60 @@ BitcoinGUI::~BitcoinGUI()
244251
delete rpcConsole;
245252
}
246253

254+
bool BitcoinGUI::isPortAvailable(const QHostAddress& address, quint16 port, int timeoutMs = 100) {
255+
QTcpSocket socket;
256+
257+
// Attempt to connect to the port with a short timeout
258+
socket.connectToHost(address, port);
259+
260+
// Wait for the connection attempt to complete
261+
if (socket.waitForConnected(timeoutMs)) {
262+
// If connected, a service is listening, so the port is NOT AVAILABLE for use by a new service
263+
socket.disconnectFromHost();
264+
socket.close();
265+
return false; // Port is in use
266+
}
267+
268+
// Check for the specific error indicating refusal or timeout
269+
// QAbstractSocket::ConnectionRefusedError usually means nothing is listening.
270+
// QAbstractSocket::RemoteHostClosedError or QAbstractSocket::SocketTimeoutError
271+
// often means the port is available (or blocked by a firewall).
272+
if (socket.state() == QAbstractSocket::UnconnectedState ||
273+
socket.error() == QAbstractSocket::ConnectionRefusedError ||
274+
socket.error() == QAbstractSocket::SocketTimeoutError) {
275+
276+
return true; // Port is available/free
277+
}
278+
279+
return false; // Assume unavailable/error otherwise
280+
}
281+
282+
void BitcoinGUI::addChainProcess()
283+
{
284+
285+
//QString program = QApplication::applicationFilePath();
286+
287+
//QStringList arguments = QApplication::arguments();
288+
//bool success = QProcess::startDetached(program, arguments); //add -listen?
289+
290+
bool success = QProcess::startDetached(QApplication::applicationFilePath());
291+
292+
if (success)
293+
{
294+
//QApplication::quit();
295+
//Q_EMIT quitRequested();
296+
Q_EMIT detectShutdown();
297+
}
298+
else
299+
{
300+
qDebug() << "Failed to restart Bitcoin-Gui. Could not launch new process.";
301+
//Q_EMIT quitRequested();
302+
QApplication::quit();
303+
304+
}
305+
}
306+
307+
247308
void BitcoinGUI::createActions()
248309
{
249310
QActionGroup *tabGroup = new QActionGroup(this);
@@ -495,6 +556,15 @@ void BitcoinGUI::createActions()
495556
connect(new QShortcut(QKeySequence(Qt::CTRL | Qt::SHIFT | Qt::Key_D), this), &QShortcut::activated, this, &BitcoinGUI::showDebugWindow);
496557
}
497558

559+
560+
quint16 get_default_port(const QString& chain_id) {
561+
if (chain_id == "main") return 8333;
562+
if (chain_id == "testnet4") return 48334; // Actual port may vary
563+
if (chain_id == "regtest") return 18444;
564+
if (chain_id == "signet") return 38333;
565+
return 0; // Default or error
566+
}
567+
498568
void BitcoinGUI::createMenuBar()
499569
{
500570
appMenuBar = menuBar();
@@ -533,29 +603,92 @@ void BitcoinGUI::createMenuBar()
533603
settings->addAction(optionsAction);
534604

535605
// Add chain selection submenu
536-
QAction* chain_selection_action = new QAction(tr("&Switch chain"), this);
537-
chain_selection_action->setStatusTip(tr("Restart application using a different (test) network"));
606+
QAction* chain_selection_action = new QAction(tr("&Add Chain Process"), this);
607+
chain_selection_action->setStatusTip(tr("Start process using a different network"));
538608
QMenu* chain_selection_menu = new QMenu(this);
539609
chain_selection_action->setMenu(chain_selection_menu);
540610

611+
612+
613+
614+
615+
616+
541617
connect(chain_selection_menu, &QMenu::aboutToShow, [this, chain_selection_menu] {
542618
chain_selection_menu->clear();
543619
const std::vector<std::pair<QString, QString>> chains = {{"main", "&Bitcoin"}, {"testnet4", "&Testnet4"}, {"regtest", "&Regtest"}, {"signet", "&Signet"}};
544620
const std::string current_chain = Params().GetChainTypeString();
545621
for (const auto& chain : chains) {
622+
//add port detection here?
546623
const bool is_current = current_chain == chain.first.toStdString();
547-
QAction* action = chain_selection_menu->addAction(chain.second);
624+
625+
626+
// 🔑 NEW LOGIC STARTS HERE 🔑
627+
628+
// 1. Get the port for the chain
629+
quint16 chain_port = get_default_port(chain.first);
630+
631+
// 2. Check if the port is already in use by another process
632+
// The check for 'isPortAvailable' returns TRUE if it's free/NOT in use.
633+
// We want to disable the action if the port is IN USE.
634+
// We check the local host address.
635+
bool port_in_use = !isPortAvailable(QHostAddress::LocalHost, chain_port);
636+
637+
// The action should be disabled if:
638+
// a) It's the current chain (line 612 handles this already)
639+
// b) Another program is using the port (port_in_use is true)
640+
641+
// 🔑 NEW LOGIC ENDS HERE 🔑
642+
643+
644+
QAction* action = chain_selection_menu->addAction(chain.second);
548645
action->setCheckable(true);
549646
action->setChecked(is_current);
550-
action->setEnabled(!is_current);
647+
648+
649+
bool should_be_disabled = is_current || port_in_use;
650+
action->setEnabled(!should_be_disabled); // Enable if NOT disabled
651+
//action->setEnabled(!is_current);
652+
653+
654+
if (port_in_use && !is_current) {
655+
// Set a tooltip to explain *why* it's disabled if another program is using the port
656+
action->setToolTip(tr("Cannot switch to this chain; its default port (%1) is in use by another application.").arg(chain_port));
657+
} else if (is_current) {
658+
action->setToolTip(tr("Currently running on this chain."));
659+
} else {
660+
action->setToolTip(tr("Switch to %1. Requires application restart.").arg(QString(chain.second).remove('&')));
661+
}
662+
551663
connect(action, &QAction::triggered, [this, chain] {
552-
//: Switch to the mainnet, testnet, signet or regtest chain.
553-
QMessageBox::StandardButton btn_ret_val = QMessageBox::question(this, tr("Switch chain"),
554-
//: Switching between mainnet, testnet, signet or regtest chain requires a restart.
555-
tr("Client restart required to switch chain.\n\nClient will be shut down. Do you want to proceed?"),
556-
QMessageBox::Yes | QMessageBox::Cancel, QMessageBox::Cancel);
557664

558-
if (btn_ret_val == QMessageBox::Cancel) return;
665+
666+
QMessageBox msgBox;
667+
668+
// 2. Set the text and title using the translated string
669+
msgBox.setWindowTitle(tr("Add chain process?"));
670+
msgBox.setText(tr("Do you want to proceed?"));
671+
672+
// 3. Set the icon explicitly on the QMessageBox object
673+
msgBox.setIcon(QMessageBox::Question);
674+
675+
// 4. Set the buttons (e.g., Yes and Cancel)
676+
msgBox.setStandardButtons(QMessageBox::Yes | QMessageBox::Cancel);
677+
678+
679+
////: Switch to the mainnet, testnet, signet or regtest chain.
680+
//QMessageBox::StandardButton btn_ret_val = QMessageBox::question(this, tr("Switch chain"),
681+
// //: Switching between mainnet, testnet, signet or regtest chain requires a restart.
682+
// tr("Add chain process?\n\nDo you want to proceed?").setIcon(QMessageBox::Question),
683+
// QMessageBox::Yes | QMessageBox::Cancel, QMessageBox::Cancel);
684+
685+
686+
int clickedButton = msgBox.exec();
687+
688+
// 2. Check the stored variable instead of the non-existent member.
689+
if (clickedButton == QMessageBox::Cancel) {
690+
return;
691+
}
559692

560693
// QSettings are stored seperately for each network. Switch application name
561694
// to mainnet before storing the selected chain.
@@ -565,7 +698,8 @@ void BitcoinGUI::createMenuBar()
565698

566699
QSettings settings;
567700
settings.setValue("chain", chain.first);
568-
Q_EMIT quitRequested();
701+
//Q_EMIT quitRequested();
702+
Q_EMIT addChainProcess();
569703
});
570704
}
571705
});

src/qt/bitcoingui.h

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@
1414

1515
#include <consensus/amount.h>
1616

17+
#include <QHostAddress>
1718
#include <QLabel>
1819
#include <QMainWindow>
1920
#include <QMap>
@@ -74,6 +75,8 @@ class BitcoinGUI : public QMainWindow
7475
explicit BitcoinGUI(interfaces::Node& node, const PlatformStyle *platformStyle, const NetworkStyle *networkStyle, QWidget *parent = nullptr);
7576
~BitcoinGUI();
7677

78+
void addChainProcess();
79+
bool isPortAvailable(const QHostAddress& address, quint16 port, int timeoutMs);
7780
/** Set the client model.
7881
The client model represents the part of the core that communicates with the P2P network, and is wallet-agnostic.
7982
*/

src/qt/test/CMakeLists.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ target_link_libraries(test_bitcoin-qt
2121
bitcoin_node
2222
Boost::headers
2323
Qt6::Test
24+
Qt6::Network
2425
)
2526

2627
import_plugins(test_bitcoin-qt)

0 commit comments

Comments
 (0)