diff --git a/src/interfaces/wallet.h b/src/interfaces/wallet.h --- a/src/interfaces/wallet.h +++ b/src/interfaces/wallet.h @@ -262,6 +262,11 @@ using WatchOnlyChangedFn = std::function; virtual std::unique_ptr handleWatchOnlyChanged(WatchOnlyChangedFn fn) = 0; + + //! Register handler for keypool changed messages. + using CanGetAddressesChangedFn = std::function; + virtual std::unique_ptr + handleCanGetAddressesChanged(CanGetAddressesChangedFn fn) = 0; }; //! Tracking object returned by CreateTransaction and passed to diff --git a/src/interfaces/wallet.cpp b/src/interfaces/wallet.cpp --- a/src/interfaces/wallet.cpp +++ b/src/interfaces/wallet.cpp @@ -431,6 +431,11 @@ handleWatchOnlyChanged(WatchOnlyChangedFn fn) override { return MakeHandler(m_wallet.NotifyWatchonlyChanged.connect(fn)); } + std::unique_ptr + handleCanGetAddressesChanged(CanGetAddressesChangedFn fn) override { + return MakeHandler( + m_wallet.NotifyCanGetAddressesChanged.connect(fn)); + } Amount getRequiredFee(unsigned int tx_bytes) override { return GetRequiredFee(m_wallet, tx_bytes); } diff --git a/src/qt/receivecoinsdialog.cpp b/src/qt/receivecoinsdialog.cpp --- a/src/qt/receivecoinsdialog.cpp +++ b/src/qt/receivecoinsdialog.cpp @@ -108,9 +108,15 @@ columnResizingFixer = new GUIUtil::TableViewLastColumnResizingFixer( tableView, AMOUNT_MINIMUM_COLUMN_WIDTH, DATE_COLUMN_WIDTH, this); - // Eventually disable the main receive button if private key operations - // are disabled. - ui->receiveButton->setEnabled(!model->privateKeysDisabled()); + // Set the button to be enabled or disabled based on whether the wallet + // can give out new addresses. + ui->receiveButton->setEnabled(model->canGetAddresses()); + + // Enable/disable the receive button if the wallet is now able/unable to + // give out new addresses. + connect(model, &WalletModel::canGetAddressesChanged, [this] { + ui->receiveButton->setEnabled(model->canGetAddresses()); + }); } } diff --git a/src/qt/walletmodel.h b/src/qt/walletmodel.h --- a/src/qt/walletmodel.h +++ b/src/qt/walletmodel.h @@ -227,6 +227,7 @@ static bool isWalletEnabled(); bool privateKeysDisabled() const; + bool canGetAddresses() const; interfaces::Node &node() const { return m_node; } interfaces::Wallet &wallet() const { return *m_wallet; } @@ -250,6 +251,7 @@ std::unique_ptr m_handler_transaction_changed; std::unique_ptr m_handler_show_progress; std::unique_ptr m_handler_watch_only_changed; + std::unique_ptr m_handler_can_get_addrs_changed; interfaces::Node &m_node; bool fHaveWatchOnly; @@ -303,6 +305,9 @@ // Signal that wallet is about to be removed void unload(); + // Notify that there are now keys in the keypool + void canGetAddressesChanged(); + public Q_SLOTS: /** Wallet status might have changed. */ void updateStatus(); diff --git a/src/qt/walletmodel.cpp b/src/qt/walletmodel.cpp --- a/src/qt/walletmodel.cpp +++ b/src/qt/walletmodel.cpp @@ -412,6 +412,10 @@ Q_ARG(bool, fHaveWatchonly)); } +static void NotifyCanGetAddressesChanged(WalletModel *walletmodel) { + QMetaObject::invokeMethod(walletmodel, "canGetAddressesChanged"); +} + void WalletModel::subscribeToCoreSignals() { // Connect signals to wallet m_handler_unload = m_wallet->handleUnload(std::bind(&NotifyUnload, this)); @@ -428,6 +432,8 @@ ShowProgress, this, std::placeholders::_1, std::placeholders::_2)); m_handler_watch_only_changed = m_wallet->handleWatchOnlyChanged( std::bind(NotifyWatchonlyChanged, this, std::placeholders::_1)); + m_handler_can_get_addrs_changed = m_wallet->handleCanGetAddressesChanged( + std::bind(NotifyCanGetAddressesChanged, this)); } void WalletModel::unsubscribeFromCoreSignals() { @@ -438,6 +444,7 @@ m_handler_transaction_changed->disconnect(); m_handler_show_progress->disconnect(); m_handler_watch_only_changed->disconnect(); + m_handler_can_get_addrs_changed->disconnect(); } // WalletModel::UnlockContext implementation @@ -498,6 +505,18 @@ return m_wallet->IsWalletFlagSet(WALLET_FLAG_DISABLE_PRIVATE_KEYS); } +bool WalletModel::canGetAddresses() const { + // The wallet can provide a fresh address if: + // * hdEnabled(): an HD seed is present; or + // * it is a legacy wallet, because: + // * !hdEnabled(): an HD seed is not present; and + // * !IsWalletFlagSet(WALLET_FLAG_DISABLE_PRIVATE_KEYS): private keys + // have not been disabled (which results in hdEnabled() == true) + return m_wallet->hdEnabled() || + (!m_wallet->hdEnabled() && + !m_wallet->IsWalletFlagSet(WALLET_FLAG_DISABLE_PRIVATE_KEYS)); +} + QString WalletModel::getWalletName() const { return QString::fromStdString(m_wallet->getWalletName()); } diff --git a/src/wallet/wallet.h b/src/wallet/wallet.h --- a/src/wallet/wallet.h +++ b/src/wallet/wallet.h @@ -1306,6 +1306,9 @@ /** Watch-only address added */ boost::signals2::signal NotifyWatchonlyChanged; + /** Keypool has new keys */ + boost::signals2::signal NotifyCanGetAddressesChanged; + /** Inquire whether this wallet broadcasts transactions. */ bool GetBroadcastTransactions() const { return fBroadcastTransactions; } /** Set whether this wallet broadcasts transactions. */ diff --git a/src/wallet/wallet.cpp b/src/wallet/wallet.cpp --- a/src/wallet/wallet.cpp +++ b/src/wallet/wallet.cpp @@ -1577,6 +1577,7 @@ : CHDChain::VERSION_HD_BASE; newHdChain.seed_id = seed.GetID(); SetHDChain(newHdChain, false); + NotifyCanGetAddressesChanged(); } void CWallet::SetHDChain(const CHDChain &chain, bool memonly) { @@ -3616,66 +3617,68 @@ if (IsWalletFlagSet(WALLET_FLAG_DISABLE_PRIVATE_KEYS)) { return false; } - LOCK(cs_wallet); + { + LOCK(cs_wallet); - if (IsLocked()) { - return false; - } + if (IsLocked()) { + return false; + } - // Top up key pool - unsigned int nTargetSize; - if (kpSize > 0) { - nTargetSize = kpSize; - } else { - nTargetSize = std::max( - gArgs.GetArg("-keypool", DEFAULT_KEYPOOL_SIZE), 0); - } + // Top up key pool + unsigned int nTargetSize; + if (kpSize > 0) { + nTargetSize = kpSize; + } else { + nTargetSize = std::max( + gArgs.GetArg("-keypool", DEFAULT_KEYPOOL_SIZE), 0); + } - // count amount of available keys (internal, external) - // make sure the keypool of external and internal keys fits the user - // selected target (-keypool) - int64_t missingExternal = std::max( - std::max(nTargetSize, 1) - setExternalKeyPool.size(), 0); - int64_t missingInternal = std::max( - std::max(nTargetSize, 1) - setInternalKeyPool.size(), 0); + // count amount of available keys (internal, external) + // make sure the keypool of external and internal keys fits the user + // selected target (-keypool) + int64_t missingExternal = std::max( + std::max(nTargetSize, 1) - setExternalKeyPool.size(), 0); + int64_t missingInternal = std::max( + std::max(nTargetSize, 1) - setInternalKeyPool.size(), 0); - if (!IsHDEnabled() || !CanSupportFeature(FEATURE_HD_SPLIT)) { - // don't create extra internal keys - missingInternal = 0; - } - bool internal = false; - WalletBatch batch(*database); - for (int64_t i = missingInternal + missingExternal; i--;) { - if (i < missingInternal) { - internal = true; + if (!IsHDEnabled() || !CanSupportFeature(FEATURE_HD_SPLIT)) { + // don't create extra internal keys + missingInternal = 0; } + bool internal = false; + WalletBatch batch(*database); + for (int64_t i = missingInternal + missingExternal; i--;) { + if (i < missingInternal) { + internal = true; + } - // How in the hell did you use so many keys? - assert(m_max_keypool_index < std::numeric_limits::max()); - int64_t index = ++m_max_keypool_index; + // How in the hell did you use so many keys? + assert(m_max_keypool_index < std::numeric_limits::max()); + int64_t index = ++m_max_keypool_index; - CPubKey pubkey(GenerateNewKey(batch, internal)); - if (!batch.WritePool(index, CKeyPool(pubkey, internal))) { - throw std::runtime_error(std::string(__func__) + - ": writing generated key failed"); - } + CPubKey pubkey(GenerateNewKey(batch, internal)); + if (!batch.WritePool(index, CKeyPool(pubkey, internal))) { + throw std::runtime_error(std::string(__func__) + + ": writing generated key failed"); + } - if (internal) { - setInternalKeyPool.insert(index); - } else { - setExternalKeyPool.insert(index); + if (internal) { + setInternalKeyPool.insert(index); + } else { + setExternalKeyPool.insert(index); + } + m_pool_key_to_index[pubkey.GetID()] = index; + } + if (missingInternal + missingExternal > 0) { + WalletLogPrintf( + "keypool added %d keys (%d internal), size=%u (%u internal)\n", + missingInternal + missingExternal, missingInternal, + setInternalKeyPool.size() + setExternalKeyPool.size() + + set_pre_split_keypool.size(), + setInternalKeyPool.size()); } - m_pool_key_to_index[pubkey.GetID()] = index; - } - if (missingInternal + missingExternal > 0) { - WalletLogPrintf( - "keypool added %d keys (%d internal), size=%u (%u internal)\n", - missingInternal + missingExternal, missingInternal, - setInternalKeyPool.size() + setExternalKeyPool.size() + - set_pre_split_keypool.size(), - setInternalKeyPool.size()); } - + NotifyCanGetAddressesChanged(); return true; } @@ -3683,52 +3686,53 @@ bool fRequestedInternal) { nIndex = -1; keypool.vchPubKey = CPubKey(); + { + LOCK(cs_wallet); - LOCK(cs_wallet); + if (!IsLocked()) { + TopUpKeyPool(); + } - if (!IsLocked()) { - TopUpKeyPool(); - } + bool fReturningInternal = IsHDEnabled() && + CanSupportFeature(FEATURE_HD_SPLIT) && + fRequestedInternal; + bool use_split_keypool = set_pre_split_keypool.empty(); + std::set &setKeyPool = + use_split_keypool + ? (fReturningInternal ? setInternalKeyPool : setExternalKeyPool) + : set_pre_split_keypool; - bool fReturningInternal = IsHDEnabled() && - CanSupportFeature(FEATURE_HD_SPLIT) && - fRequestedInternal; - bool use_split_keypool = set_pre_split_keypool.empty(); - std::set &setKeyPool = - use_split_keypool - ? (fReturningInternal ? setInternalKeyPool : setExternalKeyPool) - : set_pre_split_keypool; + // Get the oldest key + if (setKeyPool.empty()) { + return false; + } - // Get the oldest key - if (setKeyPool.empty()) { - return false; - } + WalletBatch batch(*database); - WalletBatch batch(*database); + auto it = setKeyPool.begin(); + nIndex = *it; + setKeyPool.erase(it); + if (!batch.ReadPool(nIndex, keypool)) { + throw std::runtime_error(std::string(__func__) + ": read failed"); + } + if (!HaveKey(keypool.vchPubKey.GetID())) { + throw std::runtime_error(std::string(__func__) + + ": unknown key in key pool"); + } + // If the key was pre-split keypool, we don't care about what type it is + if (use_split_keypool && keypool.fInternal != fReturningInternal) { + throw std::runtime_error(std::string(__func__) + + ": keypool entry misclassified"); + } + if (!keypool.vchPubKey.IsValid()) { + throw std::runtime_error(std::string(__func__) + + ": keypool entry invalid"); + } - auto it = setKeyPool.begin(); - nIndex = *it; - setKeyPool.erase(it); - if (!batch.ReadPool(nIndex, keypool)) { - throw std::runtime_error(std::string(__func__) + ": read failed"); - } - if (!HaveKey(keypool.vchPubKey.GetID())) { - throw std::runtime_error(std::string(__func__) + - ": unknown key in key pool"); - } - // If the key was pre-split keypool, we don't care about what type it is - if (use_split_keypool && keypool.fInternal != fReturningInternal) { - throw std::runtime_error(std::string(__func__) + - ": keypool entry misclassified"); + m_pool_key_to_index.erase(keypool.vchPubKey.GetID()); + WalletLogPrintf("keypool reserve %d\n", nIndex); } - if (!keypool.vchPubKey.IsValid()) { - throw std::runtime_error(std::string(__func__) + - ": keypool entry invalid"); - } - - m_pool_key_to_index.erase(keypool.vchPubKey.GetID()); - WalletLogPrintf("keypool reserve %d\n", nIndex); - + NotifyCanGetAddressesChanged(); return true; } @@ -3751,6 +3755,7 @@ setExternalKeyPool.insert(nIndex); } m_pool_key_to_index[pubkey.GetID()] = nIndex; + NotifyCanGetAddressesChanged(); } WalletLogPrintf("keypool return %d\n", nIndex);