Merge #17509: gui: save and load PSBT

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-ACK  764bfe4cba
  achow101:
    ACK 764bfe4cba
  jb55:
    Tested ACK 764bfe4cba
  jonatack:
    ACK 764bfe4c
  promag:
    Code review ACK 764bfe4cba.

Tree-SHA512: d284ed6895f3a271fb8ff879aac388ad217ddc13f72074725608e1c3d6d90650f6dc9e9e254479544dd71fc111516b02c8ff92158153208dc40fb2726b37d063
This commit is contained in:
Samuel Dobson 2020-04-23 10:38:35 +12:00
commit 4f802e59a0
No known key found for this signature in database
GPG Key ID: D300116E1C875A3D
13 changed files with 221 additions and 52 deletions

View File

@ -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.
*

View File

@ -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
{

View File

@ -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()

View File

@ -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();

View File

@ -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);

View File

@ -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);

View File

@ -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();

View File

@ -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 */

View File

@ -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);

View File

@ -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.

View File

@ -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.

View File

@ -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.
*

View File

@ -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.
*/