Merge bitcoin-core/gui#441: Add Create Unsigned button to SendConfirmationDialog

742918c8ef qt: hide Create Unsigned button behind an expert mode option (Andrew Chow)
5c3b800acd qt: Add Create Unsigned button to SendConfirmationDialog (Andrew Chow)

Pull request description:

  Instead of having different buttons or changing button behavior for making a PSBT, just have SendConfirmationDialog return whether the user wants a PSBT or a broadcasted transaction. Since this dialog is used by both the bumpFeeAction and the SendCoinsDialog, changes to both to support the different behavior is needed. They will check the return value of the SendConfirmationDialog for whether a PSBT needs to be created instead of checking whether private keys are disabled.

  Strings used in this dialog are being slightly modified to work with both private keys enabled and disabled wallets.

  Moved from https://github.com/bitcoin/bitcoin/pull/18789

ACKs for top commit:
  jarolrod:
    ACK 742918c
  ryanofsky:
    Code review ACK 742918c8ef. Just suggested changes since last review. Looks great!
  hebasto:
    ACK 742918c8ef, tested on Linux Mint 20.2 (Qt 5.12.8).

Tree-SHA512: dd29f4364c7b4f15befe8fe63257b26187918786b005e0f8336183270b1a162680b93f6ced60f0285c6e607c084cc0d24950fc68a8f9c056521ede614041be66
This commit is contained in:
Hennadii Stepanov 2022-01-09 17:39:31 +02:00
commit 2e01b69860
No known key found for this signature in database
GPG key ID: 410108112E7EA81F
7 changed files with 73 additions and 28 deletions

View file

@ -252,6 +252,16 @@
</property>
</widget>
</item>
<item>
<widget class="QCheckBox" name="m_enable_psbt_controls">
<property name="text">
<string extracomment="An options window setting to enable PSBT controls.">Enable &amp;PSBT controls</string>
</property>
<property name="toolTip">
<string extracomment="Tooltip text for options window setting that enables PSBT controls.">Whether to show PSBT controls.</string>
</property>
</widget>
</item>
</layout>
</widget>
</item>

View file

@ -242,6 +242,7 @@ void OptionsDialog::setMapper()
mapper->addMapping(ui->coinControlFeatures, OptionsModel::CoinControlFeatures);
mapper->addMapping(ui->subFeeFromAmount, OptionsModel::SubFeeFromAmount);
mapper->addMapping(ui->externalSignerPath, OptionsModel::ExternalSignerPath);
mapper->addMapping(ui->m_enable_psbt_controls, OptionsModel::EnablePSBTControls);
/* Network */
mapper->addMapping(ui->mapPortUpnp, OptionsModel::MapPortUPnP);

View file

