Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions src/qt/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -321,6 +321,8 @@ if(ENABLE_WALLET)
sendcoinsdialog.h
sendcoinsentry.cpp
sendcoinsentry.h
sendtransactiondialog.cpp
sendtransactiondialog.h
signverifymessagedialog.cpp
signverifymessagedialog.h
transactiondesc.cpp
Expand Down
102 changes: 102 additions & 0 deletions src/qt/forms/sendtransactiondialog.ui
Original file line number Diff line number Diff line change
@@ -0,0 +1,102 @@
<?xml version="1.0" encoding="UTF-8"?>
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Any reason we can't just use QInputDialog for this?

<ui version="4.0">
<class>SendTransactionDialog</class>
<widget class="QDialog" name="SendTransactionDialog">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>600</width>
<height>150</height>
</rect>
</property>
<property name="windowTitle">
<string>Send Transaction to Peer</string>
</property>
<layout class="QVBoxLayout" name="verticalLayout">
<item>
<layout class="QHBoxLayout" name="transactionLayout">
<item>
<widget class="QLabel" name="transactionLabel">
<property name="text">
<string>Raw Transaction (hex):</string>
</property>
</widget>
</item>
<item>
<widget class="QPlainTextEdit" name="transactionEdit">
<property name="placeholderText">
<string>Enter raw transaction in hexadecimal format...</string>
</property>
<property name="maximumBlockCount">
<number>1</number>
</property>
</widget>
</item>
<item>
<widget class="QToolButton" name="pasteButton">
<property name="toolTip">
<string>Paste transaction from clipboard</string>
</property>
<property name="text">
<string/>
</property>
<property name="icon">
<iconset resource="../bitcoin.qrc">
<normaloff>:/icons/editpaste</normaloff>:/icons/editpaste
</iconset>
</property>
<property name="iconSize">
<size>
<width>22</width>
<height>22</height>
</size>
</property>
<property name="shortcut">
<string>Alt+P</string>
</property>
</widget>
</item>
</layout>
</item>
<item>
<spacer name="verticalSpacer">
<property name="orientation">
<enum>Qt::Vertical</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>20</width>
<height>40</height>
</size>
</property>
</spacer>
</item>
<item>
<widget class="QDialogButtonBox" name="buttonBox">
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
<property name="standardButtons">
<set>QDialogButtonBox::Cancel|QDialogButtonBox::Ok</set>
</property>
</widget>
</item>
</layout>
</widget>
<resources/>
<connections>
<connection>
<sender>buttonBox</sender>
<signal>accepted()</signal>
<receiver>SendTransactionDialog</receiver>
<slot>accept()</slot>
</connection>
<connection>
<sender>buttonBox</sender>
<signal>rejected()</signal>
<receiver>SendTransactionDialog</receiver>
<slot>reject()</slot>
</connection>
</connections>
</ui>
44 changes: 44 additions & 0 deletions src/qt/rpcconsole.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -169,6 +169,8 @@ class PeerIdViewDelegate : public QStyledItemDelegate
};

#include <qt/rpcconsole.moc>
#include <qt/sendtransactiondialog.h>
#include <QMessageBox>

/**
* Split shell command line into a list of arguments and optionally execute the command(s).
Expand Down Expand Up @@ -830,6 +832,7 @@ void RPCConsole::setClientModel(ClientModel *model, int bestblock_height, int64_
});
peersTableContextMenu->addSeparator();
peersTableContextMenu->addAction(tr("&Disconnect"), this, &RPCConsole::disconnectSelectedNode);
peersTableContextMenu->addAction(tr("Send &Transaction"), this, &RPCConsole::sendTransactionToPeer);
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
peersTableContextMenu->addAction(tr("Send &Transaction"), this, &RPCConsole::sendTransactionToPeer);
peersTableContextMenu->addAction(tr("Send Raw &Transaction"), this, &RPCConsole::sendTransactionToPeer);

peersTableContextMenu->addAction(ts.ban_for + " " + tr("1 &hour"), [this] { banSelectedNode(60 * 60); });
peersTableContextMenu->addAction(ts.ban_for + " " + tr("1 d&ay"), [this] { banSelectedNode(60 * 60 * 24); });
peersTableContextMenu->addAction(ts.ban_for + " " + tr("1 &week"), [this] { banSelectedNode(60 * 60 * 24 * 7); });
Expand Down Expand Up @@ -1637,3 +1640,44 @@ void RPCConsole::updateConsoleStyleSheet()
scrollbar->setValue(oldScrollValue);
#endif
}

void RPCConsole::sendTransactionToPeer()
{
if (!clientModel)
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

One line or {} please

return;

QList<QModelIndex> nodes = GUIUtil::getEntryData(ui->peerWidget, PeerTableModel::NetNodeId);
if (nodes.isEmpty()) {
return;
}

NodeId peerId = nodes.first().data().toLongLong();
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Seems like if multiple peers are selected, you would want to send to all of them?


SendTransactionDialog dialog(platformStyle, this);
if (dialog.exec() == QDialog::Accepted) {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
if (dialog.exec() == QDialog::Accepted) {
if (dialog.exec() != QDialog::Accepted) return;

QString txHex = dialog.getTransactionHex();

if (!txHex.isEmpty()) {
QByteArray txBytes = QByteArray::fromHex(txHex.toUtf8());
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Shouldn't need UTF-8 for hex

QString txHexForRpc = QString::fromUtf8(txBytes.toHex());
Comment on lines +1661 to +1662
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why the round trip?


QString rpcCommand = QString("sendmsgtopeer %1 \"tx\" \"%2\"").arg(peerId).arg(txHexForRpc);

std::string result;
std::string executableCommand = rpcCommand.toStdString() + "\n";

try {
if (RPCExecuteCommandLine(m_node, result, executableCommand, nullptr, nullptr)) {
QMessageBox::information(this, tr("Success"),
tr("Transaction sent successfully to peer %1.").arg(peerId));
} else {
QMessageBox::warning(this, tr("Error"),
tr("Failed to send transaction to peer %1.").arg(peerId));
}
} catch (const std::exception& e) {
QMessageBox::critical(this, tr("Error"),
tr("Error sending transaction to peer %1: %2").arg(peerId).arg(QString::fromStdString(e.what())));
}
}
}
}
2 changes: 2 additions & 0 deletions src/qt/rpcconsole.h
Original file line number Diff line number Diff line change
Expand Up @@ -147,6 +147,8 @@ public Q_SLOTS:
void banSelectedNode(int bantime);
/** Unban a selected node on the Bans tab */
void unbanSelectedNode();
/** Send transaction to selected peer */
void sendTransactionToPeer();
/** set which tab has the focus (is visible) */
void setTabFocus(enum TabTypes tabType);
#ifdef ENABLE_WALLET
Expand Down
83 changes: 83 additions & 0 deletions src/qt/sendtransactiondialog.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,83 @@
// Copyright (c) 2025 Luke Dashjr
// Distributed under the MIT software license, see the accompanying
// file COPYING or http://www.opensource.org/licenses/mit-license.php.

