aboutsummaryrefslogtreecommitdiff
path: root/src/qt/sendcoinsdialog.cpp
diff options
context:
space:
mode:
authorSamuel Dobson <dobsonsa68@gmail.com>2020-04-23 10:38:35 +1200
committerSamuel Dobson <dobsonsa68@gmail.com>2020-04-23 13:16:23 +1200
commit4f802e59a044b208a52c21e6bcc908d38e1d3bd6 (patch)
tree42bc1bce6f002ada3f62473a98e94baedbd546eb /src/qt/sendcoinsdialog.cpp
parent47b94a337e1aad0c347fdfecba999b963ab51006 (diff)
parent764bfe4cba35c24f7627cc425d9e7eba56e98964 (diff)
downloadbitcoin-4f802e59a044b208a52c21e6bcc908d38e1d3bd6.tar.xz
Merge #17509: gui: save and load PSBT
764bfe4cba35c24f7627cc425d9e7eba56e98964 [psbt] add file size limit (Sjors Provoost) 1cd8dc2556b847e11a238b9e69493cd8fbeecc6c [gui] load PSBT (Sjors Provoost) f6895301f768220f3ea70231d5cc5b45ecbf4488 [gui] save PSBT to file (Sjors Provoost) 1d05a9d80b1211b47af465ba6958b0ec5a8c33ab Move DEFAULT_MAX_RAW_TX_FEE_RATE to node/transaction.h (Sjors Provoost) 86e22d23bb90383971a68ead0666f225ddd632fb [util] GetFileSize (Sjors Provoost) 6ab3aad9a51cc5e97a8e2ae7dbd5082272163c30 [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 https://github.com/bitcoin/bitcoin/pull/17509/commits/764bfe4cba35c24f7627cc425d9e7eba56e98964 achow101: ACK 764bfe4cba35c24f7627cc425d9e7eba56e98964 jb55: Tested ACK 764bfe4cba35c24f7627cc425d9e7eba56e98964 jonatack: ACK 764bfe4c promag: Code review ACK 764bfe4cba35c24f7627cc425d9e7eba56e98964. Tree-SHA512: d284ed6895f3a271fb8ff879aac388ad217ddc13f72074725608e1c3d6d90650f6dc9e9e254479544dd71fc111516b02c8ff92158153208dc40fb2726b37d063
Diffstat (limited to 'src/qt/sendcoinsdialog.cpp')
-rw-r--r--src/qt/sendcoinsdialog.cpp136
1 files changed, 90 insertions, 46 deletions
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);