mirror of
https://github.com/feather-wallet/feather.git
synced 2024-12-23 03:59:29 +00:00
Merge pull request 'Import transaction' (#143) from tobtoht/feather:import_tx into master
Reviewed-on: https://git.wownero.com/feather/feather/pulls/143
This commit is contained in:
commit
bd60e30c3f
12 changed files with 308 additions and 3 deletions
|
@ -30,7 +30,7 @@ if(DEBUG)
|
||||||
set(CMAKE_VERBOSE_MAKEFILE ON)
|
set(CMAKE_VERBOSE_MAKEFILE ON)
|
||||||
endif()
|
endif()
|
||||||
|
|
||||||
set(MONERO_HEAD "9ca5569f40a392b16946c5c3bda312eecfdcc9ab")
|
set(MONERO_HEAD "d029a63fb75c581fa060447b41d385c595144774")
|
||||||
set(BUILD_GUI_DEPS ON)
|
set(BUILD_GUI_DEPS ON)
|
||||||
set(ARCH "x86-64")
|
set(ARCH "x86-64")
|
||||||
set(BUILD_64 ON)
|
set(BUILD_64 ON)
|
||||||
|
|
2
monero
2
monero
|
@ -1 +1 @@
|
||||||
Subproject commit 9ca5569f40a392b16946c5c3bda312eecfdcc9ab
|
Subproject commit d029a63fb75c581fa060447b41d385c595144774
|
99
src/dialog/tximportdialog.cpp
Normal file
99
src/dialog/tximportdialog.cpp
Normal file
|
@ -0,0 +1,99 @@
|
||||||
|
#include "tximportdialog.h"
|
||||||
|
#include "ui_tximportdialog.h"
|
||||||
|
|
||||||
|
#include <QMessageBox>
|
||||||
|
|
||||||
|
TxImportDialog::TxImportDialog(QWidget *parent, AppContext *ctx)
|
||||||
|
: QDialog(parent)
|
||||||
|
, m_ctx(ctx)
|
||||||
|
, m_loadTimer(new QTimer(this))
|
||||||
|
, ui(new Ui::TxImportDialog)
|
||||||
|
{
|
||||||
|
ui->setupUi(this);
|
||||||
|
ui->resp->hide();
|
||||||
|
ui->label_loading->hide();
|
||||||
|
|
||||||
|
m_network = new UtilsNetworking(m_ctx->network, this);
|
||||||
|
|
||||||
|
auto node = ctx->nodes->connection();
|
||||||
|
m_rpc = new DaemonRpc(this, m_network, node.full);
|
||||||
|
|
||||||
|
connect(ui->btn_load, &QPushButton::clicked, this, &TxImportDialog::loadTx);
|
||||||
|
connect(ui->btn_import, &QPushButton::clicked, this, &TxImportDialog::onImport);
|
||||||
|
|
||||||
|
connect(m_rpc, &DaemonRpc::ApiResponse, this, &TxImportDialog::onApiResponse);
|
||||||
|
|
||||||
|
connect(m_loadTimer, &QTimer::timeout, [this]{
|
||||||
|
ui->label_loading->setText(ui->label_loading->text() + ".");
|
||||||
|
});
|
||||||
|
|
||||||
|
this->adjustSize();
|
||||||
|
}
|
||||||
|
|
||||||
|
void TxImportDialog::loadTx() {
|
||||||
|
QString txid = ui->line_txid->text();
|
||||||
|
QString node = m_ctx->nodes->connection().full;
|
||||||
|
|
||||||
|
if (!node.startsWith("http://"))
|
||||||
|
node = QString("http://%1").arg(node);
|
||||||
|
|
||||||
|
m_rpc->setDaemonAddress(node);
|
||||||
|
m_rpc->getTransactions(QStringList() << txid, false, true);
|
||||||
|
|
||||||
|
ui->label_loading->setText("Loading transaction");
|
||||||
|
ui->label_loading->setHidden(false);
|
||||||
|
m_loadTimer->start(1000);
|
||||||
|
}
|
||||||
|
|
||||||
|
void TxImportDialog::onApiResponse(const DaemonRpc::DaemonResponse &resp) {
|
||||||
|
m_loadTimer->stop();
|
||||||
|
ui->label_loading->setHidden(true);
|
||||||
|
if (!resp.ok) {
|
||||||
|
QMessageBox::warning(this, "Import transaction", resp.status);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (resp.endpoint == DaemonRpc::Endpoint::GET_TRANSACTIONS) {
|
||||||
|
ui->resp->setVisible(true);
|
||||||
|
ui->resp->setPlainText(QJsonDocument(resp.obj).toJson(QJsonDocument::Indented));
|
||||||
|
this->adjustSize();
|
||||||
|
|
||||||
|
if (resp.obj.contains("missed_tx")) {
|
||||||
|
ui->btn_import->setEnabled(false);
|
||||||
|
QMessageBox::warning(this, "Load transaction", "Transaction could not be found. Make sure the txid is correct, or try connecting to a different node.");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
QMessageBox::information(this, "Load transaction", "Transaction loaded successfully.\n\nAfter closing this message box click the Import button to import the transaction into your wallet.");
|
||||||
|
m_transaction = resp.obj;
|
||||||
|
ui->btn_import->setEnabled(true);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void TxImportDialog::onImport() {
|
||||||
|
QJsonObject tx = m_transaction.value("txs").toArray().first().toObject();
|
||||||
|
|
||||||
|
QString txid = tx.value("tx_hash").toString();
|
||||||
|
|
||||||
|
QVector<quint64> output_indices;
|
||||||
|
for (const auto &o: tx.value("output_indices").toArray()) {
|
||||||
|
output_indices.push_back(o.toInt());
|
||||||
|
}
|
||||||
|
|
||||||
|
quint64 height = tx.value("block_height").toInt();
|
||||||
|
quint64 timestamp = tx.value("block_timestamp").toInt();
|
||||||
|
|
||||||
|
bool pool = tx.value("in_pool").toBool();
|
||||||
|
bool double_spend_seen = tx.value("double_spend_seen").toBool();
|
||||||
|
|
||||||
|
if (m_ctx->currentWallet->importTransaction(tx.value("tx_hash").toString(), output_indices, height, timestamp, false, pool, double_spend_seen)) {
|
||||||
|
QMessageBox::information(this, "Import transaction", "Transaction imported successfully.");
|
||||||
|
} else {
|
||||||
|
QMessageBox::warning(this, "Import transaction", "Transaction import failed.");
|
||||||
|
}
|
||||||
|
m_ctx->refreshModels();
|
||||||
|
}
|
||||||
|
|
||||||
|
TxImportDialog::~TxImportDialog() {
|
||||||
|
delete ui;
|
||||||
|
}
|
36
src/dialog/tximportdialog.h
Normal file
36
src/dialog/tximportdialog.h
Normal file
|
@ -0,0 +1,36 @@
|
||||||
|
#ifndef FEATHER_TXIMPORTDIALOG_H
|
||||||
|
#define FEATHER_TXIMPORTDIALOG_H
|
||||||
|
|
||||||
|
#include <QDialog>
|
||||||
|
#include "appcontext.h"
|
||||||
|
#include "utils/daemonrpc.h"
|
||||||
|
|
||||||
|
namespace Ui {
|
||||||
|
class TxImportDialog;
|
||||||
|
}
|
||||||
|
|
||||||
|
class TxImportDialog : public QDialog
|
||||||
|
{
|
||||||
|
Q_OBJECT
|
||||||
|
|
||||||
|
public:
|
||||||
|
explicit TxImportDialog(QWidget *parent, AppContext *ctx);
|
||||||
|
~TxImportDialog() override;
|
||||||
|
|
||||||
|
private slots:
|
||||||
|
void loadTx();
|
||||||
|
void onImport();
|
||||||
|
void onApiResponse(const DaemonRpc::DaemonResponse &resp);
|
||||||
|
|
||||||
|
private:
|
||||||
|
Ui::TxImportDialog *ui;
|
||||||
|
UtilsNetworking *m_network;
|
||||||
|
AppContext *m_ctx;
|
||||||
|
DaemonRpc *m_rpc;
|
||||||
|
QTimer *m_loadTimer;
|
||||||
|
|
||||||
|
QJsonObject m_transaction;
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
#endif //FEATHER_TXIMPORTDIALOG_H
|
115
src/dialog/tximportdialog.ui
Normal file
115
src/dialog/tximportdialog.ui
Normal file
|
@ -0,0 +1,115 @@
|
||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<ui version="4.0">
|
||||||
|
<class>TxImportDialog</class>
|
||||||
|
<widget class="QDialog" name="TxImportDialog">
|
||||||
|
<property name="geometry">
|
||||||
|
<rect>
|
||||||
|
<x>0</x>
|
||||||
|
<y>0</y>
|
||||||
|
<width>700</width>
|
||||||
|
<height>442</height>
|
||||||
|
</rect>
|
||||||
|
</property>
|
||||||
|
<property name="minimumSize">
|
||||||
|
<size>
|
||||||
|
<width>700</width>
|
||||||
|
<height>0</height>
|
||||||
|
</size>
|
||||||
|
</property>
|
||||||
|
<property name="windowTitle">
|
||||||
|
<string>Import Transaction</string>
|
||||||
|
</property>
|
||||||
|
<layout class="QVBoxLayout" name="verticalLayout">
|
||||||
|
<item>
|
||||||
|
<widget class="QLineEdit" name="line_txid">
|
||||||
|
<property name="placeholderText">
|
||||||
|
<string>Transaction ID</string>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
<item>
|
||||||
|
<widget class="QPlainTextEdit" name="resp">
|
||||||
|
<property name="readOnly">
|
||||||
|
<bool>true</bool>
|
||||||
|
</property>
|
||||||
|
<property name="placeholderText">
|
||||||
|
<string>Debug info..</string>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
<item>
|
||||||
|
<layout class="QHBoxLayout" name="horizontalLayout_2">
|
||||||
|
<item>
|
||||||
|
<widget class="QPushButton" name="btn_load">
|
||||||
|
<property name="text">
|
||||||
|
<string>Load</string>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
<item>
|
||||||
|
<widget class="QPushButton" name="btn_import">
|
||||||
|
<property name="enabled">
|
||||||
|
<bool>false</bool>
|
||||||
|
</property>
|
||||||
|
<property name="text">
|
||||||
|
<string>Import</string>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
<item>
|
||||||
|
<widget class="QLabel" name="label_loading">
|
||||||
|
<property name="text">
|
||||||
|
<string>Loading transaction</string>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
<item>
|
||||||
|
<widget class="QDialogButtonBox" name="buttonBox">
|
||||||
|
<property name="orientation">
|
||||||
|
<enum>Qt::Horizontal</enum>
|
||||||
|
</property>
|
||||||
|
<property name="standardButtons">
|
||||||
|
<set>QDialogButtonBox::Close</set>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
</layout>
|
||||||
|
</item>
|
||||||
|
</layout>
|
||||||
|
</widget>
|
||||||
|
<resources/>
|
||||||
|
<connections>
|
||||||
|
<connection>
|
||||||
|
<sender>buttonBox</sender>
|
||||||
|
<signal>accepted()</signal>
|
||||||
|
<receiver>TxImportDialog</receiver>
|
||||||
|
<slot>accept()</slot>
|
||||||
|
<hints>
|
||||||
|
<hint type="sourcelabel">
|
||||||
|
<x>248</x>
|
||||||
|
<y>254</y>
|
||||||
|
</hint>
|
||||||
|
<hint type="destinationlabel">
|
||||||
|
<x>157</x>
|
||||||
|
<y>274</y>
|
||||||
|
</hint>
|
||||||
|
</hints>
|
||||||
|
</connection>
|
||||||
|
<connection>
|
||||||
|
<sender>buttonBox</sender>
|
||||||
|
<signal>rejected()</signal>
|
||||||
|
<receiver>TxImportDialog</receiver>
|
||||||
|
<slot>reject()</slot>
|
||||||
|
<hints>
|
||||||
|
<hint type="sourcelabel">
|
||||||
|
<x>316</x>
|
||||||
|
<y>260</y>
|
||||||
|
</hint>
|
||||||
|
<hint type="destinationlabel">
|
||||||
|
<x>286</x>
|
||||||
|
<y>274</y>
|
||||||
|
</hint>
|
||||||
|
</hints>
|
||||||
|
</connection>
|
||||||
|
</connections>
|
||||||
|
</ui>
|
|
@ -476,6 +476,23 @@ bool Wallet::importOutputs(const QString& path) {
|
||||||
return m_walletImpl->importOutputs(path.toStdString());
|
return m_walletImpl->importOutputs(path.toStdString());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool Wallet::importTransaction(const QString& txid, const QVector<quint64>& output_indeces, quint64 height, quint64 timestamp, bool miner_tx, bool pool, bool double_spend_seen) {
|
||||||
|
std::vector<uint64_t> o_indeces;
|
||||||
|
for (const auto &o : output_indeces) {
|
||||||
|
o_indeces.push_back(o);
|
||||||
|
}
|
||||||
|
|
||||||
|
return m_walletImpl->importTransaction(
|
||||||
|
txid.toStdString(),
|
||||||
|
o_indeces,
|
||||||
|
height,
|
||||||
|
17, // todo: get actual block_version
|
||||||
|
timestamp,
|
||||||
|
miner_tx,
|
||||||
|
pool,
|
||||||
|
double_spend_seen);
|
||||||
|
}
|
||||||
|
|
||||||
void Wallet::startRefresh()
|
void Wallet::startRefresh()
|
||||||
{
|
{
|
||||||
m_refreshEnabled = true;
|
m_refreshEnabled = true;
|
||||||
|
|
|
@ -206,6 +206,9 @@ public:
|
||||||
Q_INVOKABLE bool exportOutputs(const QString& path, bool all = false);
|
Q_INVOKABLE bool exportOutputs(const QString& path, bool all = false);
|
||||||
Q_INVOKABLE bool importOutputs(const QString& path);
|
Q_INVOKABLE bool importOutputs(const QString& path);
|
||||||
|
|
||||||
|
//! import a transaction
|
||||||
|
Q_INVOKABLE bool importTransaction(const QString& txid, const QVector<quint64>& output_indeces, quint64 height, quint64 timestamp, bool miner_tx, bool pool, bool double_spend_seen);
|
||||||
|
|
||||||
//! refreshes the wallet
|
//! refreshes the wallet
|
||||||
Q_INVOKABLE bool refresh(bool historyAndSubaddresses = false);
|
Q_INVOKABLE bool refresh(bool historyAndSubaddresses = false);
|
||||||
|
|
||||||
|
|
|
@ -19,6 +19,7 @@
|
||||||
#include "dialog/torinfodialog.h"
|
#include "dialog/torinfodialog.h"
|
||||||
#include "dialog/viewonlydialog.h"
|
#include "dialog/viewonlydialog.h"
|
||||||
#include "dialog/broadcasttxdialog.h"
|
#include "dialog/broadcasttxdialog.h"
|
||||||
|
#include "dialog/tximportdialog.h"
|
||||||
#include "utils/utils.h"
|
#include "utils/utils.h"
|
||||||
#include "utils/config.h"
|
#include "utils/config.h"
|
||||||
#include "utils/daemonrpc.h"
|
#include "utils/daemonrpc.h"
|
||||||
|
@ -495,6 +496,7 @@ void MainWindow::initMenu() {
|
||||||
connect(ui->actionLoadUnsignedTxFromClipboard, &QAction::triggered, this, &MainWindow::loadUnsignedTxFromClipboard);
|
connect(ui->actionLoadUnsignedTxFromClipboard, &QAction::triggered, this, &MainWindow::loadUnsignedTxFromClipboard);
|
||||||
connect(ui->actionLoadSignedTxFromFile, &QAction::triggered, this, &MainWindow::loadSignedTx);
|
connect(ui->actionLoadSignedTxFromFile, &QAction::triggered, this, &MainWindow::loadSignedTx);
|
||||||
connect(ui->actionLoadSignedTxFromText, &QAction::triggered, this, &MainWindow::loadSignedTxFromText);
|
connect(ui->actionLoadSignedTxFromText, &QAction::triggered, this, &MainWindow::loadSignedTxFromText);
|
||||||
|
connect(ui->actionImport_transaction, &QAction::triggered, this, &MainWindow::importTransaction);
|
||||||
|
|
||||||
// About screen
|
// About screen
|
||||||
connect(ui->actionAbout, &QAction::triggered, this, &MainWindow::menuAboutClicked);
|
connect(ui->actionAbout, &QAction::triggered, this, &MainWindow::menuAboutClicked);
|
||||||
|
@ -1296,6 +1298,19 @@ void MainWindow::createUnsignedTxDialog(UnsignedTransaction *tx) {
|
||||||
dialog->deleteLater();
|
dialog->deleteLater();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void MainWindow::importTransaction() {
|
||||||
|
|
||||||
|
auto result = QMessageBox::warning(this, "Warning", "Using this feature may allow a remote node to associate the transaction with your IP address.\n"
|
||||||
|
"\n"
|
||||||
|
"Connect to a trusted node or run Feather over Tor if network level metadata leakage is included in your threat model.",
|
||||||
|
QMessageBox::Ok | QMessageBox::Cancel);
|
||||||
|
if (result == QMessageBox::Ok) {
|
||||||
|
auto *dialog = new TxImportDialog(this, m_ctx);
|
||||||
|
dialog->exec();
|
||||||
|
dialog->deleteLater();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
MainWindow::~MainWindow() {
|
MainWindow::~MainWindow() {
|
||||||
delete ui;
|
delete ui;
|
||||||
}
|
}
|
||||||
|
|
|
@ -116,6 +116,7 @@ public slots:
|
||||||
void onAddContact(const QString &address, const QString &name);
|
void onAddContact(const QString &address, const QString &name);
|
||||||
void importContacts();
|
void importContacts();
|
||||||
void showRestoreHeightDialog();
|
void showRestoreHeightDialog();
|
||||||
|
void importTransaction();
|
||||||
|
|
||||||
// offline tx signing
|
// offline tx signing
|
||||||
void exportKeyImages();
|
void exportKeyImages();
|
||||||
|
|
|
@ -395,6 +395,7 @@
|
||||||
<addaction name="separator"/>
|
<addaction name="separator"/>
|
||||||
<addaction name="menuLoad_transaction"/>
|
<addaction name="menuLoad_transaction"/>
|
||||||
<addaction name="menuLoad_signed_transaction"/>
|
<addaction name="menuLoad_signed_transaction"/>
|
||||||
|
<addaction name="actionImport_transaction"/>
|
||||||
<addaction name="separator"/>
|
<addaction name="separator"/>
|
||||||
<addaction name="actionCalculator"/>
|
<addaction name="actionCalculator"/>
|
||||||
<addaction name="actionCreateDesktopEntry"/>
|
<addaction name="actionCreateDesktopEntry"/>
|
||||||
|
@ -664,6 +665,11 @@
|
||||||
<string>From text</string>
|
<string>From text</string>
|
||||||
</property>
|
</property>
|
||||||
</action>
|
</action>
|
||||||
|
<action name="actionImport_transaction">
|
||||||
|
<property name="text">
|
||||||
|
<string>Import transaction</string>
|
||||||
|
</property>
|
||||||
|
</action>
|
||||||
</widget>
|
</widget>
|
||||||
<layoutdefault spacing="6" margin="11"/>
|
<layoutdefault spacing="6" margin="11"/>
|
||||||
<customwidgets>
|
<customwidgets>
|
||||||
|
|
|
@ -23,6 +23,17 @@ void DaemonRpc::sendRawTransaction(const QString &tx_as_hex, bool do_not_relay,
|
||||||
connect(reply, &QNetworkReply::finished, std::bind(&DaemonRpc::onResponse, this, reply, Endpoint::SEND_RAW_TRANSACTION));
|
connect(reply, &QNetworkReply::finished, std::bind(&DaemonRpc::onResponse, this, reply, Endpoint::SEND_RAW_TRANSACTION));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void DaemonRpc::getTransactions(const QStringList &txs_hashes, bool decode_as_json, bool prune) {
|
||||||
|
QJsonObject req;
|
||||||
|
req["txs_hashes"] = QJsonArray::fromStringList(txs_hashes);
|
||||||
|
req["decode_as_json"] = decode_as_json;
|
||||||
|
req["prune"] = prune;
|
||||||
|
|
||||||
|
QString url = QString("%1/get_transactions").arg(m_daemonAddress);
|
||||||
|
QNetworkReply *reply = m_network->postJson(url, req);
|
||||||
|
connect(reply, &QNetworkReply::finished, std::bind(&DaemonRpc::onResponse, this, reply, Endpoint::GET_TRANSACTIONS));
|
||||||
|
}
|
||||||
|
|
||||||
void DaemonRpc::onResponse(QNetworkReply *reply, Endpoint endpoint) {
|
void DaemonRpc::onResponse(QNetworkReply *reply, Endpoint endpoint) {
|
||||||
const auto ok = reply->error() == QNetworkReply::NoError;
|
const auto ok = reply->error() == QNetworkReply::NoError;
|
||||||
const auto err = reply->errorString();
|
const auto err = reply->errorString();
|
||||||
|
|
|
@ -13,7 +13,8 @@ class DaemonRpc : public QObject {
|
||||||
|
|
||||||
public:
|
public:
|
||||||
enum Endpoint {
|
enum Endpoint {
|
||||||
SEND_RAW_TRANSACTION = 0
|
SEND_RAW_TRANSACTION = 0,
|
||||||
|
GET_TRANSACTIONS
|
||||||
};
|
};
|
||||||
|
|
||||||
struct DaemonResponse {
|
struct DaemonResponse {
|
||||||
|
@ -29,6 +30,7 @@ public:
|
||||||
explicit DaemonRpc(QObject *parent, UtilsNetworking *network, QString daemonAddress);
|
explicit DaemonRpc(QObject *parent, UtilsNetworking *network, QString daemonAddress);
|
||||||
|
|
||||||
void sendRawTransaction(const QString &tx_as_hex, bool do_not_relay = false, bool do_sanity_checks = true);
|
void sendRawTransaction(const QString &tx_as_hex, bool do_not_relay = false, bool do_sanity_checks = true);
|
||||||
|
void getTransactions(const QStringList &txs_hashes, bool decode_as_json = false, bool prune = false);
|
||||||
|
|
||||||
void setDaemonAddress(const QString &daemonAddress);
|
void setDaemonAddress(const QString &daemonAddress);
|
||||||
void setNetwork(UtilsNetworking *network);
|
void setNetwork(UtilsNetworking *network);
|
||||||
|
|
Loading…
Reference in a new issue