diff options
Diffstat (limited to 'src')
-rw-r--r-- | src/bitcoin-wallet.cpp | 2 | ||||
-rw-r--r-- | src/interfaces/node.cpp | 1 | ||||
-rw-r--r-- | src/interfaces/node.h | 3 | ||||
-rw-r--r-- | src/qt/askpassphrasedialog.cpp | 4 | ||||
-rw-r--r-- | src/qt/bitcoin.cpp | 15 | ||||
-rw-r--r-- | src/qt/bitcoin.h | 2 | ||||
-rw-r--r-- | src/qt/bitcoingui.cpp | 4 | ||||
-rw-r--r-- | src/qt/createwalletdialog.cpp | 9 | ||||
-rw-r--r-- | src/qt/createwalletdialog.h | 6 | ||||
-rw-r--r-- | src/qt/forms/askpassphrasedialog.ui | 2 | ||||
-rw-r--r-- | src/qt/forms/createwalletdialog.ui | 2 | ||||
-rw-r--r-- | src/qt/forms/intro.ui | 10 | ||||
-rw-r--r-- | src/qt/intro.cpp | 18 | ||||
-rw-r--r-- | src/qt/intro.h | 3 | ||||
-rw-r--r-- | src/qt/optionsmodel.cpp | 22 | ||||
-rw-r--r-- | src/qt/optionsmodel.h | 3 | ||||
-rw-r--r-- | src/qt/paymentserver.cpp | 8 | ||||
-rw-r--r-- | src/qt/walletcontroller.cpp | 13 | ||||
-rw-r--r-- | src/qt/walletcontroller.h | 2 | ||||
-rw-r--r-- | src/rpc/client.cpp | 2 | ||||
-rw-r--r-- | src/rpc/rawtransaction_util.cpp | 76 | ||||
-rw-r--r-- | src/validation.cpp | 23 | ||||
-rw-r--r-- | src/validation.h | 19 | ||||
-rw-r--r-- | src/wallet/rpcwallet.cpp | 15 |
24 files changed, 193 insertions, 71 deletions
diff --git a/src/bitcoin-wallet.cpp b/src/bitcoin-wallet.cpp index 361fedf35a..eb7f0098ec 100644 --- a/src/bitcoin-wallet.cpp +++ b/src/bitcoin-wallet.cpp @@ -27,7 +27,7 @@ static void SetupWalletToolArgs() gArgs.AddArg("-datadir=<dir>", "Specify data directory", ArgsManager::ALLOW_ANY, OptionsCategory::OPTIONS); gArgs.AddArg("-wallet=<wallet-name>", "Specify wallet name", ArgsManager::ALLOW_ANY | ArgsManager::NETWORK_ONLY, OptionsCategory::OPTIONS); gArgs.AddArg("-debug=<category>", "Output debugging information (default: 0).", ArgsManager::ALLOW_ANY, OptionsCategory::DEBUG_TEST); - gArgs.AddArg("-printtoconsole", "Send trace/debug info to console (default: 1 when no -debug is true, 0 otherwise.", ArgsManager::ALLOW_ANY, OptionsCategory::DEBUG_TEST); + gArgs.AddArg("-printtoconsole", "Send trace/debug info to console (default: 1 when no -debug is true, 0 otherwise).", ArgsManager::ALLOW_ANY, OptionsCategory::DEBUG_TEST); gArgs.AddArg("info", "Get wallet info", ArgsManager::ALLOW_ANY, OptionsCategory::COMMANDS); gArgs.AddArg("create", "Create new wallet file", ArgsManager::ALLOW_ANY, OptionsCategory::COMMANDS); diff --git a/src/interfaces/node.cpp b/src/interfaces/node.cpp index ccafc3ac8c..c80a8789fc 100644 --- a/src/interfaces/node.cpp +++ b/src/interfaces/node.cpp @@ -62,6 +62,7 @@ public: return gArgs.ParseParameters(argc, argv, error); } bool readConfigFiles(std::string& error) override { return gArgs.ReadConfigFiles(error, true); } + void forceSetArg(const std::string& arg, const std::string& value) override { gArgs.ForceSetArg(arg, value); } bool softSetArg(const std::string& arg, const std::string& value) override { return gArgs.SoftSetArg(arg, value); } bool softSetBoolArg(const std::string& arg, bool value) override { return gArgs.SoftSetBoolArg(arg, value); } void selectParams(const std::string& network) override { SelectParams(network); } diff --git a/src/interfaces/node.h b/src/interfaces/node.h index e8c3d0b721..2f4f396e72 100644 --- a/src/interfaces/node.h +++ b/src/interfaces/node.h @@ -46,6 +46,9 @@ public: //! Set command line arguments. virtual bool parseParameters(int argc, const char* const argv[], std::string& error) = 0; + //! Set a command line argument + virtual void forceSetArg(const std::string& arg, const std::string& value) = 0; + //! Set a command line argument if it doesn't already have a value virtual bool softSetArg(const std::string& arg, const std::string& value) = 0; diff --git a/src/qt/askpassphrasedialog.cpp b/src/qt/askpassphrasedialog.cpp index c9f17d12ec..2ababb5e1e 100644 --- a/src/qt/askpassphrasedialog.cpp +++ b/src/qt/askpassphrasedialog.cpp @@ -44,7 +44,7 @@ AskPassphraseDialog::AskPassphraseDialog(Mode _mode, QWidget *parent, SecureStri switch(mode) { case Encrypt: // Ask passphrase x2 - ui->warningLabel->setText(tr("Enter the new passphrase to the wallet.<br/>Please use a passphrase of <b>ten or more random characters</b>, or <b>eight or more words</b>.")); + ui->warningLabel->setText(tr("Enter the new passphrase for the wallet.<br/>Please use a passphrase of <b>ten or more random characters</b>, or <b>eight or more words</b>.")); ui->passLabel1->hide(); ui->passEdit1->hide(); setWindowTitle(tr("Encrypt wallet")); @@ -67,7 +67,7 @@ AskPassphraseDialog::AskPassphraseDialog(Mode _mode, QWidget *parent, SecureStri break; case ChangePass: // Ask old passphrase + new passphrase x2 setWindowTitle(tr("Change passphrase")); - ui->warningLabel->setText(tr("Enter the old passphrase and new passphrase to the wallet.")); + ui->warningLabel->setText(tr("Enter the old passphrase and new passphrase for the wallet.")); break; } textChanged(); diff --git a/src/qt/bitcoin.cpp b/src/qt/bitcoin.cpp index adc4827e63..46f8deee57 100644 --- a/src/qt/bitcoin.cpp +++ b/src/qt/bitcoin.cpp @@ -282,6 +282,10 @@ void BitcoinApplication::parameterSetup() m_node.initParameterInteraction(); } +void BitcoinApplication::SetPrune(bool prune, bool force) { + optionsModel->SetPrune(prune, force); +} + void BitcoinApplication::requestInitialize() { qDebug() << __func__ << ": Requesting initialize"; @@ -487,8 +491,10 @@ int GuiMain(int argc, char* argv[]) /// 5. Now that settings and translations are available, ask user for data directory // User language is set up: pick a data directory - if (!Intro::pickDataDirectory(*node)) - return EXIT_SUCCESS; + bool did_show_intro = false; + bool prune = false; // Intro dialog prune check box + // Gracefully exit if the user cancels + if (!Intro::showIfNeeded(*node, did_show_intro, prune)) return EXIT_SUCCESS; /// 6. Determine availability of data directory and parse bitcoin.conf /// - Do not call GetDataDir(true) before this step finishes @@ -562,6 +568,11 @@ int GuiMain(int argc, char* argv[]) // Load GUI settings from QSettings app.createOptionsModel(gArgs.GetBoolArg("-resetguisettings", false)); + if (did_show_intro) { + // Store intro dialog settings other than datadir (network specific) + app.SetPrune(prune, true); + } + if (gArgs.GetBoolArg("-splash", DEFAULT_SPLASHSCREEN) && !gArgs.GetBoolArg("-min", false)) app.createSplashScreen(networkStyle.data()); diff --git a/src/qt/bitcoin.h b/src/qt/bitcoin.h index 3869193a3a..8c77fd8a7d 100644 --- a/src/qt/bitcoin.h +++ b/src/qt/bitcoin.h @@ -67,6 +67,8 @@ public: void parameterSetup(); /// Create options model void createOptionsModel(bool resetSettings); + /// Update prune value + void SetPrune(bool prune, bool force = false); /// Create main window void createWindow(const NetworkStyle *networkStyle); /// Create splash screen diff --git a/src/qt/bitcoingui.cpp b/src/qt/bitcoingui.cpp index c672171cfb..7671fde705 100644 --- a/src/qt/bitcoingui.cpp +++ b/src/qt/bitcoingui.cpp @@ -375,7 +375,9 @@ void BitcoinGUI::createActions() for (const std::pair<const std::string, bool>& i : m_wallet_controller->listWalletDir()) { const std::string& path = i.first; QString name = path.empty() ? QString("["+tr("default wallet")+"]") : QString::fromStdString(path); - // Menu items remove single &. Single & are shown when && is in the string, but only the first occurrence. So replace only the first & with && + // Menu items remove single &. Single & are shown when && is in + // the string, but only the first occurrence. So replace only + // the first & with &&. name.replace(name.indexOf(QChar('&')), 1, QString("&&")); QAction* action = m_open_wallet_menu->addAction(name); diff --git a/src/qt/createwalletdialog.cpp b/src/qt/createwalletdialog.cpp index 10262c37c3..8e6474b0d4 100644 --- a/src/qt/createwalletdialog.cpp +++ b/src/qt/createwalletdialog.cpp @@ -25,7 +25,8 @@ CreateWalletDialog::CreateWalletDialog(QWidget* parent) : }); connect(ui->encrypt_wallet_checkbox, &QCheckBox::toggled, [this](bool checked) { - // Disable disable_privkeys_checkbox when encrypt is set to true, enable it when encrypt is false + // Disable the disable_privkeys_checkbox when isEncryptWalletChecked is + // set to true, enable it when isEncryptWalletChecked is false. ui->disable_privkeys_checkbox->setEnabled(!checked); // When the disable_privkeys_checkbox is disabled, uncheck it. @@ -45,17 +46,17 @@ QString CreateWalletDialog::walletName() const return ui->wallet_name_line_edit->text(); } -bool CreateWalletDialog::encrypt() const +bool CreateWalletDialog::isEncryptWalletChecked() const { return ui->encrypt_wallet_checkbox->isChecked(); } -bool CreateWalletDialog::disablePrivateKeys() const +bool CreateWalletDialog::isDisablePrivateKeysChecked() const { return ui->disable_privkeys_checkbox->isChecked(); } -bool CreateWalletDialog::blank() const +bool CreateWalletDialog::isMakeBlankWalletChecked() const { return ui->blank_wallet_checkbox->isChecked(); } diff --git a/src/qt/createwalletdialog.h b/src/qt/createwalletdialog.h index a1365b5969..30766107b9 100644 --- a/src/qt/createwalletdialog.h +++ b/src/qt/createwalletdialog.h @@ -24,9 +24,9 @@ public: virtual ~CreateWalletDialog(); QString walletName() const; - bool encrypt() const; - bool disablePrivateKeys() const; - bool blank() const; + bool isEncryptWalletChecked() const; + bool isDisablePrivateKeysChecked() const; + bool isMakeBlankWalletChecked() const; private: Ui::CreateWalletDialog *ui; diff --git a/src/qt/forms/askpassphrasedialog.ui b/src/qt/forms/askpassphrasedialog.ui index 69803989cd..e74d183818 100644 --- a/src/qt/forms/askpassphrasedialog.ui +++ b/src/qt/forms/askpassphrasedialog.ui @@ -95,7 +95,7 @@ <item row="3" column="1"> <widget class="QCheckBox" name="toggleShowPasswordButton"> <property name="text"> - <string>Show password</string> + <string>Show passphrase</string> </property> </widget> </item> diff --git a/src/qt/forms/createwalletdialog.ui b/src/qt/forms/createwalletdialog.ui index 1fbaeeaaab..e49bab8f3b 100644 --- a/src/qt/forms/createwalletdialog.ui +++ b/src/qt/forms/createwalletdialog.ui @@ -62,7 +62,7 @@ </rect> </property> <property name="toolTip"> - <string>Encrypt the wallet. The wallet will be encrypted with a password of your choice.</string> + <string>Encrypt the wallet. The wallet will be encrypted with a passphrase of your choice.</string> </property> <property name="text"> <string>Encrypt Wallet</string> diff --git a/src/qt/forms/intro.ui b/src/qt/forms/intro.ui index cfdd8482e3..f27a4ebe44 100644 --- a/src/qt/forms/intro.ui +++ b/src/qt/forms/intro.ui @@ -211,6 +211,16 @@ </widget> </item> <item> + <widget class="QCheckBox" name="prune"> + <property name="toolTip"> + <string>Reverting this setting requires re-downloading the entire blockchain. It is faster to download the full chain first and prune it later. Disables some advanced features.</string> + </property> + <property name="text"> + <string></string> + </property> + </widget> + </item> + <item> <widget class="QLabel" name="lblExplanation2"> <property name="text"> <string>This initial synchronisation is very demanding, and may expose hardware problems with your computer that had previously gone unnoticed. Each time you run %1, it will continue downloading where it left off.</string> diff --git a/src/qt/intro.cpp b/src/qt/intro.cpp index 102e37e471..9e05c63aa0 100644 --- a/src/qt/intro.cpp +++ b/src/qt/intro.cpp @@ -131,6 +131,11 @@ Intro::Intro(QWidget *parent, uint64_t blockchain_size, uint64_t chain_state_siz ui->lblExplanation2->setText(ui->lblExplanation2->text().arg(PACKAGE_NAME)); uint64_t pruneTarget = std::max<int64_t>(0, gArgs.GetArg("-prune", 0)); + if (pruneTarget > 1) { // -prune=1 means enabled, above that it's a size in MB + ui->prune->setChecked(true); + ui->prune->setEnabled(false); + } + ui->prune->setText(tr("Discard blocks after verification, except most recent %1 GB (prune)").arg(pruneTarget ? pruneTarget / 1000 : 2)); requiredSpace = m_blockchain_size; QString storageRequiresMsg = tr("At least %1 GB of data will be stored in this directory, and it will grow over time."); if (pruneTarget) { @@ -180,8 +185,10 @@ void Intro::setDataDirectory(const QString &dataDir) } } -bool Intro::pickDataDirectory(interfaces::Node& node) +bool Intro::showIfNeeded(interfaces::Node& node, bool& did_show_intro, bool& prune) { + did_show_intro = false; + QSettings settings; /* If data directory provided on command line, no need to look at settings or show a picking dialog */ @@ -205,6 +212,7 @@ bool Intro::pickDataDirectory(interfaces::Node& node) Intro intro(0, node.getAssumedBlockchainSize(), node.getAssumedChainStateSize()); intro.setDataDirectory(dataDir); intro.setWindowIcon(QIcon(":icons/bitcoin")); + did_show_intro = true; while(true) { @@ -227,6 +235,9 @@ bool Intro::pickDataDirectory(interfaces::Node& node) } } + // Additional preferences: + prune = intro.ui->prune->isChecked(); + settings.setValue("strDataDir", dataDir); settings.setValue("fReset", false); } @@ -263,6 +274,11 @@ void Intro::setStatus(int status, const QString &message, quint64 bytesAvailable { freeString += " " + tr("(of %n GB needed)", "", requiredSpace); ui->freeSpace->setStyleSheet("QLabel { color: #800000 }"); + ui->prune->setChecked(true); + } else if (bytesAvailable / GB_BYTES - requiredSpace < 10) { + freeString += " " + tr("(%n GB needed for full chain)", "", requiredSpace); + ui->freeSpace->setStyleSheet("QLabel { color: #999900 }"); + ui->prune->setChecked(true); } else { ui->freeSpace->setStyleSheet(""); } diff --git a/src/qt/intro.h b/src/qt/intro.h index c3b26808d4..aca7e71642 100644 --- a/src/qt/intro.h +++ b/src/qt/intro.h @@ -39,6 +39,7 @@ public: /** * Determine data directory. Let the user choose if the current one doesn't exist. + * Let the user configure additional preferences such as pruning. * * @returns true if a data directory was selected, false if the user cancelled the selection * dialog. @@ -46,7 +47,7 @@ public: * @note do NOT call global GetDataDir() before calling this function, this * will cause the wrong path to be cached. */ - static bool pickDataDirectory(interfaces::Node& node); + static bool showIfNeeded(interfaces::Node& node, bool& did_show_intro, bool& prune); Q_SIGNALS: void requestCheck(); diff --git a/src/qt/optionsmodel.cpp b/src/qt/optionsmodel.cpp index f3974b1c85..d047a82475 100644 --- a/src/qt/optionsmodel.cpp +++ b/src/qt/optionsmodel.cpp @@ -92,11 +92,7 @@ void OptionsModel::Init(bool resetSettings) settings.setValue("bPrune", false); if (!settings.contains("nPruneSize")) settings.setValue("nPruneSize", 2); - // Convert prune size from GB to MiB: - const uint64_t nPruneSizeMiB = (settings.value("nPruneSize").toInt() * GB_BYTES) >> 20; - if (!m_node.softSetArg("-prune", settings.value("bPrune").toBool() ? std::to_string(nPruneSizeMiB) : "0")) { - addOverriddenOption("-prune"); - } + SetPrune(settings.value("bPrune").toBool()); if (!settings.contains("nDatabaseCache")) settings.setValue("nDatabaseCache", (qint64)nDefaultDbCache); @@ -240,6 +236,22 @@ static const QString GetDefaultProxyAddress() return QString("%1:%2").arg(DEFAULT_GUI_PROXY_HOST).arg(DEFAULT_GUI_PROXY_PORT); } +void OptionsModel::SetPrune(bool prune, bool force) +{ + QSettings settings; + settings.setValue("bPrune", prune); + // Convert prune size from GB to MiB: + const uint64_t nPruneSizeMiB = (settings.value("nPruneSize").toInt() * GB_BYTES) >> 20; + std::string prune_val = prune ? std::to_string(nPruneSizeMiB) : "0"; + if (force) { + m_node.forceSetArg("-prune", prune_val); + return; + } + if (!m_node.softSetArg("-prune", prune_val)) { + addOverriddenOption("-prune"); + } +} + // read QSettings values and return them QVariant OptionsModel::data(const QModelIndex & index, int role) const { diff --git a/src/qt/optionsmodel.h b/src/qt/optionsmodel.h index 1af3a72b92..b1231b7c7d 100644 --- a/src/qt/optionsmodel.h +++ b/src/qt/optionsmodel.h @@ -77,6 +77,9 @@ public: bool getCoinControlFeatures() const { return fCoinControlFeatures; } const QString& getOverriddenByCommandLine() { return strOverriddenByCommandLine; } + /* Explicit setters */ + void SetPrune(bool prune, bool force = false); + /* Restart flag helper */ void setRestartRequired(bool fRequired); bool isRestartRequired() const; diff --git a/src/qt/paymentserver.cpp b/src/qt/paymentserver.cpp index 0bb87742e9..00d83d23dd 100644 --- a/src/qt/paymentserver.cpp +++ b/src/qt/paymentserver.cpp @@ -328,7 +328,9 @@ void PaymentServer::handleURIOrFile(const QString& s) #ifndef ENABLE_BIP70 if (uri.hasQueryItem("r")) { // payment request Q_EMIT message(tr("URI handling"), - tr("Cannot process payment request because BIP70 support was not compiled in."), + tr("Cannot process payment request because BIP70 support was not compiled in.")+ + tr("Due to widespread security flaws in BIP70 it's strongly recommended that any merchant instructions to switch wallets be ignored.")+ + tr("If you are receiving this error you should request the merchant provide a BIP21 compatible URI."), CClientUIInterface::ICON_WARNING); } #endif @@ -364,7 +366,9 @@ void PaymentServer::handleURIOrFile(const QString& s) return; #else Q_EMIT message(tr("Payment request file handling"), - tr("Cannot process payment request because BIP70 support was not compiled in."), + tr("Cannot process payment request because BIP70 support was not compiled in.")+ + tr("Due to widespread security flaws in BIP70 it's strongly recommended that any merchant instructions to switch wallets be ignored.")+ + tr("If you are receiving this error you should request the merchant provide a BIP21 compatible URI."), CClientUIInterface::ICON_WARNING); #endif } diff --git a/src/qt/walletcontroller.cpp b/src/qt/walletcontroller.cpp index 8b8283d3d8..fa6f9f3f16 100644 --- a/src/qt/walletcontroller.cpp +++ b/src/qt/walletcontroller.cpp @@ -75,7 +75,7 @@ void WalletController::closeWallet(WalletModel* wallet_model, QWidget* parent) { QMessageBox box(parent); box.setWindowTitle(tr("Close wallet")); - box.setText(tr("Are you sure you wish to close wallet <i>%1</i>?").arg(GUIUtil::HtmlEscape(wallet_model->getDisplayName()))); + box.setText(tr("Are you sure you wish to close the wallet <i>%1</i>?").arg(GUIUtil::HtmlEscape(wallet_model->getDisplayName()))); box.setInformativeText(tr("Closing the wallet for too long can result in having to resync the entire chain if pruning is enabled.")); box.setStandardButtons(QMessageBox::Yes|QMessageBox::Cancel); box.setDefaultButton(QMessageBox::Yes); @@ -179,9 +179,10 @@ CreateWalletActivity::~CreateWalletActivity() delete m_passphrase_dialog; } -void CreateWalletActivity::askPasshprase() +void CreateWalletActivity::askPassphrase() { m_passphrase_dialog = new AskPassphraseDialog(AskPassphraseDialog::Encrypt, m_parent_widget, &m_passphrase); + m_passphrase_dialog->setWindowModality(Qt::ApplicationModal); m_passphrase_dialog->show(); connect(m_passphrase_dialog, &QObject::destroyed, [this] { @@ -201,10 +202,10 @@ void CreateWalletActivity::createWallet() std::string name = m_create_wallet_dialog->walletName().toStdString(); uint64_t flags = 0; - if (m_create_wallet_dialog->disablePrivateKeys()) { + if (m_create_wallet_dialog->isDisablePrivateKeysChecked()) { flags |= WALLET_FLAG_DISABLE_PRIVATE_KEYS; } - if (m_create_wallet_dialog->blank()) { + if (m_create_wallet_dialog->isMakeBlankWalletChecked()) { flags |= WALLET_FLAG_BLANK_WALLET; } @@ -246,8 +247,8 @@ void CreateWalletActivity::create() Q_EMIT finished(); }); connect(m_create_wallet_dialog, &QDialog::accepted, [this] { - if (m_create_wallet_dialog->encrypt()) { - askPasshprase(); + if (m_create_wallet_dialog->isEncryptWalletChecked()) { + askPassphrase(); } else { createWallet(); } diff --git a/src/qt/walletcontroller.h b/src/qt/walletcontroller.h index 4e1a772f3a..fb37b7292c 100644 --- a/src/qt/walletcontroller.h +++ b/src/qt/walletcontroller.h @@ -118,7 +118,7 @@ Q_SIGNALS: void created(WalletModel* wallet_model); private: - void askPasshprase(); + void askPassphrase(); void createWallet(); void finish(); diff --git a/src/rpc/client.cpp b/src/rpc/client.cpp index 93fca5a6de..c2714f9c83 100644 --- a/src/rpc/client.cpp +++ b/src/rpc/client.cpp @@ -85,7 +85,7 @@ static const CRPCConvertParam vRPCConvertParams[] = { "getblockheader", 1, "verbose" }, { "getchaintxstats", 0, "nblocks" }, { "gettransaction", 1, "include_watchonly" }, - { "gettransaction", 2, "decode" }, + { "gettransaction", 2, "verbose" }, { "getrawtransaction", 1, "verbose" }, { "createrawtransaction", 0, "inputs" }, { "createrawtransaction", 1, "outputs" }, diff --git a/src/rpc/rawtransaction_util.cpp b/src/rpc/rawtransaction_util.cpp index 697c6d45c4..f1d176ba4d 100644 --- a/src/rpc/rawtransaction_util.cpp +++ b/src/rpc/rawtransaction_util.cpp @@ -196,32 +196,73 @@ void ParsePrevouts(const UniValue& prevTxsUnival, FillableSigningProvider* keyst } // if redeemScript and private keys were given, add redeemScript to the keystore so it can be signed - if (keystore && (scriptPubKey.IsPayToScriptHash() || scriptPubKey.IsPayToWitnessScriptHash())) { + const bool is_p2sh = scriptPubKey.IsPayToScriptHash(); + const bool is_p2wsh = scriptPubKey.IsPayToWitnessScriptHash(); + if (keystore && (is_p2sh || is_p2wsh)) { RPCTypeCheckObj(prevOut, { {"redeemScript", UniValueType(UniValue::VSTR)}, {"witnessScript", UniValueType(UniValue::VSTR)}, }, true); UniValue rs = find_value(prevOut, "redeemScript"); - if (!rs.isNull()) { - std::vector<unsigned char> rsData(ParseHexV(rs, "redeemScript")); - CScript redeemScript(rsData.begin(), rsData.end()); - keystore->AddCScript(redeemScript); - // Automatically also add the P2WSH wrapped version of the script (to deal with P2SH-P2WSH). - // This is only for compatibility, it is encouraged to use the explicit witnessScript field instead. - keystore->AddCScript(GetScriptForWitness(redeemScript)); - } UniValue ws = find_value(prevOut, "witnessScript"); - if (!ws.isNull()) { - std::vector<unsigned char> wsData(ParseHexV(ws, "witnessScript")); - CScript witnessScript(wsData.begin(), wsData.end()); - keystore->AddCScript(witnessScript); - // Automatically also add the P2WSH wrapped version of the script (to deal with P2SH-P2WSH). - keystore->AddCScript(GetScriptForWitness(witnessScript)); - } if (rs.isNull() && ws.isNull()) { throw JSONRPCError(RPC_INVALID_PARAMETER, "Missing redeemScript/witnessScript"); } + + // work from witnessScript when possible + std::vector<unsigned char> scriptData(!ws.isNull() ? ParseHexV(ws, "witnessScript") : ParseHexV(rs, "redeemScript")); + CScript script(scriptData.begin(), scriptData.end()); + keystore->AddCScript(script); + // Automatically also add the P2WSH wrapped version of the script (to deal with P2SH-P2WSH). + // This is done for redeemScript only for compatibility, it is encouraged to use the explicit witnessScript field instead. + CScript witness_output_script{GetScriptForWitness(script)}; + keystore->AddCScript(witness_output_script); + + if (!ws.isNull() && !rs.isNull()) { + // if both witnessScript and redeemScript are provided, + // they should either be the same (for backwards compat), + // or the redeemScript should be the encoded form of + // the witnessScript (ie, for p2sh-p2wsh) + if (ws.get_str() != rs.get_str()) { + std::vector<unsigned char> redeemScriptData(ParseHexV(rs, "redeemScript")); + CScript redeemScript(redeemScriptData.begin(), redeemScriptData.end()); + if (redeemScript != witness_output_script) { + throw JSONRPCError(RPC_INVALID_PARAMETER, "redeemScript does not correspond to witnessScript"); + } + } + } + + if (is_p2sh) { + const CTxDestination p2sh{ScriptHash(script)}; + const CTxDestination p2sh_p2wsh{ScriptHash(witness_output_script)}; + if (scriptPubKey == GetScriptForDestination(p2sh)) { + // traditional p2sh; arguably an error if + // we got here with rs.IsNull(), because + // that means the p2sh script was specified + // via witnessScript param, but for now + // we'll just quietly accept it + } else if (scriptPubKey == GetScriptForDestination(p2sh_p2wsh)) { + // p2wsh encoded as p2sh; ideally the witness + // script was specified in the witnessScript + // param, but also support specifying it via + // redeemScript param for backwards compat + // (in which case ws.IsNull() == true) + } else { + // otherwise, can't generate scriptPubKey from + // either script, so we got unusable parameters + throw JSONRPCError(RPC_INVALID_PARAMETER, "redeemScript/witnessScript does not match scriptPubKey"); + } + } else if (is_p2wsh) { + // plain p2wsh; could throw an error if script + // was specified by redeemScript rather than + // witnessScript (ie, ws.IsNull() == true), but + // accept it for backwards compat + const CTxDestination p2wsh{WitnessV0ScriptHash(script)}; + if (scriptPubKey != GetScriptForDestination(p2wsh)) { + throw JSONRPCError(RPC_INVALID_PARAMETER, "redeemScript/witnessScript does not match scriptPubKey"); + } + } } } } @@ -268,6 +309,9 @@ UniValue SignTransaction(CMutableTransaction& mtx, const SigningProvider* keysto if (serror == SCRIPT_ERR_INVALID_STACK_OPERATION) { // Unable to sign input and verification failed (possible attempt to partially sign). TxInErrorToJSON(txin, vErrors, "Unable to sign input, invalid stack size (possibly missing key)"); + } else if (serror == SCRIPT_ERR_SIG_NULLFAIL) { + // Verification failed (possibly due to insufficient signatures). + TxInErrorToJSON(txin, vErrors, "CHECK(MULTI)SIG failing with non-zero signature (possibly need more signatures)"); } else { TxInErrorToJSON(txin, vErrors, ScriptErrorString(serror)); } diff --git a/src/validation.cpp b/src/validation.cpp index d470fd5b6e..6a9b0c95fb 100644 --- a/src/validation.cpp +++ b/src/validation.cpp @@ -2240,14 +2240,12 @@ void static UpdateTip(const CBlockIndex* pindexNew, const CChainParams& chainPar if (nUpgraded > 0) AppendWarning(warningMessages, strprintf(_("%d of last 100 blocks have unexpected version").translated, nUpgraded)); } - LogPrintf("%s: new best=%s height=%d version=0x%08x log2_work=%.8g tx=%lu date='%s' progress=%f cache=%.1fMiB(%utxo)", __func__, /* Continued */ + LogPrintf("%s: new best=%s height=%d version=0x%08x log2_work=%.8g tx=%lu date='%s' progress=%f cache=%.1fMiB(%utxo)%s\n", __func__, pindexNew->GetBlockHash().ToString(), pindexNew->nHeight, pindexNew->nVersion, log(pindexNew->nChainWork.getdouble())/log(2.0), (unsigned long)pindexNew->nChainTx, FormatISO8601DateTime(pindexNew->GetBlockTime()), - GuessVerificationProgress(chainParams.TxData(), pindexNew), ::ChainstateActive().CoinsTip().DynamicMemoryUsage() * (1.0 / (1<<20)), ::ChainstateActive().CoinsTip().GetCacheSize()); - if (!warningMessages.empty()) - LogPrintf(" warning='%s'", warningMessages); /* Continued */ - LogPrintf("\n"); + GuessVerificationProgress(chainParams.TxData(), pindexNew), ::ChainstateActive().CoinsTip().DynamicMemoryUsage() * (1.0 / (1<<20)), ::ChainstateActive().CoinsTip().GetCacheSize(), + !warningMessages.empty() ? strprintf(" warning='%s'", warningMessages) : ""); } @@ -2510,6 +2508,8 @@ void CChainState::PruneBlockIndexCandidates() { /** * Try to make some progress towards making pindexMostWork the active block. * pblock is either nullptr or a pointer to a CBlock corresponding to pindexMostWork. + * + * @returns true unless a system error occurred */ bool CChainState::ActivateBestChainStep(CValidationState& state, const CChainParams& chainparams, CBlockIndex* pindexMostWork, const std::shared_ptr<const CBlock>& pblock, bool& fInvalidFound, ConnectTrace& connectTrace) { @@ -2629,15 +2629,6 @@ static void LimitValidationInterfaceQueue() LOCKS_EXCLUDED(cs_main) { } } -/** - * Make the best chain active, in multiple steps. The result is either failure - * or an activated best chain. pblock is either nullptr or a pointer to a block - * that is already loaded (to avoid loading it again from disk). - * - * ActivateBestChain is split into steps (see ActivateBestChainStep) so that - * we avoid holding cs_main for an extended period of time; the length of this - * call may be quite long during reindexing or a substantial reorg. - */ bool CChainState::ActivateBestChain(CValidationState &state, const CChainParams& chainparams, std::shared_ptr<const CBlock> pblock) { // Note that while we're often called here from ProcessNewBlock, this is // far from a guarantee. Things in the P2P/RPC will often end up calling @@ -2685,8 +2676,10 @@ bool CChainState::ActivateBestChain(CValidationState &state, const CChainParams& bool fInvalidFound = false; std::shared_ptr<const CBlock> nullBlockPtr; - if (!ActivateBestChainStep(state, chainparams, pindexMostWork, pblock && pblock->GetHash() == pindexMostWork->GetBlockHash() ? pblock : nullBlockPtr, fInvalidFound, connectTrace)) + if (!ActivateBestChainStep(state, chainparams, pindexMostWork, pblock && pblock->GetHash() == pindexMostWork->GetBlockHash() ? pblock : nullBlockPtr, fInvalidFound, connectTrace)) { + // A system error occurred return false; + } blocks_connected = true; if (fInvalidFound) { diff --git a/src/validation.h b/src/validation.h index 99850f71d9..06252b6bc8 100644 --- a/src/validation.h +++ b/src/validation.h @@ -211,7 +211,7 @@ static const uint64_t MIN_DISK_SPACE_FOR_BLOCK_FILES = 550 * 1024 * 1024; * @param[in] pblock The block we want to process. * @param[in] fForceProcessing Process this block even if unrequested; used for non-network block sources and whitelisted peers. * @param[out] fNewBlock A boolean which is set to indicate if the block was first received via this call - * @return True if state.IsValid() + * @returns If the block was processed, independently of block validity */ bool ProcessNewBlock(const CChainParams& chainparams, const std::shared_ptr<const CBlock> pblock, bool fForceProcessing, bool* fNewBlock) LOCKS_EXCLUDED(cs_main); @@ -653,6 +653,8 @@ public: * * If FlushStateMode::NONE is used, then FlushStateToDisk(...) won't do anything * besides checking if we need to prune. + * + * @returns true unless a system error occurred */ bool FlushStateToDisk( const CChainParams& chainparams, @@ -667,7 +669,20 @@ public: //! if we pruned. void PruneAndFlush(); - bool ActivateBestChain(CValidationState &state, const CChainParams& chainparams, std::shared_ptr<const CBlock> pblock) LOCKS_EXCLUDED(cs_main); + /** + * Make the best chain active, in multiple steps. The result is either failure + * or an activated best chain. pblock is either nullptr or a pointer to a block + * that is already loaded (to avoid loading it again from disk). + * + * ActivateBestChain is split into steps (see ActivateBestChainStep) so that + * we avoid holding cs_main for an extended period of time; the length of this + * call may be quite long during reindexing or a substantial reorg. + * + * @returns true unless a system error occurred + */ + bool ActivateBestChain(CValidationState& state, + const CChainParams& chainparams, + std::shared_ptr<const CBlock> pblock) LOCKS_EXCLUDED(cs_main); bool AcceptBlock(const std::shared_ptr<const CBlock>& pblock, CValidationState& state, const CChainParams& chainparams, CBlockIndex** ppindex, bool fRequested, const FlatFilePos* dbp, bool* fNewBlock) EXCLUSIVE_LOCKS_REQUIRED(cs_main); diff --git a/src/wallet/rpcwallet.cpp b/src/wallet/rpcwallet.cpp index b88aabd0fa..216205ed61 100644 --- a/src/wallet/rpcwallet.cpp +++ b/src/wallet/rpcwallet.cpp @@ -1648,8 +1648,10 @@ static UniValue gettransaction(const JSONRPCRequest& request) "\nGet detailed information about in-wallet transaction <txid>\n", { {"txid", RPCArg::Type::STR, RPCArg::Optional::NO, "The transaction id"}, - {"include_watchonly", RPCArg::Type::BOOL, /* default */ "true for watch-only wallets, otherwise false", "Whether to include watch-only addresses in balance calculation and details[]"}, - {"decode", RPCArg::Type::BOOL, /* default */ "false", "Whether to add a field with the decoded transaction"}, + {"include_watchonly", RPCArg::Type::BOOL, /* default */ "true for watch-only wallets, otherwise false", + "Whether to include watch-only addresses in balance calculation and details[]"}, + {"verbose", RPCArg::Type::BOOL, /* default */ "false", + "Whether to include a `decoded` field containing the decoded transaction (equivalent to RPC decoderawtransaction)"}, }, RPCResult{ "{\n" @@ -1685,7 +1687,8 @@ static UniValue gettransaction(const JSONRPCRequest& request) " ,...\n" " ],\n" " \"hex\" : \"data\" (string) Raw data for transaction\n" - " \"decoded\" : transaction (json object) Optional, the decoded transaction\n" + " \"decoded\" : transaction (json object) Optional, the decoded transaction (only present when `verbose` is passed), equivalent to the\n" + " RPC decoderawtransaction method, or the RPC getrawtransaction method when `verbose` is passed.\n" "}\n" }, RPCExamples{ @@ -1711,7 +1714,7 @@ static UniValue gettransaction(const JSONRPCRequest& request) filter |= ISMINE_WATCH_ONLY; } - bool decode_tx = request.params[2].isNull() ? false : request.params[2].get_bool(); + bool verbose = request.params[2].isNull() ? false : request.params[2].get_bool(); UniValue entry(UniValue::VOBJ); auto it = pwallet->mapWallet.find(hash); @@ -1738,7 +1741,7 @@ static UniValue gettransaction(const JSONRPCRequest& request) std::string strHex = EncodeHexTx(*wtx.tx, pwallet->chain().rpcSerializationFlags()); entry.pushKV("hex", strHex); - if (decode_tx) { + if (verbose) { UniValue decoded(UniValue::VOBJ); TxToUniv(*wtx.tx, uint256(), decoded, false); entry.pushKV("decoded", decoded); @@ -4189,7 +4192,7 @@ static const CRPCCommand commands[] = { "wallet", "getrawchangeaddress", &getrawchangeaddress, {"address_type"} }, { "wallet", "getreceivedbyaddress", &getreceivedbyaddress, {"address","minconf"} }, { "wallet", "getreceivedbylabel", &getreceivedbylabel, {"label","minconf"} }, - { "wallet", "gettransaction", &gettransaction, {"txid","include_watchonly","decode"} }, + { "wallet", "gettransaction", &gettransaction, {"txid","include_watchonly","verbose"} }, { "wallet", "getunconfirmedbalance", &getunconfirmedbalance, {} }, { "wallet", "getbalances", &getbalances, {} }, { "wallet", "getwalletinfo", &getwalletinfo, {} }, |