diff options
Diffstat (limited to 'src/wallet/wallet.cpp')
-rw-r--r-- | src/wallet/wallet.cpp | 531 |
1 files changed, 229 insertions, 302 deletions
diff --git a/src/wallet/wallet.cpp b/src/wallet/wallet.cpp index 81e65b044e..2c15ff7d90 100644 --- a/src/wallet/wallet.cpp +++ b/src/wallet/wallet.cpp @@ -82,7 +82,7 @@ std::shared_ptr<CWallet> GetWallet(const std::string& name) // Custom deleter for shared_ptr<CWallet>. static void ReleaseWallet(CWallet* wallet) { - LogPrintf("Releasing wallet %s\n", wallet->GetName()); + wallet->WalletLogPrintf("Releasing wallet\n"); wallet->BlockUntilSyncedToCurrentChain(); wallet->Flush(); delete wallet; @@ -164,6 +164,7 @@ const CWalletTx* CWallet::GetWalletTx(const uint256& hash) const CPubKey CWallet::GenerateNewKey(WalletBatch &batch, bool internal) { + assert(!IsWalletFlagSet(WALLET_FLAG_DISABLE_PRIVATE_KEYS)); AssertLockHeld(cs_wallet); // mapKeyMetadata bool fCompressed = CanSupportFeature(FEATURE_COMPRPUBKEY); // default to compressed public keys if we want 0.6.0 wallets @@ -303,20 +304,18 @@ bool CWallet::AddCryptedKey(const CPubKey &vchPubKey, } } -bool CWallet::LoadKeyMetadata(const CKeyID& keyID, const CKeyMetadata &meta) +void CWallet::LoadKeyMetadata(const CKeyID& keyID, const CKeyMetadata &meta) { AssertLockHeld(cs_wallet); // mapKeyMetadata UpdateTimeFirstKey(meta.nCreateTime); mapKeyMetadata[keyID] = meta; - return true; } -bool CWallet::LoadScriptMetadata(const CScriptID& script_id, const CKeyMetadata &meta) +void CWallet::LoadScriptMetadata(const CScriptID& script_id, const CKeyMetadata &meta) { AssertLockHeld(cs_wallet); // m_script_metadata UpdateTimeFirstKey(meta.nCreateTime); m_script_metadata[script_id] = meta; - return true; } bool CWallet::LoadCryptedKey(const CPubKey &vchPubKey, const std::vector<unsigned char> &vchCryptedSecret) @@ -355,8 +354,7 @@ bool CWallet::LoadCScript(const CScript& redeemScript) if (redeemScript.size() > MAX_SCRIPT_ELEMENT_SIZE) { std::string strAddr = EncodeDestination(CScriptID(redeemScript)); - LogPrintf("%s: Warning: This wallet contains a redeemScript of size %i which exceeds maximum size %i thus can never be redeemed. Do not use address %s.\n", - __func__, redeemScript.size(), MAX_SCRIPT_ELEMENT_SIZE, strAddr); + WalletLogPrintf("%s: Warning: This wallet contains a redeemScript of size %i which exceeds maximum size %i thus can never be redeemed. Do not use address %s.\n", __func__, redeemScript.size(), MAX_SCRIPT_ELEMENT_SIZE, strAddr); return true; } @@ -446,7 +444,7 @@ bool CWallet::ChangeWalletPassphrase(const SecureString& strOldWalletPassphrase, if (pMasterKey.second.nDeriveIterations < 25000) pMasterKey.second.nDeriveIterations = 25000; - LogPrintf("Wallet passphrase changed to an nDeriveIterations of %i\n", pMasterKey.second.nDeriveIterations); + WalletLogPrintf("Wallet passphrase changed to an nDeriveIterations of %i\n", pMasterKey.second.nDeriveIterations); if (!crypter.SetKeyFromPassphrase(strNewWalletPassphrase, pMasterKey.second.vchSalt, pMasterKey.second.nDeriveIterations, pMasterKey.second.nDerivationMethod)) return false; @@ -469,11 +467,11 @@ void CWallet::ChainStateFlushed(const CBlockLocator& loc) batch.WriteBestBlock(loc); } -bool CWallet::SetMinVersion(enum WalletFeature nVersion, WalletBatch* batch_in, bool fExplicit) +void CWallet::SetMinVersion(enum WalletFeature nVersion, WalletBatch* batch_in, bool fExplicit) { LOCK(cs_wallet); // nWalletVersion if (nWalletVersion >= nVersion) - return true; + return; // when doing an explicit upgrade, if we pass the max version permitted, upgrade all the way if (fExplicit && nVersion > nWalletMaxVersion) @@ -491,8 +489,6 @@ bool CWallet::SetMinVersion(enum WalletFeature nVersion, WalletBatch* batch_in, if (!batch_in) delete batch; } - - return true; } bool CWallet::SetMaxVersion(int nVersion) @@ -656,7 +652,7 @@ bool CWallet::EncryptWallet(const SecureString& strWalletPassphrase) if (kMasterKey.nDeriveIterations < 25000) kMasterKey.nDeriveIterations = 25000; - LogPrintf("Encrypting Wallet with an nDeriveIterations of %i\n", kMasterKey.nDeriveIterations); + WalletLogPrintf("Encrypting Wallet with an nDeriveIterations of %i\n", kMasterKey.nDeriveIterations); if (!crypter.SetKeyFromPassphrase(strWalletPassphrase, kMasterKey.vchSalt, kMasterKey.nDeriveIterations, kMasterKey.nDerivationMethod)) return false; @@ -702,9 +698,7 @@ bool CWallet::EncryptWallet(const SecureString& strWalletPassphrase) // if we are using HD, replace the HD seed with a new one if (IsHDEnabled()) { - if (!SetHDSeed(GenerateNewSeed())) { - return false; - } + SetHDSeed(GenerateNewSeed()); } NewKeyPool(); @@ -912,7 +906,7 @@ bool CWallet::MarkReplaced(const uint256& originalHash, const uint256& newHash) bool success = true; if (!batch.WriteTx(wtx)) { - LogPrintf("%s: Updating batch tx %s failed\n", __func__, wtx.GetHash().ToString()); + WalletLogPrintf("%s: Updating batch tx %s failed\n", __func__, wtx.GetHash().ToString()); success = false; } @@ -979,7 +973,7 @@ bool CWallet::AddToWallet(const CWalletTx& wtxIn, bool fFlushOnClose) } //// debug print - LogPrintf("AddToWallet %s %s%s\n", wtxIn.GetHash().ToString(), (fInsertedNew ? "new" : ""), (fUpdated ? "update" : "")); + WalletLogPrintf("AddToWallet %s %s%s\n", wtxIn.GetHash().ToString(), (fInsertedNew ? "new" : ""), (fUpdated ? "update" : "")); // Write to disk if (fInsertedNew || fUpdated) @@ -1005,7 +999,7 @@ bool CWallet::AddToWallet(const CWalletTx& wtxIn, bool fFlushOnClose) return true; } -bool CWallet::LoadToWallet(const CWalletTx& wtxIn) +void CWallet::LoadToWallet(const CWalletTx& wtxIn) { uint256 hash = wtxIn.GetHash(); const auto& ins = mapWallet.emplace(hash, wtxIn); @@ -1024,23 +1018,8 @@ bool CWallet::LoadToWallet(const CWalletTx& wtxIn) } } } - - return true; } -/** - * Add a transaction to the wallet, or update it. pIndex and posInBlock should - * be set when the transaction was known to be included in a block. When - * pIndex == nullptr, then wallet state is not updated in AddToWallet, but - * notifications happen and cached balances are marked dirty. - * - * If fUpdate is true, existing transactions will be updated. - * TODO: One exception to this is that the abandoned state is cleared under the - * assumption that any further notification of a transaction that was considered - * abandoned is an indication that it is not safe to be considered abandoned. - * Abandoned state should probably be more carefully tracked via different - * posInBlock signals or by checking mempool presence when necessary. - */ bool CWallet::AddToWalletIfInvolvingMe(const CTransactionRef& ptx, const CBlockIndex* pIndex, int posInBlock, bool fUpdate) { const CTransaction& tx = *ptx; @@ -1052,7 +1031,7 @@ bool CWallet::AddToWalletIfInvolvingMe(const CTransactionRef& ptx, const CBlockI std::pair<TxSpends::const_iterator, TxSpends::const_iterator> range = mapTxSpends.equal_range(txin.prevout); while (range.first != range.second) { if (range.first->second != tx.GetHash()) { - LogPrintf("Transaction %s (in block %s) conflicts with wallet transaction %s (both spend %s:%i)\n", tx.GetHash().ToString(), pIndex->GetBlockHash().ToString(), range.first->second.ToString(), range.first->first.hash.ToString(), range.first->first.n); + WalletLogPrintf("Transaction %s (in block %s) conflicts with wallet transaction %s (both spend %s:%i)\n", tx.GetHash().ToString(), pIndex->GetBlockHash().ToString(), range.first->second.ToString(), range.first->first.hash.ToString(), range.first->first.n); MarkConflicted(pIndex->GetBlockHash(), range.first->second); } range.first++; @@ -1078,11 +1057,11 @@ bool CWallet::AddToWalletIfInvolvingMe(const CTransactionRef& ptx, const CBlockI 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__); + WalletLogPrintf("%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__); + WalletLogPrintf("%s: Topping up keypool failed (locked wallet)\n", __func__); } } } @@ -1107,6 +1086,16 @@ bool CWallet::TransactionCanBeAbandoned(const uint256& hashTx) const return wtx && !wtx->isAbandoned() && wtx->GetDepthInMainChain() == 0 && !wtx->InMempool(); } +void CWallet::MarkInputsDirty(const CTransactionRef& tx) +{ + for (const CTxIn& txin : tx->vin) { + auto it = mapWallet.find(txin.prevout.hash); + if (it != mapWallet.end()) { + it->second.MarkDirty(); + } + } +} + bool CWallet::AbandonTransaction(const uint256& hashTx) { LOCK2(cs_main, cs_wallet); @@ -1146,7 +1135,7 @@ bool CWallet::AbandonTransaction(const uint256& hashTx) batch.WriteTx(wtx); NotifyTransactionChanged(this, wtx.GetHash(), CT_UPDATED); // Iterate over all its outputs, and mark transactions in the wallet that spend them abandoned too - TxSpends::const_iterator iter = mapTxSpends.lower_bound(COutPoint(hashTx, 0)); + TxSpends::const_iterator iter = mapTxSpends.lower_bound(COutPoint(now, 0)); while (iter != mapTxSpends.end() && iter->first.hash == now) { if (!done.count(iter->second)) { todo.insert(iter->second); @@ -1155,13 +1144,7 @@ bool CWallet::AbandonTransaction(const uint256& hashTx) } // If a transaction changes 'conflicted' state, that changes the balance // available of the outputs it spends. So force those to be recomputed - for (const CTxIn& txin : wtx.tx->vin) - { - auto it = mapWallet.find(txin.prevout.hash); - if (it != mapWallet.end()) { - it->second.MarkDirty(); - } - } + MarkInputsDirty(wtx.tx); } } @@ -1217,31 +1200,19 @@ void CWallet::MarkConflicted(const uint256& hashBlock, const uint256& hashTx) } // If a transaction changes 'conflicted' state, that changes the balance // available of the outputs it spends. So force those to be recomputed - for (const CTxIn& txin : wtx.tx->vin) { - auto it = mapWallet.find(txin.prevout.hash); - if (it != mapWallet.end()) { - it->second.MarkDirty(); - } - } + MarkInputsDirty(wtx.tx); } } } -void CWallet::SyncTransaction(const CTransactionRef& ptx, const CBlockIndex *pindex, int posInBlock) { - const CTransaction& tx = *ptx; - - if (!AddToWalletIfInvolvingMe(ptx, pindex, posInBlock, true)) +void CWallet::SyncTransaction(const CTransactionRef& ptx, const CBlockIndex *pindex, int posInBlock, bool update_tx) { + if (!AddToWalletIfInvolvingMe(ptx, pindex, posInBlock, update_tx)) return; // Not one of ours // If a transaction changes 'conflicted' state, that changes the balance // available of the outputs it spends. So force those to be // recomputed, also: - for (const CTxIn& txin : tx.vin) { - auto it = mapWallet.find(txin.prevout.hash); - if (it != mapWallet.end()) { - it->second.MarkDirty(); - } - } + MarkInputsDirty(ptx); } void CWallet::TransactionAddedToMempool(const CTransactionRef& ptx) { @@ -1465,6 +1436,7 @@ CAmount CWallet::GetChange(const CTransaction& tx) const CPubKey CWallet::GenerateNewSeed() { + assert(!IsWalletFlagSet(WALLET_FLAG_DISABLE_PRIVATE_KEYS)); CKey key; key.MakeNewKey(true); return DeriveNewSeed(key); @@ -1497,7 +1469,7 @@ CPubKey CWallet::DeriveNewSeed(const CKey& key) return seed; } -bool CWallet::SetHDSeed(const CPubKey& seed) +void CWallet::SetHDSeed(const CPubKey& seed) { LOCK(cs_wallet); // store the keyid (hash160) together with @@ -1507,18 +1479,15 @@ bool CWallet::SetHDSeed(const CPubKey& seed) newHdChain.nVersion = CanSupportFeature(FEATURE_HD_SPLIT) ? CHDChain::VERSION_HD_CHAIN_SPLIT : CHDChain::VERSION_HD_BASE; newHdChain.seed_id = seed.GetID(); SetHDChain(newHdChain, false); - - return true; } -bool CWallet::SetHDChain(const CHDChain& chain, bool memonly) +void CWallet::SetHDChain(const CHDChain& chain, bool memonly) { LOCK(cs_wallet); if (!memonly && !WalletBatch(*database).WriteHDChain(chain)) throw std::runtime_error(std::string(__func__) + ": writing chain failed"); hdChain = chain; - return true; } bool CWallet::IsHDEnabled() const @@ -1526,6 +1495,34 @@ bool CWallet::IsHDEnabled() const return !hdChain.seed_id.IsNull(); } +void CWallet::SetWalletFlag(uint64_t flags) +{ + LOCK(cs_wallet); + m_wallet_flags |= flags; + if (!WalletBatch(*database).WriteWalletFlags(m_wallet_flags)) + throw std::runtime_error(std::string(__func__) + ": writing wallet flags failed"); +} + +bool CWallet::IsWalletFlagSet(uint64_t flag) +{ + return (m_wallet_flags & flag); +} + +bool CWallet::SetWalletFlags(uint64_t overwriteFlags, bool memonly) +{ + LOCK(cs_wallet); + m_wallet_flags = overwriteFlags; + if (((overwriteFlags & g_known_wallet_flags) >> 32) ^ (overwriteFlags >> 32)) { + // contains unknown non-tolerable wallet flags + return false; + } + if (!memonly && !WalletBatch(*database).WriteWalletFlags(m_wallet_flags)) { + throw std::runtime_error(std::string(__func__) + ": writing wallet flags failed"); + } + + return true; +} + int64_t CWalletTx::GetTxTime() const { int64_t n = nTimeSmart; @@ -1643,8 +1640,8 @@ void CWalletTx::GetAmounts(std::list<COutputEntry>& listReceived, if (!ExtractDestination(txout.scriptPubKey, address) && !txout.scriptPubKey.IsUnspendable()) { - LogPrintf("CWalletTx::GetAmounts: Unknown transaction type found, txid %s\n", - this->GetHash().ToString()); + pwallet->WalletLogPrintf("CWalletTx::GetAmounts: Unknown transaction type found, txid %s\n", + this->GetHash().ToString()); address = CNoDestination(); } @@ -1678,7 +1675,7 @@ int64_t CWallet::RescanFromTime(int64_t startTime, const WalletRescanReserver& r { LOCK(cs_main); startBlock = chainActive.FindEarliestAtLeast(startTime - TIMESTAMP_WINDOW); - LogPrintf("%s: Rescanning last %i blocks\n", __func__, startBlock ? chainActive.Height() - startBlock->nHeight + 1 : 0); + WalletLogPrintf("%s: Rescanning last %i blocks\n", __func__, startBlock ? chainActive.Height() - startBlock->nHeight + 1 : 0); } if (startBlock) { @@ -1719,11 +1716,11 @@ CBlockIndex* CWallet::ScanForWalletTransactions(CBlockIndex* pindexStart, CBlock CBlockIndex* pindex = pindexStart; CBlockIndex* ret = nullptr; - if (pindex) LogPrintf("Rescan started from block %d...\n", pindex->nHeight); + if (pindex) WalletLogPrintf("Rescan started from block %d...\n", pindex->nHeight); { fAbortRescan = false; - ShowProgress(_("Rescanning..."), 0); // show rescan progress in GUI as dialog or on splashscreen, if -rescan on startup + ShowProgress(strprintf("%s " + _("Rescanning..."), GetDisplayName()), 0); // show rescan progress in GUI as dialog or on splashscreen, if -rescan on startup CBlockIndex* tip = nullptr; double progress_begin; double progress_end; @@ -1741,11 +1738,11 @@ CBlockIndex* CWallet::ScanForWalletTransactions(CBlockIndex* pindexStart, CBlock while (pindex && !fAbortRescan && !ShutdownRequested()) { if (pindex->nHeight % 100 == 0 && progress_end - progress_begin > 0.0) { - ShowProgress(_("Rescanning..."), std::max(1, std::min(99, (int)((progress_current - progress_begin) / (progress_end - progress_begin) * 100)))); + ShowProgress(strprintf("%s " + _("Rescanning..."), GetDisplayName()), std::max(1, std::min(99, (int)((progress_current - progress_begin) / (progress_end - progress_begin) * 100)))); } if (GetTime() >= nNow + 60) { nNow = GetTime(); - LogPrintf("Still rescanning. At block %d. Progress=%f\n", pindex->nHeight, progress_current); + WalletLogPrintf("Still rescanning. At block %d. Progress=%f\n", pindex->nHeight, progress_current); } CBlock block; @@ -1758,7 +1755,7 @@ CBlockIndex* CWallet::ScanForWalletTransactions(CBlockIndex* pindexStart, CBlock break; } for (size_t posInBlock = 0; posInBlock < block.vtx.size(); ++posInBlock) { - AddToWalletIfInvolvingMe(block.vtx[posInBlock], pindex, posInBlock, fUpdate); + SyncTransaction(block.vtx[posInBlock], pindex, posInBlock, fUpdate); } } else { ret = pindex; @@ -1778,11 +1775,11 @@ CBlockIndex* CWallet::ScanForWalletTransactions(CBlockIndex* pindexStart, CBlock } } if (pindex && fAbortRescan) { - LogPrintf("Rescan aborted at block %d. Progress=%f\n", pindex->nHeight, progress_current); + WalletLogPrintf("Rescan aborted at block %d. Progress=%f\n", pindex->nHeight, progress_current); } else if (pindex && ShutdownRequested()) { - LogPrintf("Rescan interrupted by shutdown request at block %d. Progress=%f\n", pindex->nHeight, progress_current); + WalletLogPrintf("Rescan interrupted by shutdown request at block %d. Progress=%f\n", pindex->nHeight, progress_current); } - ShowProgress(_("Rescanning..."), 100); // hide progress dialog in GUI + ShowProgress(strprintf("%s " + _("Rescanning..."), GetDisplayName()), 100); // hide progress dialog in GUI } return ret; } @@ -1825,7 +1822,7 @@ bool CWalletTx::RelayWalletTransaction(CConnman* connman) CValidationState state; /* GetDepthInMainChain already catches known conflicts. */ if (InMempool() || AcceptToMemoryPool(maxTxFee, state)) { - LogPrintf("Relaying wtx %s\n", GetHash().ToString()); + pwallet->WalletLogPrintf("Relaying wtx %s\n", GetHash().ToString()); if (connman) { CInv inv(MSG_TX, GetHash()); connman->ForEachNode([&inv](CNode* pnode) @@ -1929,7 +1926,7 @@ CAmount CWalletTx::GetImmatureCredit(bool fUseCache) const return 0; } -CAmount CWalletTx::GetAvailableCredit(bool fUseCache) const +CAmount CWalletTx::GetAvailableCredit(bool fUseCache, const isminefilter& filter) const { if (pwallet == nullptr) return 0; @@ -1938,8 +1935,20 @@ CAmount CWalletTx::GetAvailableCredit(bool fUseCache) const if (IsCoinBase() && GetBlocksToMaturity() > 0) return 0; - if (fUseCache && fAvailableCreditCached) - return nAvailableCreditCached; + CAmount* cache = nullptr; + bool* cache_used = nullptr; + + if (filter == ISMINE_SPENDABLE) { + cache = &nAvailableCreditCached; + cache_used = &fAvailableCreditCached; + } else if (filter == ISMINE_WATCH_ONLY) { + cache = &nAvailableWatchCreditCached; + cache_used = &fAvailableWatchCreditCached; + } + + if (fUseCache && cache_used && *cache_used) { + return *cache; + } CAmount nCredit = 0; uint256 hashTx = GetHash(); @@ -1948,14 +1957,17 @@ CAmount CWalletTx::GetAvailableCredit(bool fUseCache) const if (!pwallet->IsSpent(hashTx, i)) { const CTxOut &txout = tx->vout[i]; - nCredit += pwallet->GetCredit(txout, ISMINE_SPENDABLE); + nCredit += pwallet->GetCredit(txout, filter); if (!MoneyRange(nCredit)) throw std::runtime_error(std::string(__func__) + " : value out of range"); } } - nAvailableCreditCached = nCredit; - fAvailableCreditCached = true; + if (cache) { + *cache = nCredit; + assert(cache_used); + *cache_used = true; + } return nCredit; } @@ -1973,35 +1985,6 @@ CAmount CWalletTx::GetImmatureWatchOnlyCredit(const bool fUseCache) const return 0; } -CAmount CWalletTx::GetAvailableWatchOnlyCredit(const bool fUseCache) const -{ - if (pwallet == nullptr) - return 0; - - // Must wait until coinbase is safely deep enough in the chain before valuing it - if (IsCoinBase() && GetBlocksToMaturity() > 0) - return 0; - - if (fUseCache && fAvailableWatchCreditCached) - return nAvailableWatchCreditCached; - - CAmount nCredit = 0; - for (unsigned int i = 0; i < tx->vout.size(); i++) - { - if (!pwallet->IsSpent(GetHash(), i)) - { - const CTxOut &txout = tx->vout[i]; - nCredit += pwallet->GetCredit(txout, ISMINE_WATCH_ONLY); - if (!MoneyRange(nCredit)) - throw std::runtime_error(std::string(__func__) + ": value out of range"); - } - } - - nAvailableWatchCreditCached = nCredit; - fAvailableWatchCreditCached = true; - return nCredit; -} - CAmount CWalletTx::GetChange() const { if (fChangeCached) @@ -2101,7 +2084,7 @@ void CWallet::ResendWalletTransactions(int64_t nBestBlockTime, CConnman* connman // block was found: std::vector<uint256> relayed = ResendWalletTransactionsBefore(nBestBlockTime-5*60, connman); if (!relayed.empty()) - LogPrintf("%s: rebroadcast %u unconfirmed transactions\n", __func__, relayed.size()); + WalletLogPrintf("%s: rebroadcast %u unconfirmed transactions\n", __func__, relayed.size()); } /** @} */ // end of mapWallet @@ -2115,7 +2098,7 @@ void CWallet::ResendWalletTransactions(int64_t nBestBlockTime, CConnman* connman */ -CAmount CWallet::GetBalance() const +CAmount CWallet::GetBalance(const isminefilter& filter, const int min_depth) const { CAmount nTotal = 0; { @@ -2123,8 +2106,9 @@ CAmount CWallet::GetBalance() const for (const auto& entry : mapWallet) { const CWalletTx* pcoin = &entry.second; - if (pcoin->IsTrusted()) - nTotal += pcoin->GetAvailableCredit(); + if (pcoin->IsTrusted() && pcoin->GetDepthInMainChain() >= min_depth) { + nTotal += pcoin->GetAvailableCredit(true, filter); + } } } @@ -2160,22 +2144,6 @@ CAmount CWallet::GetImmatureBalance() const return nTotal; } -CAmount CWallet::GetWatchOnlyBalance() const -{ - CAmount nTotal = 0; - { - LOCK2(cs_main, cs_wallet); - for (const auto& entry : mapWallet) - { - const CWalletTx* pcoin = &entry.second; - if (pcoin->IsTrusted()) - nTotal += pcoin->GetAvailableWatchOnlyCredit(); - } - } - - return nTotal; -} - CAmount CWallet::GetUnconfirmedWatchOnlyBalance() const { CAmount nTotal = 0; @@ -2185,7 +2153,7 @@ CAmount CWallet::GetUnconfirmedWatchOnlyBalance() const { const CWalletTx* pcoin = &entry.second; if (!pcoin->IsTrusted() && pcoin->GetDepthInMainChain() == 0 && pcoin->InMempool()) - nTotal += pcoin->GetAvailableWatchOnlyCredit(); + nTotal += pcoin->GetAvailableCredit(true, ISMINE_WATCH_ONLY); } } return nTotal; @@ -2435,32 +2403,14 @@ const CTxOut& CWallet::FindNonChangeParentOutput(const CTransaction& tx, int out return ptx->vout[n]; } -bool CWallet::OutputEligibleForSpending(const COutput& output, const CoinEligibilityFilter& eligibility_filter) const -{ - if (!output.fSpendable) - return false; - - if (output.nDepth < (output.tx->IsFromMe(ISMINE_ALL) ? eligibility_filter.conf_mine : eligibility_filter.conf_theirs)) - return false; - - size_t ancestors, descendants; - mempool.GetTransactionAncestry(output.tx->GetHash(), ancestors, descendants); - if (ancestors > eligibility_filter.max_ancestors || descendants > eligibility_filter.max_descendants) { - return false; - } - - return true; -} - -bool CWallet::SelectCoinsMinConf(const CAmount& nTargetValue, const CoinEligibilityFilter& eligibility_filter, std::vector<COutput> vCoins, +bool CWallet::SelectCoinsMinConf(const CAmount& nTargetValue, const CoinEligibilityFilter& eligibility_filter, std::vector<OutputGroup> groups, std::set<CInputCoin>& setCoinsRet, CAmount& nValueRet, const CoinSelectionParams& coin_selection_params, bool& bnb_used) const { setCoinsRet.clear(); nValueRet = 0; - std::vector<CInputCoin> utxo_pool; + std::vector<OutputGroup> utxo_pool; if (coin_selection_params.use_bnb) { - // Get long term estimate FeeCalculation feeCalc; CCoinControl temp; @@ -2471,19 +2421,26 @@ bool CWallet::SelectCoinsMinConf(const CAmount& nTargetValue, const CoinEligibil CAmount cost_of_change = GetDiscardRate(*this, ::feeEstimator).GetFee(coin_selection_params.change_spend_size) + coin_selection_params.effective_fee.GetFee(coin_selection_params.change_output_size); // Filter by the min conf specs and add to utxo_pool and calculate effective value - for (const COutput &output : vCoins) - { - if (!OutputEligibleForSpending(output, eligibility_filter)) - continue; - - CInputCoin coin(output.tx->tx, output.i); - coin.effective_value = coin.txout.nValue - (output.nInputBytes < 0 ? 0 : coin_selection_params.effective_fee.GetFee(output.nInputBytes)); - // Only include outputs that are positive effective value (i.e. not dust) - if (coin.effective_value > 0) { - coin.fee = output.nInputBytes < 0 ? 0 : coin_selection_params.effective_fee.GetFee(output.nInputBytes); - coin.long_term_fee = output.nInputBytes < 0 ? 0 : long_term_feerate.GetFee(output.nInputBytes); - utxo_pool.push_back(coin); + for (OutputGroup& group : groups) { + if (!group.EligibleForSpending(eligibility_filter)) continue; + + group.fee = 0; + group.long_term_fee = 0; + group.effective_value = 0; + for (auto it = group.m_outputs.begin(); it != group.m_outputs.end(); ) { + const CInputCoin& coin = *it; + CAmount effective_value = coin.txout.nValue - (coin.m_input_bytes < 0 ? 0 : coin_selection_params.effective_fee.GetFee(coin.m_input_bytes)); + // Only include outputs that are positive effective value (i.e. not dust) + if (effective_value > 0) { + group.fee += coin.m_input_bytes < 0 ? 0 : coin_selection_params.effective_fee.GetFee(coin.m_input_bytes); + group.long_term_fee += coin.m_input_bytes < 0 ? 0 : long_term_feerate.GetFee(coin.m_input_bytes); + group.effective_value += effective_value; + ++it; + } else { + it = group.Discard(coin); + } } + if (group.effective_value > 0) utxo_pool.push_back(group); } // Calculate the fees for things that aren't inputs CAmount not_input_fees = coin_selection_params.effective_fee.GetFee(coin_selection_params.tx_noinputs_size); @@ -2491,13 +2448,9 @@ bool CWallet::SelectCoinsMinConf(const CAmount& nTargetValue, const CoinEligibil return SelectCoinsBnB(utxo_pool, nTargetValue, cost_of_change, setCoinsRet, nValueRet, not_input_fees); } else { // Filter by the min conf specs and add to utxo_pool - for (const COutput &output : vCoins) - { - if (!OutputEligibleForSpending(output, eligibility_filter)) - continue; - - CInputCoin coin = CInputCoin(output.tx->tx, output.i); - utxo_pool.push_back(coin); + for (const OutputGroup& group : groups) { + if (!group.EligibleForSpending(eligibility_filter)) continue; + utxo_pool.push_back(group); } bnb_used = false; return KnapsackSolver(nTargetValue, utxo_pool, setCoinsRet, nValueRet); @@ -2519,7 +2472,7 @@ bool CWallet::SelectCoins(const std::vector<COutput>& vAvailableCoins, const CAm if (!out.fSpendable) continue; nValueRet += out.tx->tx->vout[out.i].nValue; - setCoinsRet.insert(CInputCoin(out.tx->tx, out.i)); + setCoinsRet.insert(out.GetInputCoin()); } return (nValueRet >= nTargetValue); } @@ -2553,27 +2506,31 @@ bool CWallet::SelectCoins(const std::vector<COutput>& vAvailableCoins, const CAm // remove preset inputs from vCoins for (std::vector<COutput>::iterator it = vCoins.begin(); it != vCoins.end() && coin_control.HasSelected();) { - if (setPresetCoins.count(CInputCoin(it->tx->tx, it->i))) + if (setPresetCoins.count(it->GetInputCoin())) it = vCoins.erase(it); else ++it; } + // form groups from remaining coins; note that preset coins will not + // automatically have their associated (same address) coins included + std::vector<OutputGroup> groups = GroupOutputs(vCoins, !coin_control.m_avoid_partial_spends); + size_t max_ancestors = (size_t)std::max<int64_t>(1, gArgs.GetArg("-limitancestorcount", DEFAULT_ANCESTOR_LIMIT)); size_t max_descendants = (size_t)std::max<int64_t>(1, gArgs.GetArg("-limitdescendantcount", DEFAULT_DESCENDANT_LIMIT)); bool fRejectLongChains = gArgs.GetBoolArg("-walletrejectlongchains", DEFAULT_WALLET_REJECT_LONG_CHAINS); bool res = nTargetValue <= nValueFromPresetInputs || - SelectCoinsMinConf(nTargetValue - nValueFromPresetInputs, CoinEligibilityFilter(1, 6, 0), vCoins, setCoinsRet, nValueRet, coin_selection_params, bnb_used) || - SelectCoinsMinConf(nTargetValue - nValueFromPresetInputs, CoinEligibilityFilter(1, 1, 0), vCoins, setCoinsRet, nValueRet, coin_selection_params, bnb_used) || - (m_spend_zero_conf_change && SelectCoinsMinConf(nTargetValue - nValueFromPresetInputs, CoinEligibilityFilter(0, 1, 2), vCoins, setCoinsRet, nValueRet, coin_selection_params, bnb_used)) || - (m_spend_zero_conf_change && SelectCoinsMinConf(nTargetValue - nValueFromPresetInputs, CoinEligibilityFilter(0, 1, std::min((size_t)4, max_ancestors/3), std::min((size_t)4, max_descendants/3)), vCoins, setCoinsRet, nValueRet, coin_selection_params, bnb_used)) || - (m_spend_zero_conf_change && SelectCoinsMinConf(nTargetValue - nValueFromPresetInputs, CoinEligibilityFilter(0, 1, max_ancestors/2, max_descendants/2), vCoins, setCoinsRet, nValueRet, coin_selection_params, bnb_used)) || - (m_spend_zero_conf_change && SelectCoinsMinConf(nTargetValue - nValueFromPresetInputs, CoinEligibilityFilter(0, 1, max_ancestors-1, max_descendants-1), vCoins, setCoinsRet, nValueRet, coin_selection_params, bnb_used)) || - (m_spend_zero_conf_change && !fRejectLongChains && SelectCoinsMinConf(nTargetValue - nValueFromPresetInputs, CoinEligibilityFilter(0, 1, std::numeric_limits<uint64_t>::max()), vCoins, setCoinsRet, nValueRet, coin_selection_params, bnb_used)); + SelectCoinsMinConf(nTargetValue - nValueFromPresetInputs, CoinEligibilityFilter(1, 6, 0), groups, setCoinsRet, nValueRet, coin_selection_params, bnb_used) || + SelectCoinsMinConf(nTargetValue - nValueFromPresetInputs, CoinEligibilityFilter(1, 1, 0), groups, setCoinsRet, nValueRet, coin_selection_params, bnb_used) || + (m_spend_zero_conf_change && SelectCoinsMinConf(nTargetValue - nValueFromPresetInputs, CoinEligibilityFilter(0, 1, 2), groups, setCoinsRet, nValueRet, coin_selection_params, bnb_used)) || + (m_spend_zero_conf_change && SelectCoinsMinConf(nTargetValue - nValueFromPresetInputs, CoinEligibilityFilter(0, 1, std::min((size_t)4, max_ancestors/3), std::min((size_t)4, max_descendants/3)), groups, setCoinsRet, nValueRet, coin_selection_params, bnb_used)) || + (m_spend_zero_conf_change && SelectCoinsMinConf(nTargetValue - nValueFromPresetInputs, CoinEligibilityFilter(0, 1, max_ancestors/2, max_descendants/2), groups, setCoinsRet, nValueRet, coin_selection_params, bnb_used)) || + (m_spend_zero_conf_change && SelectCoinsMinConf(nTargetValue - nValueFromPresetInputs, CoinEligibilityFilter(0, 1, max_ancestors-1, max_descendants-1), groups, setCoinsRet, nValueRet, coin_selection_params, bnb_used)) || + (m_spend_zero_conf_change && !fRejectLongChains && SelectCoinsMinConf(nTargetValue - nValueFromPresetInputs, CoinEligibilityFilter(0, 1, std::numeric_limits<uint64_t>::max()), groups, setCoinsRet, nValueRet, coin_selection_params, bnb_used)); // because SelectCoinsMinConf clears the setCoinsRet, we now add the possible inputs to the coinset - setCoinsRet.insert(setPresetCoins.begin(), setPresetCoins.end()); + util::insert(setCoinsRet, setPresetCoins); // add preset inputs to the total value selected nValueRet += nValueFromPresetInputs; @@ -2687,7 +2644,7 @@ OutputType CWallet::TransactionChangeType(OutputType change_type, const std::vec } bool CWallet::CreateTransaction(const std::vector<CRecipient>& vecSend, CTransactionRef& tx, CReserveKey& reservekey, CAmount& nFeeRet, - int& nChangePosInOut, std::string& strFailReason, const CCoinControl& coin_control, bool sign) + int& nChangePosInOut, std::string& strFailReason, const CCoinControl& coin_control, bool sign) { CAmount nValue = 0; int nChangePosRequest = nChangePosInOut; @@ -2771,6 +2728,10 @@ bool CWallet::CreateTransaction(const std::vector<CRecipient>& vecSend, CTransac // post-backup change. // Reserve a new key pair from key pool + if (IsWalletFlagSet(WALLET_FLAG_DISABLE_PRIVATE_KEYS)) { + strFailReason = _("Can't generate a change-address key. Private keys are disabled for this wallet."); + return false; + } CPubKey vchPubKey; bool ret; ret = reservekey.GetReservedKey(vchPubKey, true); @@ -3056,13 +3017,14 @@ bool CWallet::CreateTransaction(const std::vector<CRecipient>& vecSend, CTransac size_t nLimitDescendants = gArgs.GetArg("-limitdescendantcount", DEFAULT_DESCENDANT_LIMIT); size_t nLimitDescendantSize = gArgs.GetArg("-limitdescendantsize", DEFAULT_DESCENDANT_SIZE_LIMIT)*1000; std::string errString; - if (!mempool.CalculateMemPoolAncestors(entry, setAncestors, nLimitAncestors, nLimitAncestorSize, nLimitDescendants, nLimitDescendantSize, errString)) { + LOCK(::mempool.cs); + if (!::mempool.CalculateMemPoolAncestors(entry, setAncestors, nLimitAncestors, nLimitAncestorSize, nLimitDescendants, nLimitDescendantSize, errString)) { strFailReason = _("Transaction has too long of a mempool chain"); return false; } } - LogPrintf("Fee Calculation: Fee:%d Bytes:%u Needed:%d Tgt:%d (requested %d) Reason:\"%s\" Decay %.5f: Estimation: (%g - %g) %.2f%% %.1f/(%.1f %d mem %.1f out) Fail: (%g - %g) %.2f%% %.1f/(%.1f %d mem %.1f out)\n", + WalletLogPrintf("Fee Calculation: Fee:%d Bytes:%u Needed:%d Tgt:%d (requested %d) Reason:\"%s\" Decay %.5f: Estimation: (%g - %g) %.2f%% %.1f/(%.1f %d mem %.1f out) Fail: (%g - %g) %.2f%% %.1f/(%.1f %d mem %.1f out)\n", nFeeRet, nBytes, nFeeNeeded, feeCalc.returnedTarget, feeCalc.desiredTarget, StringForFeeReason(feeCalc.reason), feeCalc.est.decay, feeCalc.est.pass.start, feeCalc.est.pass.end, 100 * feeCalc.est.pass.withinTarget / (feeCalc.est.pass.totalConfirmed + feeCalc.est.pass.inMempool + feeCalc.est.pass.leftMempool), @@ -3088,7 +3050,7 @@ bool CWallet::CommitTransaction(CTransactionRef tx, mapValue_t mapValue, std::ve wtxNew.fTimeReceivedIsTxTime = true; wtxNew.fFromMe = true; - LogPrintf("CommitTransaction:\n%s", wtxNew.tx->ToString()); /* Continued */ + WalletLogPrintf("CommitTransaction:\n%s", wtxNew.tx->ToString()); /* Continued */ { // Take key pair from key pool so it won't be used again reservekey.KeepKey(); @@ -3114,7 +3076,7 @@ bool CWallet::CommitTransaction(CTransactionRef tx, mapValue_t mapValue, std::ve { // Broadcast if (!wtx.AcceptToMemoryPool(maxTxFee, state)) { - LogPrintf("CommitTransaction(): Transaction cannot be broadcast immediately, %s\n", FormatStateMessage(state)); + WalletLogPrintf("CommitTransaction(): Transaction cannot be broadcast immediately, %s\n", FormatStateMessage(state)); // TODO: if we expect the failure to be long term or permanent, instead delete wtx from the wallet and return failure. } else { wtx.RelayWalletTransaction(connman); @@ -3171,7 +3133,7 @@ DBErrors CWallet::LoadWallet(bool& fFirstRunRet) { LOCK(cs_KeyStore); // This wallet is in its first run if all of these are empty - fFirstRunRet = mapKeys.empty() && mapCryptedKeys.empty() && mapWatchKeys.empty() && setWatchOnly.empty() && mapScripts.empty(); + fFirstRunRet = mapKeys.empty() && mapCryptedKeys.empty() && mapWatchKeys.empty() && setWatchOnly.empty() && mapScripts.empty() && !IsWalletFlagSet(WALLET_FLAG_DISABLE_PRIVATE_KEYS); } if (nLoadWalletRet != DBErrors::LOAD_OK) @@ -3295,6 +3257,9 @@ const std::string& CWallet::GetLabelName(const CScript& scriptPubKey) const */ bool CWallet::NewKeyPool() { + if (IsWalletFlagSet(WALLET_FLAG_DISABLE_PRIVATE_KEYS)) { + return false; + } { LOCK(cs_wallet); WalletBatch batch(*database); @@ -3319,7 +3284,7 @@ bool CWallet::NewKeyPool() if (!TopUpKeyPool()) { return false; } - LogPrintf("CWallet::NewKeyPool rewrote keypool\n"); + WalletLogPrintf("CWallet::NewKeyPool rewrote keypool\n"); } return true; } @@ -3353,6 +3318,9 @@ void CWallet::LoadKeyPool(int64_t nIndex, const CKeyPool &keypool) bool CWallet::TopUpKeyPool(unsigned int kpSize) { + if (IsWalletFlagSet(WALLET_FLAG_DISABLE_PRIVATE_KEYS)) { + return false; + } { LOCK(cs_wallet); @@ -3400,7 +3368,7 @@ bool CWallet::TopUpKeyPool(unsigned int kpSize) 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() + set_pre_split_keypool.size(), setInternalKeyPool.size()); + 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()); } } return true; @@ -3445,7 +3413,7 @@ bool CWallet::ReserveKeyFromKeyPool(int64_t& nIndex, CKeyPool& keypool, bool fRe } m_pool_key_to_index.erase(keypool.vchPubKey.GetID()); - LogPrintf("keypool reserve %d\n", nIndex); + WalletLogPrintf("keypool reserve %d\n", nIndex); } return true; } @@ -3455,7 +3423,7 @@ void CWallet::KeepKey(int64_t nIndex) // Remove from key pool WalletBatch batch(*database); batch.ErasePool(nIndex); - LogPrintf("keypool keep %d\n", nIndex); + WalletLogPrintf("keypool keep %d\n", nIndex); } void CWallet::ReturnKey(int64_t nIndex, bool fInternal, const CPubKey& pubkey) @@ -3472,11 +3440,15 @@ void CWallet::ReturnKey(int64_t nIndex, bool fInternal, const CPubKey& pubkey) } m_pool_key_to_index[pubkey.GetID()] = nIndex; } - LogPrintf("keypool return %d\n", nIndex); + WalletLogPrintf("keypool return %d\n", nIndex); } bool CWallet::GetKeyFromPool(CPubKey& result, bool internal) { + if (IsWalletFlagSet(WALLET_FLAG_DISABLE_PRIVATE_KEYS)) { + return false; + } + CKeyPool keypool; { LOCK(cs_wallet); @@ -3730,7 +3702,7 @@ void CWallet::MarkReserveKeysAsUsed(int64_t keypool_id) } LearnAllRelatedScripts(keypool.vchPubKey); batch.ErasePool(index); - LogPrintf("keypool index %d removed\n", index); + WalletLogPrintf("keypool index %d removed\n", index); it = setKeyPool->erase(it); } } @@ -3894,7 +3866,7 @@ unsigned int CWallet::ComputeTimeSmart(const CWalletTx& wtx) const int64_t blocktime = pindex->GetBlockTime(); nTimeSmart = std::max(latestEntry, std::min(blocktime, latestNow)); } else { - LogPrintf("%s: found %s in block %s not in index\n", __func__, wtx.GetHash().ToString(), wtx.hashBlock.ToString()); + WalletLogPrintf("%s: found %s in block %s not in index\n", __func__, wtx.GetHash().ToString(), wtx.hashBlock.ToString()); } } return nTimeSmart; @@ -3916,10 +3888,9 @@ bool CWallet::EraseDestData(const CTxDestination &dest, const std::string &key) return WalletBatch(*database).EraseDestData(EncodeDestination(dest), key); } -bool CWallet::LoadDestData(const CTxDestination &dest, const std::string &key, const std::string &value) +void CWallet::LoadDestData(const CTxDestination &dest, const std::string &key, const std::string &value) { mapAddressBook[dest].destdata.insert(std::make_pair(key, value)); - return true; } bool CWallet::GetDestData(const CTxDestination &dest, const std::string &key, std::string *value) const @@ -4016,7 +3987,7 @@ bool CWallet::Verify(std::string wallet_file, bool salvage_wallet, std::string& return WalletBatch::VerifyDatabaseFile(wallet_path, warning_string, error_string); } -std::shared_ptr<CWallet> CWallet::CreateWalletFromFile(const std::string& name, const fs::path& path) +std::shared_ptr<CWallet> CWallet::CreateWalletFromFile(const std::string& name, const fs::path& path, uint64_t wallet_creation_flags) { const std::string& walletFile = name; @@ -4034,7 +4005,7 @@ std::shared_ptr<CWallet> CWallet::CreateWalletFromFile(const std::string& name, } } - uiInterface.InitMessage(_("Loading wallet...")); + uiInterface.InitMessage(strprintf(_("Loading wallet %s..."), walletFile)); int64_t nStart = GetTimeMillis(); bool fFirstRun = true; @@ -4075,12 +4046,12 @@ std::shared_ptr<CWallet> CWallet::CreateWalletFromFile(const std::string& name, int nMaxVersion = gArgs.GetArg("-upgradewallet", 0); if (nMaxVersion == 0) // the -upgradewallet without argument case { - LogPrintf("Performing wallet upgrade to %i\n", FEATURE_LATEST); - nMaxVersion = CLIENT_VERSION; + walletInstance->WalletLogPrintf("Performing wallet upgrade to %i\n", FEATURE_LATEST); + nMaxVersion = FEATURE_LATEST; walletInstance->SetMinVersion(FEATURE_LATEST); // permanently upgrade the wallet immediately } else - LogPrintf("Allowing wallet upgrade up to %i\n", nMaxVersion); + walletInstance->WalletLogPrintf("Allowing wallet upgrade up to %i\n", nMaxVersion); if (nMaxVersion < walletInstance->GetVersion()) { InitError(_("Cannot downgrade wallet")); @@ -4103,19 +4074,17 @@ std::shared_ptr<CWallet> CWallet::CreateWalletFromFile(const std::string& name, bool hd_upgrade = false; bool split_upgrade = false; if (walletInstance->CanSupportFeature(FEATURE_HD) && !walletInstance->IsHDEnabled()) { - LogPrintf("Upgrading wallet to HD\n"); + walletInstance->WalletLogPrintf("Upgrading wallet to HD\n"); walletInstance->SetMinVersion(FEATURE_HD); // generate a new master key CPubKey masterPubKey = walletInstance->GenerateNewSeed(); - if (!walletInstance->SetHDSeed(masterPubKey)) { - throw std::runtime_error(std::string(__func__) + ": Storing master key failed"); - } + walletInstance->SetHDSeed(masterPubKey); hd_upgrade = true; } // Upgrade to HD chain split if necessary if (walletInstance->CanSupportFeature(FEATURE_HD_SPLIT)) { - LogPrintf("Upgrading wallet to use HD chain split\n"); + walletInstance->WalletLogPrintf("Upgrading wallet to use HD chain split\n"); walletInstance->SetMinVersion(FEATURE_PRE_SPLIT_KEYPOOL); split_upgrade = FEATURE_HD_SPLIT > prev_version; } @@ -4126,7 +4095,7 @@ std::shared_ptr<CWallet> CWallet::CreateWalletFromFile(const std::string& name, // Regenerate the keypool if upgraded to HD if (hd_upgrade) { if (!walletInstance->TopUpKeyPool()) { - InitError(_("Unable to generate keys") += "\n"); + InitError(_("Unable to generate keys")); return nullptr; } } @@ -4141,18 +4110,31 @@ std::shared_ptr<CWallet> CWallet::CreateWalletFromFile(const std::string& name, } walletInstance->SetMinVersion(FEATURE_LATEST); - // generate a new seed - CPubKey seed = walletInstance->GenerateNewSeed(); - if (!walletInstance->SetHDSeed(seed)) - throw std::runtime_error(std::string(__func__) + ": Storing HD seed failed"); + if ((wallet_creation_flags & WALLET_FLAG_DISABLE_PRIVATE_KEYS)) { + //selective allow to set flags + walletInstance->SetWalletFlag(WALLET_FLAG_DISABLE_PRIVATE_KEYS); + } else { + // generate a new seed + CPubKey seed = walletInstance->GenerateNewSeed(); + walletInstance->SetHDSeed(seed); + } // Top up the keypool - if (!walletInstance->TopUpKeyPool()) { - InitError(_("Unable to generate initial keys") += "\n"); + if (!walletInstance->IsWalletFlagSet(WALLET_FLAG_DISABLE_PRIVATE_KEYS) && !walletInstance->TopUpKeyPool()) { + InitError(_("Unable to generate initial keys")); return nullptr; } walletInstance->ChainStateFlushed(chainActive.GetLocator()); + } else if (wallet_creation_flags & WALLET_FLAG_DISABLE_PRIVATE_KEYS) { + // Make it impossible to disable private keys after creation + InitError(strprintf(_("Error loading %s: Private keys can only be disabled during creation"), walletFile)); + return NULL; + } else if (walletInstance->IsWalletFlagSet(WALLET_FLAG_DISABLE_PRIVATE_KEYS)) { + LOCK(walletInstance->cs_KeyStore); + if (!walletInstance->mapKeys.empty() || !walletInstance->mapCryptedKeys.empty()) { + InitWarning(strprintf(_("Warning: Private keys detected in wallet {%s} with disabled private keys"), walletFile)); + } } else if (gArgs.IsArgSet("-usehd")) { bool useHD = gArgs.GetBoolArg("-usehd", true); if (walletInstance->IsHDEnabled() && !useHD) { @@ -4235,7 +4217,7 @@ std::shared_ptr<CWallet> CWallet::CreateWalletFromFile(const std::string& name, walletInstance->m_spend_zero_conf_change = gArgs.GetBoolArg("-spendzeroconfchange", DEFAULT_SPEND_ZEROCONF_CHANGE); walletInstance->m_signal_rbf = gArgs.GetBoolArg("-walletrbf", DEFAULT_WALLET_RBF); - LogPrintf(" wallet %15dms\n", GetTimeMillis() - nStart); + walletInstance->WalletLogPrintf("wallet %15dms\n", GetTimeMillis() - nStart); // Try to top up keypool. No-op if the wallet is locked. walletInstance->TopUpKeyPool(); @@ -4271,7 +4253,7 @@ std::shared_ptr<CWallet> CWallet::CreateWalletFromFile(const std::string& name, } uiInterface.InitMessage(_("Rescanning...")); - LogPrintf("Rescanning last %i blocks (from block %i)...\n", chainActive.Height() - pindexRescan->nHeight, pindexRescan->nHeight); + walletInstance->WalletLogPrintf("Rescanning last %i blocks (from block %i)...\n", chainActive.Height() - pindexRescan->nHeight, pindexRescan->nHeight); // No need to read and scan block if block was created before // our wallet birthday (as adjusted for block time variability) @@ -4288,7 +4270,7 @@ std::shared_ptr<CWallet> CWallet::CreateWalletFromFile(const std::string& name, } walletInstance->ScanForWalletTransactions(pindexRescan, nullptr, reserver, true); } - LogPrintf(" rescan %15dms\n", GetTimeMillis() - nStart); + walletInstance->WalletLogPrintf("rescan %15dms\n", GetTimeMillis() - nStart); walletInstance->ChainStateFlushed(chainActive.GetLocator()); walletInstance->database->IncrementUpdateCounter(); @@ -4327,9 +4309,9 @@ std::shared_ptr<CWallet> CWallet::CreateWalletFromFile(const std::string& name, { LOCK(walletInstance->cs_wallet); - LogPrintf("setKeyPool.size() = %u\n", walletInstance->GetKeyPoolSize()); - LogPrintf("mapWallet.size() = %u\n", walletInstance->mapWallet.size()); - LogPrintf("mapAddressBook.size() = %u\n", walletInstance->mapAddressBook.size()); + walletInstance->WalletLogPrintf("setKeyPool.size() = %u\n", walletInstance->GetKeyPoolSize()); + walletInstance->WalletLogPrintf("mapWallet.size() = %u\n", walletInstance->mapWallet.size()); + walletInstance->WalletLogPrintf("mapAddressBook.size() = %u\n", walletInstance->mapAddressBook.size()); } return walletInstance; @@ -4377,7 +4359,7 @@ void CMerkleTx::SetMerkleBranch(const CBlockIndex* pindex, int posInBlock) nIndex = posInBlock; } -int CMerkleTx::GetDepthInMainChain(const CBlockIndex* &pindexRet) const +int CMerkleTx::GetDepthInMainChain() const { if (hashUnset()) return 0; @@ -4389,7 +4371,6 @@ int CMerkleTx::GetDepthInMainChain(const CBlockIndex* &pindexRet) const if (!pindex || !chainActive.Contains(pindex)) return 0; - pindexRet = pindex; return ((nIndex == -1) ? (-1) : 1) * (chainActive.Height() - pindex->nHeight + 1); } @@ -4416,35 +4397,6 @@ bool CWalletTx::AcceptToMemoryPool(const CAmount& nAbsurdFee, CValidationState& return ret; } -static const std::string OUTPUT_TYPE_STRING_LEGACY = "legacy"; -static const std::string OUTPUT_TYPE_STRING_P2SH_SEGWIT = "p2sh-segwit"; -static const std::string OUTPUT_TYPE_STRING_BECH32 = "bech32"; - -bool ParseOutputType(const std::string& type, OutputType& output_type) -{ - if (type == OUTPUT_TYPE_STRING_LEGACY) { - output_type = OutputType::LEGACY; - return true; - } else if (type == OUTPUT_TYPE_STRING_P2SH_SEGWIT) { - output_type = OutputType::P2SH_SEGWIT; - return true; - } else if (type == OUTPUT_TYPE_STRING_BECH32) { - output_type = OutputType::BECH32; - return true; - } - return false; -} - -const std::string& FormatOutputType(OutputType type) -{ - switch (type) { - case OutputType::LEGACY: return OUTPUT_TYPE_STRING_LEGACY; - case OutputType::P2SH_SEGWIT: return OUTPUT_TYPE_STRING_P2SH_SEGWIT; - case OutputType::BECH32: return OUTPUT_TYPE_STRING_BECH32; - default: assert(false); - } -} - void CWallet::LearnRelatedScripts(const CPubKey& key, OutputType type) { if (key.IsCompressed() && (type == OutputType::P2SH_SEGWIT || type == OutputType::BECH32)) { @@ -4462,57 +4414,32 @@ void CWallet::LearnAllRelatedScripts(const CPubKey& key) LearnRelatedScripts(key, OutputType::P2SH_SEGWIT); } -CTxDestination GetDestinationForKey(const CPubKey& key, OutputType type) -{ - switch (type) { - case OutputType::LEGACY: return key.GetID(); - case OutputType::P2SH_SEGWIT: - case OutputType::BECH32: { - if (!key.IsCompressed()) return key.GetID(); - CTxDestination witdest = WitnessV0KeyHash(key.GetID()); - CScript witprog = GetScriptForDestination(witdest); - if (type == OutputType::P2SH_SEGWIT) { - return CScriptID(witprog); - } else { - return witdest; - } - } - default: assert(false); - } -} - -std::vector<CTxDestination> GetAllDestinationsForKey(const CPubKey& key) -{ - CKeyID keyid = key.GetID(); - if (key.IsCompressed()) { - CTxDestination segwit = WitnessV0KeyHash(keyid); - CTxDestination p2sh = CScriptID(GetScriptForDestination(segwit)); - return std::vector<CTxDestination>{std::move(keyid), std::move(p2sh), std::move(segwit)}; - } else { - return std::vector<CTxDestination>{std::move(keyid)}; - } -} - -CTxDestination CWallet::AddAndGetDestinationForScript(const CScript& script, OutputType type) -{ - // Note that scripts over 520 bytes are not yet supported. - switch (type) { - case OutputType::LEGACY: - return CScriptID(script); - case OutputType::P2SH_SEGWIT: - case OutputType::BECH32: { - CTxDestination witdest = WitnessV0ScriptHash(script); - CScript witprog = GetScriptForDestination(witdest); - // Check if the resulting program is solvable (i.e. doesn't use an uncompressed key) - if (!IsSolvable(*this, witprog)) return CScriptID(script); - // Add the redeemscript, so that P2WSH and P2SH-P2WSH outputs are recognized as ours. - AddCScript(witprog); - if (type == OutputType::BECH32) { - return witdest; - } else { - return CScriptID(witprog); +std::vector<OutputGroup> CWallet::GroupOutputs(const std::vector<COutput>& outputs, bool single_coin) const { + std::vector<OutputGroup> groups; + std::map<CTxDestination, OutputGroup> gmap; + CTxDestination dst; + for (const auto& output : outputs) { + if (output.fSpendable) { + CInputCoin input_coin = output.GetInputCoin(); + + size_t ancestors, descendants; + mempool.GetTransactionAncestry(output.tx->GetHash(), ancestors, descendants); + if (!single_coin && ExtractDestination(output.tx->tx->vout[output.i].scriptPubKey, dst)) { + // Limit output groups to no more than 10 entries, to protect + // against inadvertently creating a too-large transaction + // when using -avoidpartialspends + if (gmap[dst].m_outputs.size() >= 10) { + groups.push_back(gmap[dst]); + gmap.erase(dst); + } + gmap[dst].Insert(input_coin, output.nDepth, output.tx->IsFromMe(ISMINE_ALL), ancestors, descendants); + } else { + groups.emplace_back(input_coin, output.nDepth, output.tx->IsFromMe(ISMINE_ALL), ancestors, descendants); + } } } - default: assert(false); + if (!single_coin) { + for (const auto& it : gmap) groups.push_back(it.second); } + return groups; } |