@ -83,6 +83,11 @@ void OptionsModel::Init(bool resetSettings)
settings.setValue("fCoinControlFeatures", false);
fCoinControlFeatures = settings.value("fCoinControlFeatures", false).toBool();
if (!settings.contains("enable_psbt_controls")) {
settings.setValue("enable_psbt_controls", false);
}
m_enable_psbt_controls = settings.value("enable_psbt_controls", false).toBool();
// These are shared with the core or have a command-line parameter
// and we want command-line parameters to overwrite the GUI settings.
//
@ -360,6 +365,8 @@ QVariant OptionsModel::data(const QModelIndex & index, int role) const
return m_use_embedded_monospaced_font;
case CoinControlFeatures:
return fCoinControlFeatures;
case EnablePSBTControls:
return settings.value("enable_psbt_controls");
case Prune:
return settings.value("bPrune");
case PruneSize:
@ -507,6 +514,10 @@ bool OptionsModel::setData(const QModelIndex & index, const QVariant & value, in
settings.setValue("fCoinControlFeatures", fCoinControlFeatures);
Q_EMIT coinControlFeaturesChanged(fCoinControlFeatures);
break;
case EnablePSBTControls:
m_enable_psbt_controls = value.toBool();
settings.setValue("enable_psbt_controls", m_enable_psbt_controls);
break;
case Prune:
if (settings.value("bPrune") != value) {
settings.setValue("bPrune", value);

View file

@ -69,6 +69,7 @@ public:
SpendZeroConfChange, // bool
Listen, // bool
Server, // bool
EnablePSBTControls, // bool
OptionIDRowCount,
};
@ -90,6 +91,7 @@ public:
bool getUseEmbeddedMonospacedFont() const { return m_use_embedded_monospaced_font; }
bool getCoinControlFeatures() const { return fCoinControlFeatures; }
bool getSubFeeFromAmount() const { return m_sub_fee_from_amount; }
bool getEnablePSBTControls() const { return m_enable_psbt_controls; }
const QString& getOverriddenByCommandLine() { return strOverriddenByCommandLine; }
/* Explicit setters */
@ -115,6 +117,7 @@ private:
bool m_use_embedded_monospaced_font;
bool fCoinControlFeatures;
bool m_sub_fee_from_amount;
bool m_enable_psbt_controls;
/* settings that were overridden by command-line */
QString strOverriddenByCommandLine;

View file

@ -324,16 +324,22 @@ bool SendCoinsDialog::PrepareSendText(QString& question_string, QString& informa
formatted.append(recipientElement);
}
if (model->wallet().privateKeysDisabled() && !model->wallet().hasExternalSigner()) {
question_string.append(tr("Do you want to draft this transaction?"));
} else {
question_string.append(tr("Are you sure you want to send?"));
}
/*: Message displayed when attempting to create a transaction. Cautionary text to prompt the user to verify
that the displayed transaction details represent the transaction the user intends to create. */
question_string.append(tr("Do you want to create this transaction?"));
question_string.append("<br /><span style='font-size:10pt;'>");
if (model->wallet().privateKeysDisabled() && !model->wallet().hasExternalSigner()) {
/*: Text to inform a user attempting to create a transaction of their current options. At this stage,
a user can only create a PSBT. This string is displayed when private keys are disabled and an external
signer is not available. */
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 if (model->getOptionsModel()->getEnablePSBTControls()) {
/*: Text to inform a user attempting to create a transaction of their current options. At this stage,
a user can send their transaction or create a PSBT. This string is displayed when both private keys
and PSBT controls are enabled. */
question_string.append(tr("Please, review your transaction. You can create and send this transaction or create 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 {
/*: Text to prompt a user to review the details of the transaction they are attempting to send. */
question_string.append(tr("Please, review your transaction."));
}
question_string.append("</span>%1");
@ -397,21 +403,20 @@ void SendCoinsDialog::sendButtonClicked([[maybe_unused]] bool checked)
if (!PrepareSendText(question_string, informative_text, detailed_text)) return;
assert(m_current_transaction);
const QString confirmation = model->wallet().privateKeysDisabled() && !model->wallet().hasExternalSigner() ? tr("Confirm transaction proposal") : tr("Confirm send coins");
const QString confirmButtonText = model->wallet().privateKeysDisabled() && !model->wallet().hasExternalSigner() ? tr("Create Unsigned") : tr("Sign and send");
auto confirmationDialog = new SendConfirmationDialog(confirmation, question_string, informative_text, detailed_text, SEND_CONFIRM_DELAY, confirmButtonText, this);
const QString confirmation = tr("Confirm send coins");
auto confirmationDialog = new SendConfirmationDialog(confirmation, question_string, informative_text, detailed_text, SEND_CONFIRM_DELAY, !model->wallet().privateKeysDisabled(), model->getOptionsModel()->getEnablePSBTControls(), this);
confirmationDialog->setAttribute(Qt::WA_DeleteOnClose);
// TODO: Replace QDialog::exec() with safer QDialog::show().
const auto retval = static_cast<QMessageBox::StandardButton>(confirmationDialog->exec());
if(retval != QMessageBox::Yes)
if(retval != QMessageBox::Yes && retval != QMessageBox::Save)
{
fNewRecipientAllowed = true;
return;
}
bool send_failure = false;
if (model->wallet().privateKeysDisabled()) {
if (retval == QMessageBox::Save) {
CMutableTransaction mtx = CMutableTransaction{*(m_current_transaction->getWtx())};
PartiallySignedTransaction psbtx(mtx);
bool complete = false;
@ -512,6 +517,7 @@ void SendCoinsDialog::sendButtonClicked([[maybe_unused]] bool checked)
assert(false);
} // msgBox.exec()
} else {
assert(!model->wallet().privateKeysDisabled());
// now send the prepared transaction
WalletModel::SendCoinsReturn sendStatus = model->sendCoins(*m_current_transaction);
// process sendStatus and on error generate message shown to user
@ -1031,8 +1037,8 @@ void SendCoinsDialog::coinControlUpdateLabels()
}
}
SendConfirmationDialog::SendConfirmationDialog(const QString& title, const QString& text, const QString& informative_text, const QString& detailed_text, int _secDelay, const QString& _confirmButtonText, QWidget* parent)
: QMessageBox(parent), secDelay(_secDelay), confirmButtonText(_confirmButtonText)
SendConfirmationDialog::SendConfirmationDialog(const QString& title, const QString& text, const QString& informative_text, const QString& detailed_text, int _secDelay, bool enable_send, bool always_show_unsigned, QWidget* parent)
: QMessageBox(parent), secDelay(_secDelay), m_enable_send(enable_send)
{
setIcon(QMessageBox::Question);
setWindowTitle(title); // On macOS, the window title is ignored (as required by the macOS Guidelines).
@ -1040,18 +1046,20 @@ SendConfirmationDialog::SendConfirmationDialog(const QString& title, const QStri
setInformativeText(informative_text);
setDetailedText(detailed_text);
setStandardButtons(QMessageBox::Yes | QMessageBox::Cancel);
if (always_show_unsigned || !enable_send) addButton(QMessageBox::Save);
setDefaultButton(QMessageBox::Cancel);
yesButton = button(QMessageBox::Yes);
if (confirmButtonText.isEmpty()) {
confirmButtonText = yesButton->text();
}
updateYesButton();
m_psbt_button = button(QMessageBox::Save);
updateButtons();
connect(&countDownTimer, &QTimer::timeout, this, &SendConfirmationDialog::countDown);
}
int SendConfirmationDialog::exec()
{
updateYesButton();
updateButtons();
countDownTimer.start(1000);
return QMessageBox::exec();
}
@ -1059,7 +1067,7 @@ int SendConfirmationDialog::exec()
void SendConfirmationDialog::countDown()
{
secDelay--;
updateYesButton();
updateButtons();
if(secDelay <= 0)
{
@ -1067,16 +1075,24 @@ void SendConfirmationDialog::countDown()
}
}
void SendConfirmationDialog::updateYesButton()
void SendConfirmationDialog::updateButtons()
{
if(secDelay > 0)
{
yesButton->setEnabled(false);
yesButton->setText(confirmButtonText + " (" + QString::number(secDelay) + ")");
yesButton->setText(confirmButtonText + (m_enable_send ? (" (" + QString::number(secDelay) + ")") : QString("")));
if (m_psbt_button) {
m_psbt_button->setEnabled(false);
m_psbt_button->setText(m_psbt_button_text + " (" + QString::number(secDelay) + ")");
}
}
else
{
yesButton->setEnabled(true);
yesButton->setEnabled(m_enable_send);
yesButton->setText(confirmButtonText);
if (m_psbt_button) {
m_psbt_button->setEnabled(true);
m_psbt_button->setText(m_psbt_button_text);
}
}
}

View file

@ -114,18 +114,21 @@ class SendConfirmationDialog : public QMessageBox
Q_OBJECT
public:
SendConfirmationDialog(const QString& title, const QString& text, const QString& informative_text = "", const QString& detailed_text = "", int secDelay = SEND_CONFIRM_DELAY, const QString& confirmText = "", QWidget* parent = nullptr);
SendConfirmationDialog(const QString& title, const QString& text, const QString& informative_text = "", const QString& detailed_text = "", int secDelay = SEND_CONFIRM_DELAY, bool enable_send = true, bool always_show_unsigned = true, QWidget* parent = nullptr);
int exec() override;
private Q_SLOTS:
void countDown();
void updateYesButton();
void updateButtons();
private:
QAbstractButton *yesButton;
QAbstractButton *m_psbt_button;
QTimer countDownTimer;
int secDelay;
QString confirmButtonText;
QString confirmButtonText{tr("Send")};
bool m_enable_send;
QString m_psbt_button_text{tr("Create Unsigned")};
};
#endif // BITCOIN_QT_SENDCOINSDIALOG_H

View file

@ -480,10 +480,9 @@ bool WalletModel::bumpFee(uint256 hash, uint256& new_hash)
return false;
}
const bool create_psbt = m_wallet->privateKeysDisabled();
// allow a user based fee verification
QString questionString = create_psbt ? tr("Do you want to draft a transaction with fee increase?") : tr("Do you want to increase the fee?");
/*: Asks a user if they would like to manually increase the fee of a transaction that has already been created. */
QString questionString = tr("Do you want to increase the fee?");
questionString.append("<br />");
questionString.append("<table style=\"text-align: left;\">");
questionString.append("<tr><td>");
@ -506,13 +505,13 @@ bool WalletModel::bumpFee(uint256 hash, uint256& new_hash)
questionString.append(tr("Warning: This may pay the additional fee by reducing change outputs or adding inputs, when necessary. It may add a new change output if one does not already exist. These changes may potentially leak privacy."));
}
auto confirmationDialog = new SendConfirmationDialog(tr("Confirm fee bump"), questionString);
auto confirmationDialog = new SendConfirmationDialog(tr("Confirm fee bump"), questionString, "", "", SEND_CONFIRM_DELAY, !m_wallet->privateKeysDisabled(), getOptionsModel()->getEnablePSBTControls(), nullptr);
confirmationDialog->setAttribute(Qt::WA_DeleteOnClose);
// TODO: Replace QDialog::exec() with safer QDialog::show().
const auto retval = static_cast<QMessageBox::StandardButton>(confirmationDialog->exec());
// cancel sign&broadcast if user doesn't want to bump the fee
if (retval != QMessageBox::Yes) {
if (retval != QMessageBox::Yes && retval != QMessageBox::Save) {
return false;
}
@ -523,7 +522,7 @@ bool WalletModel::bumpFee(uint256 hash, uint256& new_hash)
}
// Short-circuit if we are returning a bumped transaction PSBT to clipboard
if (create_psbt) {
if (retval == QMessageBox::Save) {
PartiallySignedTransaction psbtx(mtx);
bool complete = false;
const TransactionError err = wallet().fillPSBT(SIGHASH_ALL, false /* sign */, true /* bip32derivs */, nullptr, psbtx, complete);
@ -539,6 +538,8 @@ bool WalletModel::bumpFee(uint256 hash, uint256& new_hash)
return true;
}
assert(!m_wallet->privateKeysDisabled());
// sign bumped transaction
if (!m_wallet->signBumpTransaction(mtx)) {
QMessageBox::critical(nullptr, tr("Fee bump error"), tr("Can't sign transaction."));