mirror of
https://github.com/Retropex/bitcoin.git
synced 2025-05-24 11:02:37 +02:00

764bfe4cba
[psbt] add file size limit (Sjors Provoost)1cd8dc2556
[gui] load PSBT (Sjors Provoost)f6895301f7
[gui] save PSBT to file (Sjors Provoost)1d05a9d80b
Move DEFAULT_MAX_RAW_TX_FEE_RATE to node/transaction.h (Sjors Provoost)86e22d23bb
[util] GetFileSize (Sjors Provoost)6ab3aad9a5
[gui] send dialog: split on_sendButton_clicked (Sjors Provoost) Pull request description: This adds: * a dialog after Create Unsigned, which lets you save a PSBT file in binary format, e.g. to an SD card * a "Load PSBT" menu entry lets you pick a PSBT file. We broadcast the transaction if complete ## Save flow <img width="482" alt="Schermafbeelding 2020-01-04 om 20 39 34" src="https://user-images.githubusercontent.com/10217/71765684-ba60d580-2f32-11ea-8dea-0c4398eb6e15.png"> <img width="287" alt="Schermafbeelding 2020-01-04 om 20 40 35" src="https://user-images.githubusercontent.com/10217/71765677-a0bf8e00-2f32-11ea-8172-12dfd34a89f3.png"> <img width="594" alt="Schermafbeelding 2020-01-04 om 20 41 12" src="https://user-images.githubusercontent.com/10217/71765681-aa48f600-2f32-11ea-8e2c-c4f6bf9f5309.png"> <img width="632" alt="Schermafbeelding 2020-01-04 om 20 41 28" src="https://user-images.githubusercontent.com/10217/71765691-d19fc300-2f32-11ea-97ff-70f5dd59987a.png"> By default the file name contains the destination address(es) and amount(s). We only use the binary format for files, in order to avoid compatibility hell. If we do want to add base64 file format support, we should use a different extension for that (`.psbt64`?). ## Load flow Select a file: <img width="649" alt="Schermafbeelding 2020-01-04 om 21 08 57" src="https://user-images.githubusercontent.com/10217/71766089-2ba28780-2f37-11ea-875d-074794b5707d.png"> Offer to send if complete: <img width="308" alt="Schermafbeelding 2020-01-04 om 21 09 06" src="https://user-images.githubusercontent.com/10217/71766088-2a715a80-2f37-11ea-807d-394c8b840c59.png"> Tell user if signatures are missing, offer to copy to clipboard: <img width="308" alt="Schermafbeelding 2020-01-04 om 21 15 57" src="https://user-images.githubusercontent.com/10217/71766115-702e2300-2f37-11ea-9f62-a6ede499c0fa.png"> Incomplete for another reason: <img width="309" alt="Schermafbeelding 2020-01-04 om 21 07 51" src="https://user-images.githubusercontent.com/10217/71766090-2c3b1e00-2f37-11ea-8a22-6188377b67a1.png"> ACKs for top commit: instagibbs: re-ACK764bfe4cba
achow101: ACK764bfe4cba
jb55: Tested ACK764bfe4cba
jonatack: ACK764bfe4c
promag: Code review ACK764bfe4cba
. Tree-SHA512: d284ed6895f3a271fb8ff879aac388ad217ddc13f72074725608e1c3d6d90650f6dc9e9e254479544dd71fc111516b02c8ff92158153208dc40fb2726b37d063
387 lines
14 KiB
C++
387 lines
14 KiB
C++
// Copyright (c) 2011-2020 The Bitcoin Core developers
|
|
// Distributed under the MIT software license, see the accompanying
|
|
// file COPYING or http://www.opensource.org/licenses/mit-license.php.
|
|
|
|
#include <qt/walletview.h>
|
|
|
|
#include <node/psbt.h>
|
|
#include <node/transaction.h>
|
|
#include <policy/policy.h>
|
|
#include <qt/addressbookpage.h>
|
|
#include <qt/askpassphrasedialog.h>
|
|
#include <qt/clientmodel.h>
|
|
#include <qt/guiutil.h>
|
|
#include <qt/optionsmodel.h>
|
|
#include <qt/overviewpage.h>
|
|
#include <qt/platformstyle.h>
|
|
#include <qt/receivecoinsdialog.h>
|
|
#include <qt/sendcoinsdialog.h>
|
|
#include <qt/signverifymessagedialog.h>
|
|
#include <qt/transactiontablemodel.h>
|
|
#include <qt/transactionview.h>
|
|
#include <qt/walletmodel.h>
|
|
|
|
#include <interfaces/node.h>
|
|
#include <ui_interface.h>
|
|
#include <util/strencodings.h>
|
|
|
|
#include <QAction>
|
|
#include <QActionGroup>
|
|
#include <QFileDialog>
|
|
#include <QHBoxLayout>
|
|
#include <QProgressDialog>
|
|
#include <QPushButton>
|
|
#include <QVBoxLayout>
|
|
|
|
WalletView::WalletView(const PlatformStyle *_platformStyle, QWidget *parent):
|
|
QStackedWidget(parent),
|
|
clientModel(nullptr),
|
|
walletModel(nullptr),
|
|
platformStyle(_platformStyle)
|
|
{
|
|
// Create tabs
|
|
overviewPage = new OverviewPage(platformStyle);
|
|
|
|
transactionsPage = new QWidget(this);
|
|
QVBoxLayout *vbox = new QVBoxLayout();
|
|
QHBoxLayout *hbox_buttons = new QHBoxLayout();
|
|
transactionView = new TransactionView(platformStyle, this);
|
|
vbox->addWidget(transactionView);
|
|
QPushButton *exportButton = new QPushButton(tr("&Export"), this);
|
|
exportButton->setToolTip(tr("Export the data in the current tab to a file"));
|
|
if (platformStyle->getImagesOnButtons()) {
|
|
exportButton->setIcon(platformStyle->SingleColorIcon(":/icons/export"));
|
|
}
|
|
hbox_buttons->addStretch();
|
|
hbox_buttons->addWidget(exportButton);
|
|
vbox->addLayout(hbox_buttons);
|
|
transactionsPage->setLayout(vbox);
|
|
|
|
receiveCoinsPage = new ReceiveCoinsDialog(platformStyle);
|
|
sendCoinsPage = new SendCoinsDialog(platformStyle);
|
|
|
|
usedSendingAddressesPage = new AddressBookPage(platformStyle, AddressBookPage::ForEditing, AddressBookPage::SendingTab, this);
|
|
usedReceivingAddressesPage = new AddressBookPage(platformStyle, AddressBookPage::ForEditing, AddressBookPage::ReceivingTab, this);
|
|
|
|
addWidget(overviewPage);
|
|
addWidget(transactionsPage);
|
|
addWidget(receiveCoinsPage);
|
|
addWidget(sendCoinsPage);
|
|
|
|
connect(overviewPage, &OverviewPage::transactionClicked, this, &WalletView::transactionClicked);
|
|
// Clicking on a transaction on the overview pre-selects the transaction on the transaction history page
|
|
connect(overviewPage, &OverviewPage::transactionClicked, transactionView, static_cast<void (TransactionView::*)(const QModelIndex&)>(&TransactionView::focusTransaction));
|
|
|
|
connect(overviewPage, &OverviewPage::outOfSyncWarningClicked, this, &WalletView::requestedSyncWarningInfo);
|
|
|
|
connect(sendCoinsPage, &SendCoinsDialog::coinsSent, this, &WalletView::coinsSent);
|
|
// Highlight transaction after send
|
|
connect(sendCoinsPage, &SendCoinsDialog::coinsSent, transactionView, static_cast<void (TransactionView::*)(const uint256&)>(&TransactionView::focusTransaction));
|
|
|
|
// Clicking on "Export" allows to export the transaction list
|
|
connect(exportButton, &QPushButton::clicked, transactionView, &TransactionView::exportClicked);
|
|
|
|
// Pass through messages from sendCoinsPage
|
|
connect(sendCoinsPage, &SendCoinsDialog::message, this, &WalletView::message);
|
|
// Pass through messages from transactionView
|
|
connect(transactionView, &TransactionView::message, this, &WalletView::message);
|
|
}
|
|
|
|
WalletView::~WalletView()
|
|
{
|
|
}
|
|
|
|
void WalletView::setClientModel(ClientModel *_clientModel)
|
|
{
|
|
this->clientModel = _clientModel;
|
|
|
|
overviewPage->setClientModel(_clientModel);
|
|
sendCoinsPage->setClientModel(_clientModel);
|
|
}
|
|
|
|
void WalletView::setWalletModel(WalletModel *_walletModel)
|
|
{
|
|
this->walletModel = _walletModel;
|
|
|
|
// Put transaction list in tabs
|
|
transactionView->setModel(_walletModel);
|
|
overviewPage->setWalletModel(_walletModel);
|
|
receiveCoinsPage->setModel(_walletModel);
|
|
sendCoinsPage->setModel(_walletModel);
|
|
usedReceivingAddressesPage->setModel(_walletModel ? _walletModel->getAddressTableModel() : nullptr);
|
|
usedSendingAddressesPage->setModel(_walletModel ? _walletModel->getAddressTableModel() : nullptr);
|
|
|
|
if (_walletModel)
|
|
{
|
|
// Receive and pass through messages from wallet model
|
|
connect(_walletModel, &WalletModel::message, this, &WalletView::message);
|
|
|
|
// Handle changes in encryption status
|
|
connect(_walletModel, &WalletModel::encryptionStatusChanged, this, &WalletView::encryptionStatusChanged);
|
|
updateEncryptionStatus();
|
|
|
|
// update HD status
|
|
Q_EMIT hdEnabledStatusChanged();
|
|
|
|
// Balloon pop-up for new transaction
|
|
connect(_walletModel->getTransactionTableModel(), &TransactionTableModel::rowsInserted, this, &WalletView::processNewTransaction);
|
|
|
|
// Ask for passphrase if needed
|
|
connect(_walletModel, &WalletModel::requireUnlock, this, &WalletView::unlockWallet);
|
|
|
|
// Show progress dialog
|
|
connect(_walletModel, &WalletModel::showProgress, this, &WalletView::showProgress);
|
|
}
|
|
}
|
|
|
|
void WalletView::processNewTransaction(const QModelIndex& parent, int start, int /*end*/)
|
|
{
|
|
// Prevent balloon-spam when initial block download is in progress
|
|
if (!walletModel || !clientModel || clientModel->node().isInitialBlockDownload())
|
|
return;
|
|
|
|
TransactionTableModel *ttm = walletModel->getTransactionTableModel();
|
|
if (!ttm || ttm->processingQueuedTransactions())
|
|
return;
|
|
|
|
QString date = ttm->index(start, TransactionTableModel::Date, parent).data().toString();
|
|
qint64 amount = ttm->index(start, TransactionTableModel::Amount, parent).data(Qt::EditRole).toULongLong();
|
|
QString type = ttm->index(start, TransactionTableModel::Type, parent).data().toString();
|
|
QModelIndex index = ttm->index(start, 0, parent);
|
|
QString address = ttm->data(index, TransactionTableModel::AddressRole).toString();
|
|
QString label = GUIUtil::HtmlEscape(ttm->data(index, TransactionTableModel::LabelRole).toString());
|
|
|
|
Q_EMIT incomingTransaction(date, walletModel->getOptionsModel()->getDisplayUnit(), amount, type, address, label, GUIUtil::HtmlEscape(walletModel->getWalletName()));
|
|
}
|
|
|
|
void WalletView::gotoOverviewPage()
|
|
{
|
|
setCurrentWidget(overviewPage);
|
|
}
|
|
|
|
void WalletView::gotoHistoryPage()
|
|
{
|
|
setCurrentWidget(transactionsPage);
|
|
}
|
|
|
|
void WalletView::gotoReceiveCoinsPage()
|
|
{
|
|
setCurrentWidget(receiveCoinsPage);
|
|
}
|
|
|
|
void WalletView::gotoSendCoinsPage(QString addr)
|
|
{
|
|
setCurrentWidget(sendCoinsPage);
|
|
|
|
if (!addr.isEmpty())
|
|
sendCoinsPage->setAddress(addr);
|
|
}
|
|
|
|
void WalletView::gotoSignMessageTab(QString addr)
|
|
{
|
|
// calls show() in showTab_SM()
|
|
SignVerifyMessageDialog *signVerifyMessageDialog = new SignVerifyMessageDialog(platformStyle, this);
|
|
signVerifyMessageDialog->setAttribute(Qt::WA_DeleteOnClose);
|
|
signVerifyMessageDialog->setModel(walletModel);
|
|
signVerifyMessageDialog->showTab_SM(true);
|
|
|
|
if (!addr.isEmpty())
|
|
signVerifyMessageDialog->setAddress_SM(addr);
|
|
}
|
|
|
|
void WalletView::gotoVerifyMessageTab(QString addr)
|
|
{
|
|
// calls show() in showTab_VM()
|
|
SignVerifyMessageDialog *signVerifyMessageDialog = new SignVerifyMessageDialog(platformStyle, this);
|
|
signVerifyMessageDialog->setAttribute(Qt::WA_DeleteOnClose);
|
|
signVerifyMessageDialog->setModel(walletModel);
|
|
signVerifyMessageDialog->showTab_VM(true);
|
|
|
|
if (!addr.isEmpty())
|
|
signVerifyMessageDialog->setAddress_VM(addr);
|
|
}
|
|
|
|
void WalletView::gotoLoadPSBT()
|
|
{
|
|
QString filename = GUIUtil::getOpenFileName(this,
|
|
tr("Load Transaction Data"), QString(),
|
|
tr("Partially Signed Transaction (*.psbt)"), nullptr);
|
|
if (filename.isEmpty()) return;
|
|
if (GetFileSize(filename.toLocal8Bit().data(), MAX_FILE_SIZE_PSBT) == MAX_FILE_SIZE_PSBT) {
|
|
Q_EMIT message(tr("Error"), tr("PSBT file must be smaller than 100 MiB"), CClientUIInterface::MSG_ERROR);
|
|
return;
|
|
}
|
|
std::ifstream in(filename.toLocal8Bit().data(), std::ios::binary);
|
|
std::string data(std::istreambuf_iterator<char>{in}, {});
|
|
|
|
std::string error;
|
|
PartiallySignedTransaction psbtx;
|
|
if (!DecodeRawPSBT(psbtx, data, error)) {
|
|
Q_EMIT message(tr("Error"), tr("Unable to decode PSBT file") + "\n" + QString::fromStdString(error), CClientUIInterface::MSG_ERROR);
|
|
return;
|
|
}
|
|
|
|
CMutableTransaction mtx;
|
|
bool complete = false;
|
|
PSBTAnalysis analysis = AnalyzePSBT(psbtx);
|
|
QMessageBox msgBox;
|
|
msgBox.setText("PSBT");
|
|
switch (analysis.next) {
|
|
case PSBTRole::CREATOR:
|
|
case PSBTRole::UPDATER:
|
|
msgBox.setInformativeText("PSBT is incomplete. Copy to clipboard for manual inspection?");
|
|
break;
|
|
case PSBTRole::SIGNER:
|
|
msgBox.setInformativeText("Transaction needs more signatures. Copy to clipboard?");
|
|
break;
|
|
case PSBTRole::FINALIZER:
|
|
case PSBTRole::EXTRACTOR:
|
|
complete = FinalizeAndExtractPSBT(psbtx, mtx);
|
|
if (complete) {
|
|
msgBox.setInformativeText(tr("Would you like to send this transaction?"));
|
|
} else {
|
|
// The analyzer missed something, e.g. if there are final_scriptSig/final_scriptWitness
|
|
// but with invalid signatures.
|
|
msgBox.setInformativeText(tr("There was an unexpected problem processing the PSBT. Copy to clipboard for manual inspection?"));
|
|
}
|
|
}
|
|
|
|
msgBox.setStandardButtons(QMessageBox::Yes | QMessageBox::Cancel);
|
|
switch (msgBox.exec()) {
|
|
case QMessageBox::Yes: {
|
|
if (complete) {
|
|
std::string err_string;
|
|
CTransactionRef tx = MakeTransactionRef(mtx);
|
|
|
|
TransactionError result = BroadcastTransaction(*clientModel->node().context(), tx, err_string, DEFAULT_MAX_RAW_TX_FEE_RATE.GetFeePerK(), /* relay */ true, /* wait_callback */ false);
|
|
if (result == TransactionError::OK) {
|
|
Q_EMIT message(tr("Success"), tr("Broadcasted transaction sucessfully."), CClientUIInterface::MSG_INFORMATION | CClientUIInterface::MODAL);
|
|
} else {
|
|
Q_EMIT message(tr("Error"), QString::fromStdString(err_string), CClientUIInterface::MSG_ERROR);
|
|
}
|
|
} else {
|
|
// Serialize the PSBT
|
|
CDataStream ssTx(SER_NETWORK, PROTOCOL_VERSION);
|
|
ssTx << psbtx;
|
|
GUIUtil::setClipboard(EncodeBase64(ssTx.str()).c_str());
|
|
Q_EMIT message(tr("PSBT copied"), "Copied to clipboard", CClientUIInterface::MSG_INFORMATION);
|
|
return;
|
|
}
|
|
}
|
|
case QMessageBox::Cancel:
|
|
break;
|
|
default:
|
|
assert(false);
|
|
}
|
|
}
|
|
|
|
bool WalletView::handlePaymentRequest(const SendCoinsRecipient& recipient)
|
|
{
|
|
return sendCoinsPage->handlePaymentRequest(recipient);
|
|
}
|
|
|
|
void WalletView::showOutOfSyncWarning(bool fShow)
|
|
{
|
|
overviewPage->showOutOfSyncWarning(fShow);
|
|
}
|
|
|
|
void WalletView::updateEncryptionStatus()
|
|
{
|
|
Q_EMIT encryptionStatusChanged();
|
|
}
|
|
|
|
void WalletView::encryptWallet(bool status)
|
|
{
|
|
if(!walletModel)
|
|
return;
|
|
AskPassphraseDialog dlg(status ? AskPassphraseDialog::Encrypt : AskPassphraseDialog::Decrypt, this);
|
|
dlg.setModel(walletModel);
|
|
dlg.exec();
|
|
|
|
updateEncryptionStatus();
|
|
}
|
|
|
|
void WalletView::backupWallet()
|
|
{
|
|
QString filename = GUIUtil::getSaveFileName(this,
|
|
tr("Backup Wallet"), QString(),
|
|
tr("Wallet Data (*.dat)"), nullptr);
|
|
|
|
if (filename.isEmpty())
|
|
return;
|
|
|
|
if (!walletModel->wallet().backupWallet(filename.toLocal8Bit().data())) {
|
|
Q_EMIT message(tr("Backup Failed"), tr("There was an error trying to save the wallet data to %1.").arg(filename),
|
|
CClientUIInterface::MSG_ERROR);
|
|
}
|
|
else {
|
|
Q_EMIT message(tr("Backup Successful"), tr("The wallet data was successfully saved to %1.").arg(filename),
|
|
CClientUIInterface::MSG_INFORMATION);
|
|
}
|
|
}
|
|
|
|
void WalletView::changePassphrase()
|
|
{
|
|
AskPassphraseDialog dlg(AskPassphraseDialog::ChangePass, this);
|
|
dlg.setModel(walletModel);
|
|
dlg.exec();
|
|
}
|
|
|
|
void WalletView::unlockWallet()
|
|
{
|
|
if(!walletModel)
|
|
return;
|
|
// Unlock wallet when requested by wallet model
|
|
if (walletModel->getEncryptionStatus() == WalletModel::Locked)
|
|
{
|
|
AskPassphraseDialog dlg(AskPassphraseDialog::Unlock, this);
|
|
dlg.setModel(walletModel);
|
|
dlg.exec();
|
|
}
|
|
}
|
|
|
|
void WalletView::usedSendingAddresses()
|
|
{
|
|
if(!walletModel)
|
|
return;
|
|
|
|
GUIUtil::bringToFront(usedSendingAddressesPage);
|
|
}
|
|
|
|
void WalletView::usedReceivingAddresses()
|
|
{
|
|
if(!walletModel)
|
|
return;
|
|
|
|
GUIUtil::bringToFront(usedReceivingAddressesPage);
|
|
}
|
|
|
|
void WalletView::showProgress(const QString &title, int nProgress)
|
|
{
|
|
if (nProgress == 0) {
|
|
progressDialog = new QProgressDialog(title, tr("Cancel"), 0, 100);
|
|
GUIUtil::PolishProgressDialog(progressDialog);
|
|
progressDialog->setWindowModality(Qt::ApplicationModal);
|
|
progressDialog->setMinimumDuration(0);
|
|
progressDialog->setAutoClose(false);
|
|
progressDialog->setValue(0);
|
|
} else if (nProgress == 100) {
|
|
if (progressDialog) {
|
|
progressDialog->close();
|
|
progressDialog->deleteLater();
|
|
progressDialog = nullptr;
|
|
}
|
|
} else if (progressDialog) {
|
|
if (progressDialog->wasCanceled()) {
|
|
getWalletModel()->wallet().abortRescan();
|
|
} else {
|
|
progressDialog->setValue(nProgress);
|
|
}
|
|
}
|
|
}
|
|
|
|
void WalletView::requestedSyncWarningInfo()
|
|
{
|
|
Q_EMIT outOfSyncWarningClicked();
|
|
}
|