diff options
Diffstat (limited to 'src/wallet')
-rw-r--r-- | src/wallet/rpcdump.cpp | 5 | ||||
-rw-r--r-- | src/wallet/rpcwallet.cpp | 5 | ||||
-rw-r--r-- | src/wallet/wallet.cpp | 167 | ||||
-rw-r--r-- | src/wallet/wallet.h | 32 |
4 files changed, 131 insertions, 78 deletions
diff --git a/src/wallet/rpcdump.cpp b/src/wallet/rpcdump.cpp index 5abf32480a..67c6d9ec64 100644 --- a/src/wallet/rpcdump.cpp +++ b/src/wallet/rpcdump.cpp @@ -619,9 +619,8 @@ UniValue dumpwallet(const JSONRPCRequest& request) throw JSONRPCError(RPC_INVALID_PARAMETER, "Cannot open wallet dump file"); std::map<CTxDestination, int64_t> mapKeyBirth; - std::set<CKeyID> setKeyPool; + const std::map<CKeyID, int64_t>& mapKeyPool = pwallet->GetAllReserveKeys(); pwallet->GetKeyBirthTimes(mapKeyBirth); - pwallet->GetAllReserveKeys(setKeyPool); // sort time/key pairs std::vector<std::pair<int64_t, CKeyID> > vKeyBirth; @@ -666,7 +665,7 @@ UniValue dumpwallet(const JSONRPCRequest& request) file << strprintf("label=%s", EncodeDumpString(pwallet->mapAddressBook[keyid].name)); } else if (keyid == masterKeyID) { file << "hdmaster=1"; - } else if (setKeyPool.count(keyid)) { + } else if (mapKeyPool.count(keyid)) { file << "reserve=1"; } else if (pwallet->mapKeyMetadata[keyid].hdKeypath == "m") { file << "inactivehdmaster=1"; diff --git a/src/wallet/rpcwallet.cpp b/src/wallet/rpcwallet.cpp index 2c6d93e1ad..8ef33f248b 100644 --- a/src/wallet/rpcwallet.cpp +++ b/src/wallet/rpcwallet.cpp @@ -2589,6 +2589,7 @@ UniValue resendwallettransactions(const JSONRPCRequest& request) "Immediately re-broadcast unconfirmed wallet transactions to all peers.\n" "Intended only for testing; the wallet code periodically re-broadcasts\n" "automatically.\n" + "Returns an RPC error if -walletbroadcast is set to false.\n" "Returns array of transaction ids that were re-broadcast.\n" ); @@ -2597,6 +2598,10 @@ UniValue resendwallettransactions(const JSONRPCRequest& request) LOCK2(cs_main, pwallet->cs_wallet); + if (!pwallet->GetBroadcastTransactions()) { + throw JSONRPCError(RPC_WALLET_ERROR, "Error: Wallet transaction broadcasting is disabled with -walletbroadcast"); + } + std::vector<uint256> txids = pwallet->ResendWalletTransactionsBefore(GetTime(), g_connman.get()); UniValue result(UniValue::VARR); for (const uint256& txid : txids) diff --git a/src/wallet/wallet.cpp b/src/wallet/wallet.cpp index 17b101ffef..c152b9d697 100644 --- a/src/wallet/wallet.cpp +++ b/src/wallet/wallet.cpp @@ -12,6 +12,7 @@ #include "consensus/consensus.h" #include "consensus/validation.h" #include "fs.h" +#include "init.h" #include "key.h" #include "keystore.h" #include "validation.h" @@ -80,6 +81,38 @@ std::string COutput::ToString() const return strprintf("COutput(%s, %d, %d) [%s]", tx->GetHash().ToString(), i, nDepth, FormatMoney(tx->tx->vout[i].nValue)); } +class CAffectedKeysVisitor : public boost::static_visitor<void> { +private: + const CKeyStore &keystore; + std::vector<CKeyID> &vKeys; + +public: + CAffectedKeysVisitor(const CKeyStore &keystoreIn, std::vector<CKeyID> &vKeysIn) : keystore(keystoreIn), vKeys(vKeysIn) {} + + void Process(const CScript &script) { + txnouttype type; + std::vector<CTxDestination> vDest; + int nRequired; + if (ExtractDestinations(script, type, vDest, nRequired)) { + for (const CTxDestination &dest : vDest) + boost::apply_visitor(*this, dest); + } + } + + void operator()(const CKeyID &keyId) { + if (keystore.HaveKey(keyId)) + vKeys.push_back(keyId); + } + + void operator()(const CScriptID &scriptId) { + CScript script; + if (keystore.GetCScript(scriptId, script)) + Process(script); + } + + void operator()(const CNoDestination &none) {} +}; + const CWalletTx* CWallet::GetWalletTx(const uint256& hash) const { LOCK(cs_wallet); @@ -1021,6 +1054,30 @@ bool CWallet::AddToWalletIfInvolvingMe(const CTransactionRef& ptx, const CBlockI if (fExisted && !fUpdate) return false; if (fExisted || IsMine(tx) || IsFromMe(tx)) { + /* Check if any keys in the wallet keypool that were supposed to be unused + * have appeared in a new transaction. If so, remove those keys from the keypool. + * This can happen when restoring an old wallet backup that does not contain + * the mostly recently created transactions from newer versions of the wallet. + */ + + // loop though all outputs + for (const CTxOut& txout: tx.vout) { + // extract addresses and check if they match with an unused keypool key + std::vector<CKeyID> vAffected; + CAffectedKeysVisitor(*this, vAffected).Process(txout.scriptPubKey); + for (const CKeyID &keyid : vAffected) { + std::map<CKeyID, int64_t>::const_iterator mi = m_pool_key_to_index.find(keyid); + if (mi != m_pool_key_to_index.end()) { + LogPrintf("%s: Detected a used keypool key, mark all keypool key up to this key as used\n", __func__); + MarkReserveKeysAsUsed(mi->second); + + if (!TopUpKeyPool()) { + LogPrintf("%s: Topping up keypool failed (locked wallet)\n", __func__); + } + } + } + } + CWalletTx wtx(this, ptx); // Get merkle branch if transaction was found in a block @@ -1868,6 +1925,7 @@ std::vector<uint256> CWallet::ResendWalletTransactionsBefore(int64_t nTime, CCon std::vector<uint256> result; LOCK(cs_wallet); + // Sort them in chronological order std::multimap<unsigned int, CWalletTx*> mapSorted; for (std::pair<const uint256, CWalletTx>& item : mapWallet) @@ -3049,6 +3107,7 @@ DBErrors CWallet::LoadWallet(bool& fFirstRunRet) LOCK(cs_wallet); setInternalKeyPool.clear(); setExternalKeyPool.clear(); + m_pool_key_to_index.clear(); // Note: can't top-up keypool here, because wallet is locked. // User will be prompted to unlock wallet the next operation // that requires a new key. @@ -3078,6 +3137,7 @@ DBErrors CWallet::ZapSelectTx(std::vector<uint256>& vHashIn, std::vector<uint256 { setInternalKeyPool.clear(); setExternalKeyPool.clear(); + m_pool_key_to_index.clear(); // Note: can't top-up keypool here, because wallet is locked. // User will be prompted to unlock wallet the next operation // that requires a new key. @@ -3104,6 +3164,7 @@ DBErrors CWallet::ZapWalletTx(std::vector<CWalletTx>& vWtx) LOCK(cs_wallet); setInternalKeyPool.clear(); setExternalKeyPool.clear(); + m_pool_key_to_index.clear(); // Note: can't top-up keypool here, because wallet is locked. // User will be prompted to unlock wallet the next operation // that requires a new key. @@ -3198,6 +3259,8 @@ bool CWallet::NewKeyPool() } setExternalKeyPool.clear(); + m_pool_key_to_index.clear(); + if (!TopUpKeyPool()) { return false; } @@ -3212,6 +3275,25 @@ size_t CWallet::KeypoolCountExternalKeys() return setExternalKeyPool.size(); } +void CWallet::LoadKeyPool(int64_t nIndex, const CKeyPool &keypool) +{ + AssertLockHeld(cs_wallet); + if (keypool.fInternal) { + setInternalKeyPool.insert(nIndex); + } else { + setExternalKeyPool.insert(nIndex); + } + m_max_keypool_index = std::max(m_max_keypool_index, nIndex); + m_pool_key_to_index[keypool.vchPubKey.GetID()] = nIndex; + + // If no metadata exists yet, create a default with the pool key's + // creation time. Note that this may be overwritten by actually + // stored metadata for that key later, which is fine. + CKeyID keyid = keypool.vchPubKey.GetID(); + if (mapKeyMetadata.count(keyid) == 0) + mapKeyMetadata[keyid] = CKeyMetadata(keypool.nTime); +} + bool CWallet::TopUpKeyPool(unsigned int kpSize) { { @@ -3248,7 +3330,8 @@ bool CWallet::TopUpKeyPool(unsigned int kpSize) assert(m_max_keypool_index < std::numeric_limits<int64_t>::max()); // How in the hell did you use so many keys? int64_t index = ++m_max_keypool_index; - if (!walletdb.WritePool(index, CKeyPool(GenerateNewKey(walletdb, internal), internal))) { + CPubKey pubkey(GenerateNewKey(walletdb, internal)); + if (!walletdb.WritePool(index, CKeyPool(pubkey, internal))) { throw std::runtime_error(std::string(__func__) + ": writing generated key failed"); } @@ -3257,6 +3340,7 @@ bool CWallet::TopUpKeyPool(unsigned int kpSize) } else { setExternalKeyPool.insert(index); } + m_pool_key_to_index[pubkey.GetID()] = index; } if (missingInternal + missingExternal > 0) { LogPrintf("keypool added %d keys (%d internal), size=%u (%u internal)\n", missingInternal + missingExternal, missingInternal, setInternalKeyPool.size() + setExternalKeyPool.size(), setInternalKeyPool.size()); @@ -3298,6 +3382,7 @@ void CWallet::ReserveKeyFromKeyPool(int64_t& nIndex, CKeyPool& keypool, bool fRe } assert(keypool.vchPubKey.IsValid()); + m_pool_key_to_index.erase(keypool.vchPubKey.GetID()); LogPrintf("keypool reserve %d\n", nIndex); } } @@ -3310,7 +3395,7 @@ void CWallet::KeepKey(int64_t nIndex) LogPrintf("keypool keep %d\n", nIndex); } -void CWallet::ReturnKey(int64_t nIndex, bool fInternal) +void CWallet::ReturnKey(int64_t nIndex, bool fInternal, const CPubKey& pubkey) { // Return to key pool { @@ -3320,6 +3405,7 @@ void CWallet::ReturnKey(int64_t nIndex, bool fInternal) } else { setExternalKeyPool.insert(nIndex); } + m_pool_key_to_index[pubkey.GetID()] = nIndex; } LogPrintf("keypool return %d\n", nIndex); } @@ -3549,41 +3635,39 @@ void CReserveKey::KeepKey() void CReserveKey::ReturnKey() { if (nIndex != -1) { - pwallet->ReturnKey(nIndex, fInternal); + pwallet->ReturnKey(nIndex, fInternal, vchPubKey); } nIndex = -1; vchPubKey = CPubKey(); } -static void LoadReserveKeysToSet(std::set<CKeyID>& setAddress, const std::set<int64_t>& setKeyPool, CWalletDB& walletdb) { - for (const int64_t& id : setKeyPool) - { - CKeyPool keypool; - if (!walletdb.ReadPool(id, keypool)) - throw std::runtime_error(std::string(__func__) + ": read failed"); - assert(keypool.vchPubKey.IsValid()); - CKeyID keyID = keypool.vchPubKey.GetID(); - setAddress.insert(keyID); - } -} - -void CWallet::GetAllReserveKeys(std::set<CKeyID>& setAddress) const +void CWallet::MarkReserveKeysAsUsed(int64_t keypool_id) { - setAddress.clear(); + AssertLockHeld(cs_wallet); + bool internal = setInternalKeyPool.count(keypool_id); + if (!internal) assert(setExternalKeyPool.count(keypool_id)); + std::set<int64_t> *setKeyPool = internal ? &setInternalKeyPool : &setExternalKeyPool; + auto it = setKeyPool->begin(); CWalletDB walletdb(*dbw); + while (it != std::end(*setKeyPool)) { + const int64_t& index = *(it); + if (index > keypool_id) break; // set*KeyPool is ordered - LOCK2(cs_main, cs_wallet); - LoadReserveKeysToSet(setAddress, setInternalKeyPool, walletdb); - LoadReserveKeysToSet(setAddress, setExternalKeyPool, walletdb); - - for (const CKeyID& keyID : setAddress) { - if (!HaveKey(keyID)) { - throw std::runtime_error(std::string(__func__) + ": unknown key in key pool"); + CKeyPool keypool; + if (walletdb.ReadPool(index, keypool)) { //TODO: This should be unnecessary + m_pool_key_to_index.erase(keypool.vchPubKey.GetID()); } + walletdb.ErasePool(index); + it = setKeyPool->erase(it); } } +bool CWallet::HasUnusedKeys(int min_keys) const +{ + return setExternalKeyPool.size() >= min_keys && (setInternalKeyPool.size() >= min_keys || !CanSupportFeature(FEATURE_HD_SPLIT)); +} + void CWallet::GetScriptForMining(std::shared_ptr<CReserveScript> &script) { std::shared_ptr<CReserveKey> rKey = std::make_shared<CReserveKey>(this); @@ -3633,38 +3717,6 @@ void CWallet::ListLockedCoins(std::vector<COutPoint>& vOutpts) const /** @} */ // end of Actions -class CAffectedKeysVisitor : public boost::static_visitor<void> { -private: - const CKeyStore &keystore; - std::vector<CKeyID> &vKeys; - -public: - CAffectedKeysVisitor(const CKeyStore &keystoreIn, std::vector<CKeyID> &vKeysIn) : keystore(keystoreIn), vKeys(vKeysIn) {} - - void Process(const CScript &script) { - txnouttype type; - std::vector<CTxDestination> vDest; - int nRequired; - if (ExtractDestinations(script, type, vDest, nRequired)) { - for (const CTxDestination &dest : vDest) - boost::apply_visitor(*this, dest); - } - } - - void operator()(const CKeyID &keyId) { - if (keystore.HaveKey(keyId)) - vKeys.push_back(keyId); - } - - void operator()(const CScriptID &scriptId) { - CScript script; - if (keystore.GetCScript(scriptId, script)) - Process(script); - } - - void operator()(const CNoDestination &none) {} -}; - void CWallet::GetKeyBirthTimes(std::map<CTxDestination, int64_t> &mapKeyBirth) const { AssertLockHeld(cs_wallet); // mapKeyMetadata mapKeyBirth.clear(); @@ -3989,6 +4041,9 @@ CWallet* CWallet::CreateWalletFromFile(const std::string walletFile) RegisterValidationInterface(walletInstance); + // Try to top up keypool. No-op if the wallet is locked. + walletInstance->TopUpKeyPool(); + CBlockIndex *pindexRescan = chainActive.Genesis(); if (!GetBoolArg("-rescan", false)) { diff --git a/src/wallet/wallet.h b/src/wallet/wallet.h index a75f1b3fe5..f97a99d82a 100644 --- a/src/wallet/wallet.h +++ b/src/wallet/wallet.h @@ -470,6 +470,7 @@ public: int64_t GetTxTime() const; int GetRequestCount() const; + // RelayWalletTransaction may only be called if fBroadcastTransactions! bool RelayWalletTransaction(CConnman* connman); std::set<uint256> GetConflicts() const; @@ -704,6 +705,7 @@ private: std::set<int64_t> setInternalKeyPool; std::set<int64_t> setExternalKeyPool; int64_t m_max_keypool_index; + std::map<CKeyID, int64_t> m_pool_key_to_index; int64_t nTimeFirstKey; @@ -746,22 +748,7 @@ public: } } - void LoadKeyPool(int64_t nIndex, const CKeyPool &keypool) - { - if (keypool.fInternal) { - setInternalKeyPool.insert(nIndex); - } else { - setExternalKeyPool.insert(nIndex); - } - m_max_keypool_index = std::max(m_max_keypool_index, nIndex); - - // If no metadata exists yet, create a default with the pool key's - // creation time. Note that this may be overwritten by actually - // stored metadata for that key later, which is fine. - CKeyID keyid = keypool.vchPubKey.GetID(); - if (mapKeyMetadata.count(keyid) == 0) - mapKeyMetadata[keyid] = CKeyMetadata(keypool.nTime); - } + void LoadKeyPool(int64_t nIndex, const CKeyPool &keypool); // Map from Key ID (for regular keys) or Script ID (for watch-only keys) to // key metadata. @@ -827,7 +814,7 @@ public: const CWalletTx* GetWalletTx(const uint256& hash) const; //! check whether we are allowed to upgrade (or already support) to the named feature - bool CanSupportFeature(enum WalletFeature wf) { AssertLockHeld(cs_wallet); return nWalletMaxVersion >= wf; } + bool CanSupportFeature(enum WalletFeature wf) const { AssertLockHeld(cs_wallet); return nWalletMaxVersion >= wf; } /** * populate vCoins with vector of available COutputs. @@ -937,6 +924,7 @@ public: CBlockIndex* ScanForWalletTransactions(CBlockIndex* pindexStart, bool fUpdate = false); void ReacceptWalletTransactions(); void ResendWalletTransactions(int64_t nBestBlockTime, CConnman* connman) override; + // ResendWalletTransactionsBefore may only be called if fBroadcastTransactions! std::vector<uint256> ResendWalletTransactionsBefore(int64_t nTime, CConnman* connman); CAmount GetBalance() const; CAmount GetUnconfirmedBalance() const; @@ -988,10 +976,16 @@ public: bool TopUpKeyPool(unsigned int kpSize = 0); void ReserveKeyFromKeyPool(int64_t& nIndex, CKeyPool& keypool, bool fRequestedInternal); void KeepKey(int64_t nIndex); - void ReturnKey(int64_t nIndex, bool fInternal); + void ReturnKey(int64_t nIndex, bool fInternal, const CPubKey& pubkey); bool GetKeyFromPool(CPubKey &key, bool internal = false); int64_t GetOldestKeyPoolTime(); - void GetAllReserveKeys(std::set<CKeyID>& setAddress) const; + /** + * Marks all keys in the keypool up to and including reserve_key as used. + */ + void MarkReserveKeysAsUsed(int64_t keypool_id); + const std::map<CKeyID, int64_t>& GetAllReserveKeys() const { return m_pool_key_to_index; } + /** Does the wallet have at least min_keys in the keypool? */ + bool HasUnusedKeys(int min_keys) const; std::set< std::set<CTxDestination> > GetAddressGroupings(); std::map<CTxDestination, CAmount> GetAddressBalances(); |