#include <qt/sendtransactiondialog.h>
#include <qt/forms/ui_sendtransactiondialog.h>

#include <qt/guiutil.h>
#include <qt/platformstyle.h>

#include <QAbstractButton>
#include <QPlainTextEdit>
#include <QClipboard>
#include <QApplication>
#include <QRegularExpression>

SendTransactionDialog::SendTransactionDialog(const PlatformStyle* platformStyle, QWidget* parent)
: QDialog(parent, GUIUtil::dialog_flags),
ui(new Ui::SendTransactionDialog),
m_platform_style(platformStyle)
{
ui->setupUi(this);
ui->pasteButton->setIcon(m_platform_style->SingleColorIcon(":/icons/editpaste"));

QObject::connect(ui->pasteButton, &QAbstractButton::clicked, this, &SendTransactionDialog::on_pasteButton_clicked);

GUIUtil::handleCloseWindowShortcut(this);
}

SendTransactionDialog::~SendTransactionDialog()
{
delete ui;
}

QString SendTransactionDialog::getTransactionHex() const
{
return ui->transactionEdit->toPlainText().trimmed();
}

void SendTransactionDialog::accept()
{
QString txHex = getTransactionHex();

if (txHex.isEmpty()) {
ui->transactionEdit->setFocus();
return;
}

txHex = txHex.replace(QRegularExpression("\\s+"), "");
bool isValidHex = true;
for (const QChar& c : txHex) {
if (!c.isDigit() && !(c >= 'a' && c <= 'f') && !(c >= 'A' && c <= 'F')) {
isValidHex = false;
break;
}
}

if (!isValidHex || txHex.length() % 2 != 0) {
ui->transactionEdit->setFocus();
return;
}

QDialog::accept();
}

void SendTransactionDialog::changeEvent(QEvent* e)
{
if (e->type() == QEvent::PaletteChange) {
ui->pasteButton->setIcon(m_platform_style->SingleColorIcon(":/icons/editpaste"));
}

QDialog::changeEvent(e);
}

void SendTransactionDialog::on_pasteButton_clicked()
{
QClipboard* clipboard = QApplication::clipboard();
QString clipboardText = clipboard->text();

if (!clipboardText.isEmpty()) {
ui->transactionEdit->setPlainText(clipboardText);
}
}
38 changes: 38 additions & 0 deletions src/qt/sendtransactiondialog.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
// Copyright (c) 2025 Luke Dashjr
// Distributed under the MIT software license, see the accompanying
// file COPYING or http://www.opensource.org/licenses/mit-license.php.

#ifndef BITCOIN_QT_SENDTRANSACTIONDIALOG_H
#define BITCOIN_QT_SENDTRANSACTIONDIALOG_H

#include <QDialog>

class PlatformStyle;

namespace Ui {
class SendTransactionDialog;
}

class SendTransactionDialog : public QDialog
{
Q_OBJECT

public:
explicit SendTransactionDialog(const PlatformStyle* platformStyle, QWidget* parent);
~SendTransactionDialog();

QString getTransactionHex() const;

protected Q_SLOTS:
void accept() override;
void changeEvent(QEvent* e) override;

private Q_SLOTS:
void on_pasteButton_clicked();

private:
Ui::SendTransactionDialog* ui;
const PlatformStyle* m_platform_style;
};

#endif