aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--src/node/transaction.h8
-rw-r--r--src/psbt.h4
-rw-r--r--src/qt/bitcoingui.cpp8
-rw-r--r--src/qt/bitcoingui.h3
-rw-r--r--src/qt/sendcoinsdialog.cpp136
-rw-r--r--src/qt/sendcoinsdialog.h3
-rw-r--r--src/qt/walletframe.cpp8
-rw-r--r--src/qt/walletframe.h3
-rw-r--r--src/qt/walletview.cpp78
-rw-r--r--src/qt/walletview.h2
-rw-r--r--src/rpc/rawtransaction.cpp6
-rw-r--r--src/util/system.cpp6
-rw-r--r--src/util/system.h8
13 files changed, 221 insertions, 52 deletions
diff --git a/src/node/transaction.h b/src/node/transaction.h
index 98d65bc4dd..6491700d44 100644
--- a/src/node/transaction.h
+++ b/src/node/transaction.h
@@ -6,11 +6,19 @@
#define BITCOIN_NODE_TRANSACTION_H
#include <attributes.h>
+#include <policy/feerate.h>
#include <primitives/transaction.h>
#include <util/error.h>
struct NodeContext;
+/** Maximum fee rate for sendrawtransaction and testmempoolaccept RPC calls.
+ * Also used by the GUI when broadcasting a completed PSBT.
+ * By default, a transaction with a fee rate higher than this will be rejected
+ * by these RPCs and the GUI. This can be overridden with the maxfeerate argument.
+ */
+static const CFeeRate DEFAULT_MAX_RAW_TX_FEE_RATE{COIN / 10};
+
/**
* Submit a transaction to the mempool and (optionally) relay it to all P2P peers.
*
diff --git a/src/psbt.h b/src/psbt.h
index 83a729217b..af57994f3a 100644
--- a/src/psbt.h
+++ b/src/psbt.h
@@ -40,6 +40,10 @@ static constexpr uint8_t PSBT_OUT_BIP32_DERIVATION = 0x02;
// as a 0 length key which indicates that this is the separator. The separator has no value.
static constexpr uint8_t PSBT_SEPARATOR = 0x00;
+// BIP 174 does not specify a maximum file size, but we set a limit anyway
+// to prevent reading a stream indefinately and running out of memory.
+const std::streamsize MAX_FILE_SIZE_PSBT = 100000000; // 100 MiB
+
/** A structure for PSBTs which contain per-input information */
struct PSBTInput
{
diff --git a/src/qt/bitcoingui.cpp b/src/qt/bitcoingui.cpp
index 75f3e9bf45..71e4d59ea7 100644
--- a/src/qt/bitcoingui.cpp
+++ b/src/qt/bitcoingui.cpp
@@ -317,6 +317,8 @@ void BitcoinGUI::createActions()
signMessageAction->setStatusTip(tr("Sign messages with your Bitcoin addresses to prove you own them"));
verifyMessageAction = new QAction(tr("&Verify message..."), this);
verifyMessageAction->setStatusTip(tr("Verify messages to ensure they were signed with specified Bitcoin addresses"));
+ m_load_psbt_action = new QAction(tr("Load PSBT..."), this);
+ m_load_psbt_action->setStatusTip(tr("Load Partially Signed Bitcoin Transaction"));
openRPCConsoleAction = new QAction(tr("Node window"), this);
openRPCConsoleAction->setStatusTip(tr("Open node debugging and diagnostic console"));
@@ -366,6 +368,7 @@ void BitcoinGUI::createActions()
connect(changePassphraseAction, &QAction::triggered, walletFrame, &WalletFrame::changePassphrase);
connect(signMessageAction, &QAction::triggered, [this]{ showNormalIfMinimized(); });
connect(signMessageAction, &QAction::triggered, [this]{ gotoSignMessageTab(); });
+ connect(m_load_psbt_action, &QAction::triggered, [this]{ gotoLoadPSBT(); });
connect(verifyMessageAction, &QAction::triggered, [this]{ showNormalIfMinimized(); });
connect(verifyMessageAction, &QAction::triggered, [this]{ gotoVerifyMessageTab(); });
connect(usedSendingAddressesAction, &QAction::triggered, walletFrame, &WalletFrame::usedSendingAddresses);
@@ -438,6 +441,7 @@ void BitcoinGUI::createMenuBar()
file->addAction(backupWalletAction);
file->addAction(signMessageAction);
file->addAction(verifyMessageAction);
+ file->addAction(m_load_psbt_action);
file->addSeparator();
}
file->addAction(quitAction);
@@ -854,6 +858,10 @@ void BitcoinGUI::gotoVerifyMessageTab(QString addr)
{
if (walletFrame) walletFrame->gotoVerifyMessageTab(addr);
}
+void BitcoinGUI::gotoLoadPSBT()
+{
+ if (walletFrame) walletFrame->gotoLoadPSBT();
+}
#endif // ENABLE_WALLET
void BitcoinGUI::updateNetworkState()
diff --git a/src/qt/bitcoingui.h b/src/qt/bitcoingui.h
index 9b5523f625..70366e12d1 100644
--- a/src/qt/bitcoingui.h
+++ b/src/qt/bitcoingui.h
@@ -135,6 +135,7 @@ private:
QAction* usedReceivingAddressesAction = nullptr;
QAction* signMessageAction = nullptr;
QAction* verifyMessageAction = nullptr;
+ QAction* m_load_psbt_action = nullptr;
QAction* aboutAction = nullptr;
QAction* receiveCoinsAction = nullptr;
QAction* receiveCoinsMenuAction = nullptr;
@@ -270,6 +271,8 @@ public Q_SLOTS:
void gotoSignMessageTab(QString addr = "");
/** Show Sign/Verify Message dialog and switch to verify message tab */
void gotoVerifyMessageTab(QString addr = "");
+ /** Show load Partially Signed Bitcoin Transaction dialog */
+ void gotoLoadPSBT();
/** Show open dialog */
void openClicked();
diff --git a/src/qt/sendcoinsdialog.cpp b/src/qt/sendcoinsdialog.cpp
index 345c258845..7b389a48d6 100644
--- a/src/qt/sendcoinsdialog.cpp
+++ b/src/qt/sendcoinsdialog.cpp
@@ -219,11 +219,8 @@ SendCoinsDialog::~SendCoinsDialog()
delete ui;
}
-void SendCoinsDialog::on_sendButton_clicked()
+bool SendCoinsDialog::PrepareSendText(QString& question_string, QString& informative_text, QString& detailed_text)
{
- if(!model || !model->getOptionsModel())
- return;
-
QList<SendCoinsRecipient> recipients;
bool valid = true;
@@ -246,7 +243,7 @@ void SendCoinsDialog::on_sendButton_clicked()
if(!valid || recipients.isEmpty())
{
- return;
+ return false;
}
fNewRecipientAllowed = false;
@@ -255,11 +252,11 @@ void SendCoinsDialog::on_sendButton_clicked()
{
// Unlock wallet was cancelled
fNewRecipientAllowed = true;
- return;
+ return false;
}
// prepare transaction for getting txFee earlier
- WalletModelTransaction currentTransaction(recipients);
+ m_current_transaction = MakeUnique<WalletModelTransaction>(recipients);
WalletModel::SendCoinsReturn prepareStatus;
// Always use a CCoinControl instance, use the CoinControlDialog instance if CoinControl has been enabled
@@ -269,22 +266,20 @@ void SendCoinsDialog::on_sendButton_clicked()
updateCoinControlState(ctrl);
- prepareStatus = model->prepareTransaction(currentTransaction, ctrl);
+ prepareStatus = model->prepareTransaction(*m_current_transaction, ctrl);
// process prepareStatus and on error generate message shown to user
processSendCoinsReturn(prepareStatus,
- BitcoinUnits::formatWithUnit(model->getOptionsModel()->getDisplayUnit(), currentTransaction.getTransactionFee()));
+ BitcoinUnits::formatWithUnit(model->getOptionsModel()->getDisplayUnit(), m_current_transaction->getTransactionFee()));
if(prepareStatus.status != WalletModel::OK) {
fNewRecipientAllowed = true;
- return;
+ return false;
}
- CAmount txFee = currentTransaction.getTransactionFee();
-
- // Format confirmation message
+ CAmount txFee = m_current_transaction->getTransactionFee();
QStringList formatted;
- for (const SendCoinsRecipient &rcp : currentTransaction.getRecipients())
+ for (const SendCoinsRecipient &rcp : m_current_transaction->getRecipients())
{
// generate amount string with wallet name in case of multiwallet
QString amount = BitcoinUnits::formatWithUnit(model->getOptionsModel()->getDisplayUnit(), rcp.amount);
@@ -311,72 +306,82 @@ void SendCoinsDialog::on_sendButton_clicked()
formatted.append(recipientElement);
}
- QString questionString;
if (model->wallet().privateKeysDisabled()) {
- questionString.append(tr("Do you want to draft this transaction?"));
+ question_string.append(tr("Do you want to draft this transaction?"));
} else {
- questionString.append(tr("Are you sure you want to send?"));
+ question_string.append(tr("Are you sure you want to send?"));
}
- questionString.append("<br /><span style='font-size:10pt;'>");
+ question_string.append("<br /><span style='font-size:10pt;'>");
if (model->wallet().privateKeysDisabled()) {
- questionString.append(tr("Please, review your transaction proposal. This will produce a Partially Signed Bitcoin Transaction (PSBT) which you can copy and then sign with e.g. an offline %1 wallet, or a PSBT-compatible hardware wallet.").arg(PACKAGE_NAME));
+ question_string.append(tr("Please, review your transaction proposal. This will produce a Partially Signed Bitcoin Transaction (PSBT) which you can save or copy and then sign with e.g. an offline %1 wallet, or a PSBT-compatible hardware wallet.").arg(PACKAGE_NAME));
} else {
- questionString.append(tr("Please, review your transaction."));
+ question_string.append(tr("Please, review your transaction."));
}
- questionString.append("</span>%1");
+ question_string.append("</span>%1");
if(txFee > 0)
{
// append fee string if a fee is required
- questionString.append("<hr /><b>");
- questionString.append(tr("Transaction fee"));
- questionString.append("</b>");
+ question_string.append("<hr /><b>");
+ question_string.append(tr("Transaction fee"));
+ question_string.append("</b>");
// append transaction size
- questionString.append(" (" + QString::number((double)currentTransaction.getTransactionSize() / 1000) + " kB): ");
+ question_string.append(" (" + QString::number((double)m_current_transaction->getTransactionSize() / 1000) + " kB): ");
// append transaction fee value
- questionString.append("<span style='color:#aa0000; font-weight:bold;'>");
- questionString.append(BitcoinUnits::formatHtmlWithUnit(model->getOptionsModel()->getDisplayUnit(), txFee));
- questionString.append("</span><br />");
+ question_string.append("<span style='color:#aa0000; font-weight:bold;'>");
+ question_string.append(BitcoinUnits::formatHtmlWithUnit(model->getOptionsModel()->getDisplayUnit(), txFee));
+ question_string.append("</span><br />");
// append RBF message according to transaction's signalling
- questionString.append("<span style='font-size:10pt; font-weight:normal;'>");
+ question_string.append("<span style='font-size:10pt; font-weight:normal;'>");
if (ui->optInRBF->isChecked()) {
- questionString.append(tr("You can increase the fee later (signals Replace-By-Fee, BIP-125)."));
+ question_string.append(tr("You can increase the fee later (signals Replace-By-Fee, BIP-125)."));
} else {
- questionString.append(tr("Not signalling Replace-By-Fee, BIP-125."));
+ question_string.append(tr("Not signalling Replace-By-Fee, BIP-125."));
}
- questionString.append("</span>");
+ question_string.append("</span>");
}
// add total amount in all subdivision units
- questionString.append("<hr />");
- CAmount totalAmount = currentTransaction.getTotalTransactionAmount() + txFee;
+ question_string.append("<hr />");
+ CAmount totalAmount = m_current_transaction->getTotalTransactionAmount() + txFee;
QStringList alternativeUnits;
for (const BitcoinUnits::Unit u : BitcoinUnits::availableUnits())
{
if(u != model->getOptionsModel()->getDisplayUnit())
alternativeUnits.append(BitcoinUnits::formatHtmlWithUnit(u, totalAmount));
}
- questionString.append(QString("<b>%1</b>: <b>%2</b>").arg(tr("Total Amount"))
+ question_string.append(QString("<b>%1</b>: <b>%2</b>").arg(tr("Total Amount"))
.arg(BitcoinUnits::formatHtmlWithUnit(model->getOptionsModel()->getDisplayUnit(), totalAmount)));
- questionString.append(QString("<br /><span style='font-size:10pt; font-weight:normal;'>(=%1)</span>")
+ question_string.append(QString("<br /><span style='font-size:10pt; font-weight:normal;'>(=%1)</span>")
.arg(alternativeUnits.join(" " + tr("or") + " ")));
- QString informative_text;
- QString detailed_text;
if (formatted.size() > 1) {
- questionString = questionString.arg("");
+ question_string = question_string.arg("");
informative_text = tr("To review recipient list click \"Show Details...\"");
detailed_text = formatted.join("\n\n");
} else {
- questionString = questionString.arg("<br /><br />" + formatted.at(0));
+ question_string = question_string.arg("<br /><br />" + formatted.at(0));
}
+
+ return true;
+}
+
+void SendCoinsDialog::on_sendButton_clicked()
+{
+ if(!model || !model->getOptionsModel())
+ return;
+
+ QString question_string, informative_text, detailed_text;
+ if (!PrepareSendText(question_string, informative_text, detailed_text)) return;
+ assert(m_current_transaction);
+
const QString confirmation = model->wallet().privateKeysDisabled() ? tr("Confirm transaction proposal") : tr("Confirm send coins");
- const QString confirmButtonText = model->wallet().privateKeysDisabled() ? tr("Copy PSBT to clipboard") : tr("Send");
- SendConfirmationDialog confirmationDialog(confirmation, questionString, informative_text, detailed_text, SEND_CONFIRM_DELAY, confirmButtonText, this);
+ const QString confirmButtonText = model->wallet().privateKeysDisabled() ? tr("Create Unsigned") : tr("Send");
+ SendConfirmationDialog confirmationDialog(confirmation, question_string, informative_text, detailed_text, SEND_CONFIRM_DELAY, confirmButtonText, this);
confirmationDialog.exec();
QMessageBox::StandardButton retval = static_cast<QMessageBox::StandardButton>(confirmationDialog.result());
@@ -388,7 +393,7 @@ void SendCoinsDialog::on_sendButton_clicked()
bool send_failure = false;
if (model->wallet().privateKeysDisabled()) {
- CMutableTransaction mtx = CMutableTransaction{*(currentTransaction.getWtx())};
+ CMutableTransaction mtx = CMutableTransaction{*(m_current_transaction->getWtx())};
PartiallySignedTransaction psbtx(mtx);
bool complete = false;
const TransactionError err = model->wallet().fillPSBT(SIGHASH_ALL, false /* sign */, true /* bip32derivs */, psbtx, complete);
@@ -398,15 +403,51 @@ void SendCoinsDialog::on_sendButton_clicked()
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);
+ QMessageBox msgBox;
+ msgBox.setText("Unsigned Transaction");
+ msgBox.setInformativeText("The PSBT has been copied to the clipboard. You can also save it.");
+ msgBox.setStandardButtons(QMessageBox::Save | QMessageBox::Discard);
+ msgBox.setDefaultButton(QMessageBox::Discard);
+ switch (msgBox.exec()) {
+ case QMessageBox::Save: {
+ QString selectedFilter;
+ QString fileNameSuggestion = "";
+ bool first = true;
+ for (const SendCoinsRecipient &rcp : m_current_transaction->getRecipients()) {
+ if (!first) {
+ fileNameSuggestion.append(" - ");
+ }
+ QString labelOrAddress = rcp.label.isEmpty() ? rcp.address : rcp.label;
+ QString amount = BitcoinUnits::formatWithUnit(model->getOptionsModel()->getDisplayUnit(), rcp.amount);
+ fileNameSuggestion.append(labelOrAddress + "-" + amount);
+ first = false;
+ }
+ fileNameSuggestion.append(".psbt");
+ QString filename = GUIUtil::getSaveFileName(this,
+ tr("Save Transaction Data"), fileNameSuggestion,
+ tr("Partially Signed Transaction (Binary) (*.psbt)"), &selectedFilter);
+ if (filename.isEmpty()) {
+ return;
+ }
+ std::ofstream out(filename.toLocal8Bit().data());
+ out << ssTx.str();
+ out.close();
+ Q_EMIT message(tr("PSBT saved"), "PSBT saved to disk", CClientUIInterface::MSG_INFORMATION);
+ break;
+ }
+ case QMessageBox::Discard:
+ break;
+ default:
+ assert(false);
+ }
} else {
// now send the prepared transaction
- WalletModel::SendCoinsReturn sendStatus = model->sendCoins(currentTransaction);
+ WalletModel::SendCoinsReturn sendStatus = model->sendCoins(*m_current_transaction);
// process sendStatus and on error generate message shown to user
processSendCoinsReturn(sendStatus);
if (sendStatus.status == WalletModel::OK) {
- Q_EMIT coinsSent(currentTransaction.getWtx()->GetHash());
+ Q_EMIT coinsSent(m_current_transaction->getWtx()->GetHash());
} else {
send_failure = true;
}
@@ -417,10 +458,13 @@ void SendCoinsDialog::on_sendButton_clicked()
coinControlUpdateLabels();
}
fNewRecipientAllowed = true;
+ m_current_transaction.reset();
}
void SendCoinsDialog::clear()
{
+ m_current_transaction.reset();
+
// Clear coin control settings
CoinControlDialog::coinControl()->UnSelectAll();
ui->checkBoxCoinControlChange->setChecked(false);
diff --git a/src/qt/sendcoinsdialog.h b/src/qt/sendcoinsdialog.h
index 86422c4030..36bc2a846b 100644
--- a/src/qt/sendcoinsdialog.h
+++ b/src/qt/sendcoinsdialog.h
@@ -60,6 +60,7 @@ private:
Ui::SendCoinsDialog *ui;
ClientModel *clientModel;
WalletModel *model;
+ std::unique_ptr<WalletModelTransaction> m_current_transaction;
bool fNewRecipientAllowed;
bool fFeeMinimized;
const PlatformStyle *platformStyle;
@@ -69,6 +70,8 @@ private:
// Additional parameter msgArg can be used via .arg(msgArg).
void processSendCoinsReturn(const WalletModel::SendCoinsReturn &sendCoinsReturn, const QString &msgArg = QString());
void minimizeFeeSection(bool fMinimize);
+ // Format confirmation message
+ bool PrepareSendText(QString& question_string, QString& informative_text, QString& detailed_text);
void updateFeeMinimizedLabel();
// Update the passed in CCoinControl with state from the GUI
void updateCoinControlState(CCoinControl& ctrl);
diff --git a/src/qt/walletframe.cpp b/src/qt/walletframe.cpp
index dac3326cc4..02a9583ae9 100644
--- a/src/qt/walletframe.cpp
+++ b/src/qt/walletframe.cpp
@@ -163,6 +163,14 @@ void WalletFrame::gotoVerifyMessageTab(QString addr)
walletView->gotoVerifyMessageTab(addr);
}
+void WalletFrame::gotoLoadPSBT()
+{
+ WalletView *walletView = currentWalletView();
+ if (walletView) {
+ walletView->gotoLoadPSBT();
+ }
+}
+
void WalletFrame::encryptWallet(bool status)
{
WalletView *walletView = currentWalletView();
diff --git a/src/qt/walletframe.h b/src/qt/walletframe.h
index 20fad08b0e..d90ade5005 100644
--- a/src/qt/walletframe.h
+++ b/src/qt/walletframe.h
@@ -78,6 +78,9 @@ public Q_SLOTS:
/** Show Sign/Verify Message dialog and switch to verify message tab */
void gotoVerifyMessageTab(QString addr = "");
+ /** Load Partially Signed Bitcoin Transaction */
+ void gotoLoadPSBT();
+
/** Encrypt the wallet */
void encryptWallet(bool status);
/** Backup the wallet */
diff --git a/src/qt/walletview.cpp b/src/qt/walletview.cpp
index 5b58e5fc1f..5d9b420df7 100644
--- a/src/qt/walletview.cpp
+++ b/src/qt/walletview.cpp
@@ -4,6 +4,9 @@
#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>
@@ -20,6 +23,7 @@
#include <interfaces/node.h>
#include <ui_interface.h>
+#include <util/strencodings.h>
#include <QAction>
#include <QActionGroup>
@@ -197,6 +201,80 @@ void WalletView::gotoVerifyMessageTab(QString addr)
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);
diff --git a/src/qt/walletview.h b/src/qt/walletview.h
index 9100807c0a..11f894e7f8 100644
--- a/src/qt/walletview.h
+++ b/src/qt/walletview.h
@@ -83,6 +83,8 @@ public Q_SLOTS:
void gotoSignMessageTab(QString addr = "");
/** Show Sign/Verify Message dialog and switch to verify message tab */
void gotoVerifyMessageTab(QString addr = "");
+ /** Load Partially Signed Bitcoin Transaction */
+ void gotoLoadPSBT();
/** Show incoming transaction notification for new transactions.
diff --git a/src/rpc/rawtransaction.cpp b/src/rpc/rawtransaction.cpp
index 2f4e2916e2..063ee1697c 100644
--- a/src/rpc/rawtransaction.cpp
+++ b/src/rpc/rawtransaction.cpp
@@ -40,12 +40,6 @@
#include <univalue.h>
-/** Maximum fee rate for sendrawtransaction and testmempoolaccept.
- * By default, a transaction with a fee rate higher than this will be rejected
- * by the RPCs. This can be overridden with the maxfeerate argument.
- */
-static const CFeeRate DEFAULT_MAX_RAW_TX_FEE_RATE{COIN / 10};
-
static void TxToJSON(const CTransaction& tx, const uint256 hashBlock, UniValue& entry)
{
// Call into TxToUniv() in bitcoin-common to decode the transaction hex.
diff --git a/src/util/system.cpp b/src/util/system.cpp
index 69a7be96dc..0790ea0d48 100644
--- a/src/util/system.cpp
+++ b/src/util/system.cpp
@@ -141,6 +141,12 @@ bool CheckDiskSpace(const fs::path& dir, uint64_t additional_bytes)
return free_bytes_available >= min_disk_space + additional_bytes;
}
+std::streampos GetFileSize(const char* path, std::streamsize max) {
+ std::ifstream file(path, std::ios::binary);
+ file.ignore(max);
+ return file.gcount();
+}
+
/**
* Interpret a string argument as a boolean.
*
diff --git a/src/util/system.h b/src/util/system.h
index 96f51e6074..a5eea5dfab 100644
--- a/src/util/system.h
+++ b/src/util/system.h
@@ -63,6 +63,14 @@ void UnlockDirectory(const fs::path& directory, const std::string& lockfile_name
bool DirIsWritable(const fs::path& directory);
bool CheckDiskSpace(const fs::path& dir, uint64_t additional_bytes = 0);
+/** Get the size of a file by scanning it.
+ *
+ * @param[in] path The file path
+ * @param[in] max Stop seeking beyond this limit
+ * @return The file size or max
+ */
+std::streampos GetFileSize(const char* path, std::streamsize max = std::numeric_limits<std::streamsize>::max());
+
/** Release all directory locks. This is used for unit testing only, at runtime
* the global destructor will take care of the locks.
*/