diff --git a/doc/release-notes-15454.md b/doc/release-notes-15454.md new file mode 100644 index 0000000000..9cb0368084 --- /dev/null +++ b/doc/release-notes-15454.md @@ -0,0 +1,6 @@ +Wallet +------ + +Dash Core will no longer create an unnamed `""` wallet by default when no wallet is specified on the command line or in the configuration files. +For backwards compatibility, if an unnamed `""` wallet already exists and would have been loaded previously, then it will still be loaded. +Users without an unnamed `""` wallet and without any other wallets to be loaded on startup will be prompted to either choose a wallet to load, or to create a new wallet. diff --git a/doc/release-notes/release-notes-20186.md b/doc/release-notes/release-notes-20186.md new file mode 100644 index 0000000000..f20ae08200 --- /dev/null +++ b/doc/release-notes/release-notes-20186.md @@ -0,0 +1,16 @@ +Wallet +------ + +### Automatic wallet creation removed + +Dash Core will no longer automatically create new wallets on startup. It will +load existing wallets specified by `-wallet` options on the command line or in +`dash.conf` or `settings.json` files. And by default it will also load a +top-level unnamed ("") wallet. However, if specified wallets don't exist, +Dash Core will now just log warnings instead of creating new wallets with +new keys and addresses like previous releases did. + +New wallets can be created through the GUI (which has a more prominent create +wallet option), through the `dash-cli createwallet` or `dash-wallet +create` commands, or the `createwallet` RPC. + diff --git a/src/bench/coin_selection.cpp b/src/bench/coin_selection.cpp index ad5c9f1988..cbdb2e1001 100644 --- a/src/bench/coin_selection.cpp +++ b/src/bench/coin_selection.cpp @@ -31,7 +31,7 @@ static void CoinSelection(benchmark::Bench& bench) { NodeContext node; auto chain = interfaces::MakeChain(node); - const CWallet wallet(chain.get(), WalletLocation(), CreateDummyWalletDatabase()); + const CWallet wallet(chain.get(), "", CreateDummyWalletDatabase()); std::vector> wtxs; LOCK(wallet.cs_wallet); @@ -63,7 +63,7 @@ static void CoinSelection(benchmark::Bench& bench) typedef std::set CoinSet; static NodeContext testNode; static auto testChain = interfaces::MakeChain(testNode); -static const CWallet testWallet(testChain.get(), WalletLocation(), CreateDummyWalletDatabase()); +static const CWallet testWallet(testChain.get(), "", CreateDummyWalletDatabase()); std::vector> wtxn; // Copied from src/wallet/test/coinselector_tests.cpp diff --git a/src/bench/wallet_balance.cpp b/src/bench/wallet_balance.cpp index e88acebd9f..647b8c81a9 100644 --- a/src/bench/wallet_balance.cpp +++ b/src/bench/wallet_balance.cpp @@ -19,7 +19,7 @@ static void WalletBalance(benchmark::Bench& bench, const bool set_dirty, const b NodeContext node; std::unique_ptr chain = interfaces::MakeChain(node); - CWallet wallet{chain.get(), WalletLocation(), CreateMockWalletDatabase()}; + CWallet wallet{chain.get(), "", CreateMockWalletDatabase()}; { bool first_run; if (wallet.LoadWallet(first_run) != DBErrors::LOAD_OK) assert(false); diff --git a/src/init.cpp b/src/init.cpp index 1bb0f12071..6dfe22404d 100644 --- a/src/init.cpp +++ b/src/init.cpp @@ -1214,7 +1214,7 @@ std::set g_enabled_filter_types; std::terminate(); }; -bool AppInitBasicSetup(ArgsManager& args) +bool AppInitBasicSetup(const ArgsManager& args) { // ********************************************************* Step 1: setup #ifdef _MSC_VER diff --git a/src/init.h b/src/init.h index f384d53971..271d080f0f 100644 --- a/src/init.h +++ b/src/init.h @@ -33,7 +33,7 @@ void InitParameterInteraction(ArgsManager& args); * @note This can be done before daemonization. Do not call Shutdown() if this function fails. * @pre Parameters should be parsed and config file should be read. */ -bool AppInitBasicSetup(ArgsManager& args); +bool AppInitBasicSetup(const ArgsManager& args); /** * Initialization: parameter interaction. * @note This can be done before daemonization. Do not call Shutdown() if this function fails. diff --git a/src/interfaces/node.h b/src/interfaces/node.h index 388b7ba4b6..4b9f779f43 100644 --- a/src/interfaces/node.h +++ b/src/interfaces/node.h @@ -33,7 +33,6 @@ class proxyType; struct bilingual_str; enum class SynchronizationState; struct CNodeStateStats; -enum class WalletCreationStatus; struct NodeContext; namespace interfaces { diff --git a/src/interfaces/wallet.cpp b/src/interfaces/wallet.cpp index 5571ec6c24..32d7eec193 100644 --- a/src/interfaces/wallet.cpp +++ b/src/interfaces/wallet.cpp @@ -75,7 +75,7 @@ WalletTx MakeWalletTx(CWallet& wallet, const CWalletTx& wtx) } //! Construct wallet tx status struct. -WalletTxStatus MakeWalletTxStatus(CWallet& wallet, const CWalletTx& wtx) +WalletTxStatus MakeWalletTxStatus(const CWallet& wallet, const CWalletTx& wtx) { WalletTxStatus result; result.block_height = wallet.chain().getBlockHeight(wtx.m_confirm.hashBlock).value_or(std::numeric_limits::max()); @@ -94,7 +94,7 @@ WalletTxStatus MakeWalletTxStatus(CWallet& wallet, const CWalletTx& wtx) } //! Construct wallet TxOut struct. -WalletTxOut MakeWalletTxOut(CWallet& wallet, +WalletTxOut MakeWalletTxOut(const CWallet& wallet, const CWalletTx& wtx, int n, int depth) EXCLUSIVE_LOCKS_REQUIRED(wallet.cs_wallet) @@ -582,8 +582,7 @@ public: class WalletClientImpl : public WalletClient { public: - WalletClientImpl(Chain& chain, ArgsManager& args, std::vector wallet_filenames) - : m_wallet_filenames(std::move(wallet_filenames)) + WalletClientImpl(Chain& chain, ArgsManager& args) { m_context.chain = &chain; m_context.args = &args; @@ -600,23 +599,30 @@ public: m_rpc_handlers.emplace_back(m_context.chain->handleRpc(m_rpc_commands.back())); } } - bool verify() override { return VerifyWallets(*m_context.chain, m_wallet_filenames); } - bool load() override { return LoadWallets(*m_context.chain, m_wallet_filenames); } + bool verify() override { return VerifyWallets(*m_context.chain); } + bool load() override { return LoadWallets(*m_context.chain); } void start(CScheduler& scheduler) override { return StartWallets(scheduler, *Assert(m_context.args)); } void flush() override { return FlushWallets(); } void stop() override { return StopWallets(); } void setMockTime(int64_t time) override { return SetMockTime(time); } //! WalletClient methods - std::unique_ptr createWallet(const std::string& name, const SecureString& passphrase, uint64_t wallet_creation_flags, WalletCreationStatus& status, bilingual_str& error, std::vector& warnings) override + std::unique_ptr createWallet(const std::string& name, const SecureString& passphrase, uint64_t wallet_creation_flags, bilingual_str& error, std::vector& warnings) override { std::shared_ptr wallet; - status = CreateWallet(*m_context.chain, passphrase, wallet_creation_flags, name, true /* load_on_start */, error, warnings, wallet); - return MakeWallet(std::move(wallet)); + DatabaseOptions options; + DatabaseStatus status; + options.require_create = true; + options.create_flags = wallet_creation_flags; + options.create_passphrase = passphrase; + return MakeWallet(CreateWallet(*m_context.chain, name, true /* load_on_start */, options, status, error, warnings)); } std::unique_ptr loadWallet(const std::string& name, bilingual_str& error, std::vector& warnings) override { - return MakeWallet(LoadWallet(*m_context.chain, WalletLocation(name), true /* load_on_start */, error, warnings)); + DatabaseOptions options; + DatabaseStatus status; + options.require_existing = true; + return MakeWallet(LoadWallet(*m_context.chain, name, true /* load_on_start */, options, status, error, warnings)); } std::string getWalletDir() override { @@ -653,9 +659,9 @@ public: std::unique_ptr MakeWallet(const std::shared_ptr& wallet) { return wallet ? MakeUnique(wallet) : nullptr; } -std::unique_ptr MakeWalletClient(Chain& chain, ArgsManager& args, std::vector wallet_filenames) +std::unique_ptr MakeWalletClient(Chain& chain, ArgsManager& args) { - return MakeUnique(chain, args, std::move(wallet_filenames)); + return MakeUnique(chain, args); } } // namespace interfaces diff --git a/src/interfaces/wallet.h b/src/interfaces/wallet.h index b790e0b5cd..768e9f9d63 100644 --- a/src/interfaces/wallet.h +++ b/src/interfaces/wallet.h @@ -29,7 +29,6 @@ class CKey; class CWallet; enum class FeeReason; enum class TransactionError; -enum class WalletCreationStatus; enum isminetype : unsigned int; struct bilingual_str; struct CRecipient; @@ -345,7 +344,7 @@ class WalletClient : public ChainClient { public: //! Create new wallet. - virtual std::unique_ptr createWallet(const std::string& name, const SecureString& passphrase, uint64_t wallet_creation_flags, WalletCreationStatus& status, bilingual_str& error, std::vector& warnings) = 0; + virtual std::unique_ptr createWallet(const std::string& name, const SecureString& passphrase, uint64_t wallet_creation_flags, bilingual_str& error, std::vector& warnings) = 0; //! Load existing wallet. virtual std::unique_ptr loadWallet(const std::string& name, bilingual_str& error, std::vector& warnings) = 0; @@ -450,7 +449,7 @@ std::unique_ptr MakeWallet(const std::shared_ptr& wallet); //! Return implementation of ChainClient interface for a wallet client. This //! function will be undefined in builds where ENABLE_WALLET is false. -std::unique_ptr MakeWalletClient(Chain& chain, ArgsManager& args, std::vector wallet_filenames); +std::unique_ptr MakeWalletClient(Chain& chain, ArgsManager& args); } // namespace interfaces diff --git a/src/node/coinstats.cpp b/src/node/coinstats.cpp index dc9f044440..782aa76fc9 100644 --- a/src/node/coinstats.cpp +++ b/src/node/coinstats.cpp @@ -145,7 +145,7 @@ bool GetUTXOStats(CCoinsView* view, CCoinsStats& stats, CoinStatsHashType hash_t } // The legacy hash serializes the hashBlock -static void PrepareHash(CHashWriter& ss, CCoinsStats& stats) +static void PrepareHash(CHashWriter& ss, const CCoinsStats& stats) { ss << stats.hashBlock; } diff --git a/src/qt/bitcoingui.cpp b/src/qt/bitcoingui.cpp index c30bb725ee..acff2f2331 100644 --- a/src/qt/bitcoingui.cpp +++ b/src/qt/bitcoingui.cpp @@ -870,6 +870,11 @@ void BitcoinGUI::setWalletController(WalletController* wallet_controller) } } +WalletController* BitcoinGUI::getWalletController() +{ + return m_wallet_controller; +} + void BitcoinGUI::addWallet(WalletModel* walletModel) { if (!walletFrame) return; diff --git a/src/qt/bitcoingui.h b/src/qt/bitcoingui.h index 754b45572a..bf8e280086 100644 --- a/src/qt/bitcoingui.h +++ b/src/qt/bitcoingui.h @@ -80,6 +80,7 @@ public: void setClientModel(ClientModel *clientModel = nullptr, interfaces::BlockAndHeaderTipInfo* tip_info = nullptr); #ifdef ENABLE_WALLET void setWalletController(WalletController* wallet_controller); + WalletController* getWalletController(); #endif #ifdef ENABLE_WALLET diff --git a/src/qt/guiutil.cpp b/src/qt/guiutil.cpp index 79497599f6..3d4b39df8c 100644 --- a/src/qt/guiutil.cpp +++ b/src/qt/guiutil.cpp @@ -1455,7 +1455,7 @@ void updateFonts() std::vector vecIgnoreClasses{ "QWidget", "QDialog", "QFrame", "QStackedWidget", "QDesktopWidget", "QDesktopScreenWidget", "QTipLabel", "QMessageBox", "QMenu", "QComboBoxPrivateScroller", "QComboBoxPrivateContainer", - "QScrollBar", "QListView", "BitcoinGUI", "WalletView", "WalletFrame" + "QScrollBar", "QListView", "BitcoinGUI", "WalletView", "WalletFrame", "QVBoxLayout", "QGroupBox" }; std::vector vecIgnoreObjects{ "messagesWidget" diff --git a/src/qt/test/addressbooktests.cpp b/src/qt/test/addressbooktests.cpp index 31dad56d1a..55c1f3c5fc 100644 --- a/src/qt/test/addressbooktests.cpp +++ b/src/qt/test/addressbooktests.cpp @@ -59,7 +59,7 @@ void TestAddAddressesToSendBook(interfaces::Node& node) { TestChain100Setup test; node.setContext(&test.m_node); - std::shared_ptr wallet = std::make_shared(node.context()->chain.get(), WalletLocation(), CreateMockWalletDatabase()); + std::shared_ptr wallet = std::make_shared(node.context()->chain.get(), "", CreateMockWalletDatabase()); bool firstRun; wallet->LoadWallet(firstRun); diff --git a/src/qt/test/wallettests.cpp b/src/qt/test/wallettests.cpp index 6b6cd7e128..b35a14aa53 100644 --- a/src/qt/test/wallettests.cpp +++ b/src/qt/test/wallettests.cpp @@ -109,7 +109,7 @@ void TestGUI(interfaces::Node& node) test.CreateAndProcessBlock({}, GetScriptForRawPubKey(test.coinbaseKey.GetPubKey())); } node.setContext(&test.m_node); - std::shared_ptr wallet = std::make_shared(node.context()->chain.get(), WalletLocation(), CreateMockWalletDatabase()); + std::shared_ptr wallet = std::make_shared(node.context()->chain.get(), "", CreateMockWalletDatabase()); AddWallet(wallet); bool firstRun; wallet->LoadWallet(firstRun); diff --git a/src/qt/walletcontroller.cpp b/src/qt/walletcontroller.cpp index 68716ce72f..71194264a7 100644 --- a/src/qt/walletcontroller.cpp +++ b/src/qt/walletcontroller.cpp @@ -225,10 +225,9 @@ void CreateWalletActivity::createWallet() } QTimer::singleShot(500, worker(), [this, name, flags] { - WalletCreationStatus status; - std::unique_ptr wallet = node().walletClient().createWallet(name, m_passphrase, flags, status, m_error_message, m_warning_message); + std::unique_ptr wallet = node().walletClient().createWallet(name, m_passphrase, flags, m_error_message, m_warning_message); - if (status == WalletCreationStatus::SUCCESS) m_wallet_model = m_wallet_controller->getOrCreateWallet(std::move(wallet)); + if (wallet) m_wallet_model = m_wallet_controller->getOrCreateWallet(std::move(wallet)); QTimer::singleShot(500, this, &CreateWalletActivity::finish); }); diff --git a/src/qt/walletframe.cpp b/src/qt/walletframe.cpp index 77d116f2c8..5a478109d4 100644 --- a/src/qt/walletframe.cpp +++ b/src/qt/walletframe.cpp @@ -2,6 +2,8 @@ // Distributed under the MIT software license, see the accompanying // file COPYING or http://www.opensource.org/licenses/mit-license.php. +#include +#include #include #include @@ -12,8 +14,11 @@ #include +#include #include #include +#include +#include WalletFrame::WalletFrame(BitcoinGUI* _gui) : QFrame(_gui), @@ -26,9 +31,25 @@ WalletFrame::WalletFrame(BitcoinGUI* _gui) : walletFrameLayout->setContentsMargins(0,0,0,0); walletFrameLayout->addWidget(walletStack); - QLabel *noWallet = new QLabel(tr("No wallet has been loaded.")); + // hbox for no wallet + QGroupBox* no_wallet_group = new QGroupBox(walletStack); + QVBoxLayout* no_wallet_layout = new QVBoxLayout(no_wallet_group); + + QLabel *noWallet = new QLabel(tr("No wallet has been loaded.\nGo to File > Open Wallet to load a wallet.\n- OR -")); noWallet->setAlignment(Qt::AlignCenter); - walletStack->addWidget(noWallet); + no_wallet_layout->addWidget(noWallet, 0, Qt::AlignHCenter | Qt::AlignBottom); + + // A button for create wallet dialog + QPushButton* create_wallet_button = new QPushButton(tr("Create a new wallet"), walletStack); + connect(create_wallet_button, &QPushButton::clicked, [this] { + auto activity = new CreateWalletActivity(gui->getWalletController(), this); + connect(activity, &CreateWalletActivity::finished, activity, &QObject::deleteLater); + activity->create(); + }); + no_wallet_layout->addWidget(create_wallet_button, 0, Qt::AlignHCenter | Qt::AlignTop); + no_wallet_group->setLayout(no_wallet_layout); + + walletStack->addWidget(no_wallet_group); masternodeListPage = new MasternodeList(); walletStack->addWidget(masternodeListPage); diff --git a/src/rpc/rawtransaction.cpp b/src/rpc/rawtransaction.cpp index 4f490debd3..5350f8a44f 100644 --- a/src/rpc/rawtransaction.cpp +++ b/src/rpc/rawtransaction.cpp @@ -782,7 +782,9 @@ static UniValue signrawtransactionwithkey(const JSONRPCRequest& request) // Parse the prevtxs array ParsePrevouts(request.params[2], &keystore, coins); - return SignTransaction(mtx, &keystore, coins, request.params[3]); + UniValue result(UniValue::VOBJ); + SignTransaction(mtx, &keystore, coins, request.params[3], result); + return result; } UniValue sendrawtransaction(const JSONRPCRequest& request) diff --git a/src/rpc/rawtransaction_util.cpp b/src/rpc/rawtransaction_util.cpp index 1c38e5552b..600ed642f4 100644 --- a/src/rpc/rawtransaction_util.cpp +++ b/src/rpc/rawtransaction_util.cpp @@ -194,55 +194,31 @@ void ParsePrevouts(const UniValue& prevTxsUnival, FillableSigningProvider* keyst } } -UniValue SignTransaction(CMutableTransaction& mtx, const SigningProvider* keystore, const std::map& coins, const UniValue& hashType) +void SignTransaction(CMutableTransaction& mtx, const SigningProvider* keystore, const std::map& coins, const UniValue& hashType, UniValue& result) { int nHashType = ParseSighashString(hashType); - bool fHashSingle = ((nHashType & ~SIGHASH_ANYONECANPAY) == SIGHASH_SINGLE); - // Script verification errors + std::map input_errors; + + bool complete = SignTransaction(mtx, keystore, coins, nHashType, input_errors); + SignTransactionResultToJSON(mtx, complete, coins, input_errors, result); +} + +void SignTransactionResultToJSON(CMutableTransaction& mtx, bool complete, const std::map& coins, const std::map& input_errors, UniValue& result) +{ + // Make errors UniValue UniValue vErrors(UniValue::VARR); - - // Use CTransaction for the constant parts of the - // transaction to avoid rehashing. - const CTransaction txConst(mtx); - // Sign what we can: - for (unsigned int i = 0; i < mtx.vin.size(); i++) { - CTxIn& txin = mtx.vin[i]; - auto coin = coins.find(txin.prevout); - if (coin == coins.end() || coin->second.IsSpent()) { - TxInErrorToJSON(txin, vErrors, "Input not found or already spent"); - continue; - } - const CScript& prevPubKey = coin->second.out.scriptPubKey; - const CAmount& amount = coin->second.out.nValue; - - SignatureData sigdata = DataFromTransaction(mtx, i, coin->second.out); - // Only sign SIGHASH_SINGLE if there's a corresponding output: - if (!fHashSingle || (i < mtx.vout.size())) { - ProduceSignature(*keystore, MutableTransactionSignatureCreator(&mtx, i, amount, nHashType), prevPubKey, sigdata); - } - - UpdateInput(txin, sigdata); - - ScriptError serror = SCRIPT_ERR_OK; - if (!VerifyScript(txin.scriptSig, prevPubKey, STANDARD_SCRIPT_VERIFY_FLAGS, TransactionSignatureChecker(&txConst, i, amount), &serror)) { - 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 { - TxInErrorToJSON(txin, vErrors, ScriptErrorString(serror)); - } - } + for (const auto& err_pair : input_errors) { + TxInErrorToJSON(mtx.vin.at(err_pair.first), vErrors, err_pair.second); } - bool fComplete = vErrors.empty(); - UniValue result(UniValue::VOBJ); result.pushKV("hex", EncodeHexTx(CTransaction(mtx))); - result.pushKV("complete", fComplete); + result.pushKV("complete", complete); if (!vErrors.empty()) { + if (result.exists("errors")) { + vErrors.push_backV(result["errors"].getValues()); + } result.pushKV("errors", vErrors); } - - return result; } diff --git a/src/rpc/rawtransaction_util.h b/src/rpc/rawtransaction_util.h index b92b5c8425..2c37ab7b49 100644 --- a/src/rpc/rawtransaction_util.h +++ b/src/rpc/rawtransaction_util.h @@ -6,6 +6,7 @@ #define BITCOIN_RPC_RAWTRANSACTION_UTIL_H #include +#include class FillableSigningProvider; class UniValue; @@ -21,9 +22,10 @@ class SigningProvider; * @param keystore Temporary keystore containing signing keys * @param coins Map of unspent outputs * @param hashType The signature hash type - * @returns JSON object with details of signed transaction + * @param result JSON object where signed transaction results accumulate */ -UniValue SignTransaction(CMutableTransaction& mtx, const SigningProvider* keystore, const std::map& coins, const UniValue& hashType); +void SignTransaction(CMutableTransaction& mtx, const SigningProvider* keystore, const std::map& coins, const UniValue& hashType, UniValue& result); +void SignTransactionResultToJSON(CMutableTransaction& mtx, bool complete, const std::map& coins, const std::map& input_errors, UniValue& result); /** * Parse a prevtxs UniValue array and get the map of coins from it diff --git a/src/script/sigcache.cpp b/src/script/sigcache.cpp index 13fc9a07fc..7276710f36 100644 --- a/src/script/sigcache.cpp +++ b/src/script/sigcache.cpp @@ -56,7 +56,7 @@ public: return setValid.contains(entry, erase); } - void Set(uint256& entry) + void Set(const uint256& entry) { std::unique_lock lock(cs_sigcache); setValid.insert(entry); diff --git a/src/script/sign.cpp b/src/script/sign.cpp index 02fda848e8..0334dd16c8 100644 --- a/src/script/sign.cpp +++ b/src/script/sign.cpp @@ -382,3 +382,42 @@ bool IsSolvable(const SigningProvider& provider, const CScript& script) } return false; } + +bool SignTransaction(CMutableTransaction& mtx, const SigningProvider* keystore, const std::map& coins, int nHashType, std::map& input_errors) +{ + bool fHashSingle = ((nHashType & ~SIGHASH_ANYONECANPAY) == SIGHASH_SINGLE); + + // Use CTransaction for the constant parts of the + // transaction to avoid rehashing. + const CTransaction txConst(mtx); + // Sign what we can: + for (unsigned int i = 0; i < mtx.vin.size(); i++) { + CTxIn& txin = mtx.vin[i]; + auto coin = coins.find(txin.prevout); + if (coin == coins.end() || coin->second.IsSpent()) { + input_errors[i] = "Input not found or already spent"; + continue; + } + const CScript& prevPubKey = coin->second.out.scriptPubKey; + const CAmount& amount = coin->second.out.nValue; + + SignatureData sigdata = DataFromTransaction(mtx, i, coin->second.out); + // Only sign SIGHASH_SINGLE if there's a corresponding output: + if (!fHashSingle || (i < mtx.vout.size())) { + ProduceSignature(*keystore, MutableTransactionSignatureCreator(&mtx, i, amount, nHashType), prevPubKey, sigdata); + } + + UpdateInput(txin, sigdata); + + ScriptError serror = SCRIPT_ERR_OK; + if (!VerifyScript(txin.scriptSig, prevPubKey, STANDARD_SCRIPT_VERIFY_FLAGS, TransactionSignatureChecker(&txConst, i, amount), &serror)) { + if (serror == SCRIPT_ERR_INVALID_STACK_OPERATION) { + // Unable to sign input and verification failed (possible attempt to partially sign). + input_errors[i] = "Unable to sign input, invalid stack size (possibly missing key)"; + } else { + input_errors[i] = ScriptErrorString(serror); + } + } + } + return input_errors.empty(); +} diff --git a/src/script/sign.h b/src/script/sign.h index 5454b11601..6a909fc3bc 100644 --- a/src/script/sign.h +++ b/src/script/sign.h @@ -6,6 +6,7 @@ #ifndef BITCOIN_SCRIPT_SIGN_H #define BITCOIN_SCRIPT_SIGN_H +#include #include #include #include