aboutsummaryrefslogtreecommitdiff
path: root/src/wallet/wallet.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'src/wallet/wallet.cpp')
-rw-r--r--src/wallet/wallet.cpp1896
1 files changed, 221 insertions, 1675 deletions
diff --git a/src/wallet/wallet.cpp b/src/wallet/wallet.cpp
index 456c26ea31..9a61ca698d 100644
--- a/src/wallet/wallet.cpp
+++ b/src/wallet/wallet.cpp
@@ -8,6 +8,7 @@
#include <chain.h>
#include <consensus/consensus.h>
#include <consensus/validation.h>
+#include <external_signer.h>
#include <fs.h>
#include <interfaces/chain.h>
#include <interfaces/wallet.h>
@@ -53,8 +54,6 @@ const std::map<uint64_t,std::string> WALLET_FLAG_CAVEATS{
},
};
-static const size_t OUTPUT_GROUP_MAX_ENTRIES = 10;
-
RecursiveMutex cs_wallets;
static std::vector<std::shared_ptr<CWallet>> vpwallets GUARDED_BY(cs_wallets);
static std::list<LoadWalletFn> g_load_wallet_fns GUARDED_BY(cs_wallets);
@@ -213,7 +212,8 @@ std::shared_ptr<CWallet> LoadWalletInternal(interfaces::Chain& chain, const std:
return nullptr;
}
- std::shared_ptr<CWallet> wallet = CWallet::Create(chain, name, std::move(database), options.create_flags, error, warnings);
+ chain.initMessage(_("Loading wallet…").translated);
+ std::shared_ptr<CWallet> wallet = CWallet::Create(&chain, name, std::move(database), options.create_flags, error, warnings);
if (!wallet) {
error = Untranslated("Wallet loading failed.") + Untranslated(" ") + error;
status = DatabaseStatus::FAILED_LOAD;
@@ -292,7 +292,8 @@ std::shared_ptr<CWallet> CreateWallet(interfaces::Chain& chain, const std::strin
}
// Make the wallet
- std::shared_ptr<CWallet> wallet = CWallet::Create(chain, name, std::move(database), wallet_creation_flags, error, warnings);
+ chain.initMessage(_("Loading wallet…").translated);
+ std::shared_ptr<CWallet> wallet = CWallet::Create(&chain, name, std::move(database), wallet_creation_flags, error, warnings);
if (!wallet) {
error = Untranslated("Wallet creation failed.") + Untranslated(" ") + error;
status = DatabaseStatus::FAILED_CREATE;
@@ -349,11 +350,6 @@ std::shared_ptr<CWallet> CreateWallet(interfaces::Chain& chain, const std::strin
* @{
*/
-std::string COutput::ToString() const
-{
- return strprintf("COutput(%s, %d, %d) [%s]", tx->GetHash().ToString(), i, nDepth, FormatMoney(tx->tx->vout[i].nValue));
-}
-
const CWalletTx* CWallet::GetWalletTx(const uint256& hash) const
{
AssertLockHeld(cs_wallet);
@@ -378,6 +374,19 @@ void CWallet::UpgradeKeyMetadata()
SetWalletFlag(WALLET_FLAG_KEY_ORIGIN_METADATA);
}
+void CWallet::UpgradeDescriptorCache()
+{
+ if (!IsWalletFlagSet(WALLET_FLAG_DESCRIPTORS) || IsLocked() || IsWalletFlagSet(WALLET_FLAG_LAST_HARDENED_XPUB_CACHED)) {
+ return;
+ }
+
+ for (ScriptPubKeyMan* spkm : GetAllScriptPubKeyMans()) {
+ DescriptorScriptPubKeyMan* desc_spkm = dynamic_cast<DescriptorScriptPubKeyMan*>(spkm);
+ desc_spkm->UpgradeDescriptorCache();
+ }
+ SetWalletFlag(WALLET_FLAG_LAST_HARDENED_XPUB_CACHED);
+}
+
bool CWallet::Unlock(const SecureString& strWalletPassphrase, bool accept_no_keys)
{
CCrypter crypter;
@@ -394,6 +403,8 @@ bool CWallet::Unlock(const SecureString& strWalletPassphrase, bool accept_no_key
if (Unlock(_vMasterKey, accept_no_keys)) {
// Now that we've unlocked, upgrade the key metadata
UpgradeKeyMetadata();
+ // Now that we've unlocked, upgrade the descriptor cache
+ UpgradeDescriptorCache();
return true;
}
}
@@ -805,7 +816,7 @@ bool CWallet::MarkReplaced(const uint256& originalHash, const uint256& newHash)
success = false;
}
- NotifyTransactionChanged(this, originalHash, CT_UPDATED);
+ NotifyTransactionChanged(originalHash, CT_UPDATED);
return success;
}
@@ -819,12 +830,11 @@ void CWallet::SetSpentKeyState(WalletBatch& batch, const uint256& hash, unsigned
CTxDestination dst;
if (ExtractDestination(srctx->tx->vout[n].scriptPubKey, dst)) {
if (IsMine(dst)) {
- if (used && !GetDestData(dst, "used", nullptr)) {
- if (AddDestData(batch, dst, "used", "p")) { // p for "present", opposite of absent (null)
+ if (used != IsAddressUsed(dst)) {
+ if (used) {
tx_destinations.insert(dst);
}
- } else if (!used && GetDestData(dst, "used", nullptr)) {
- EraseDestData(batch, dst, "used");
+ SetAddressUsed(batch, dst, used);
}
}
}
@@ -840,7 +850,7 @@ bool CWallet::IsSpentKey(const uint256& hash, unsigned int n) const
if (!ExtractDestination(srctx->tx->vout[n].scriptPubKey, dest)) {
return false;
}
- if (GetDestData(dest, "used", nullptr)) {
+ if (IsAddressUsed(dest)) {
return true;
}
if (IsLegacy()) {
@@ -848,15 +858,15 @@ bool CWallet::IsSpentKey(const uint256& hash, unsigned int n) const
assert(spk_man != nullptr);
for (const auto& keyid : GetAffectedKeys(srctx->tx->vout[n].scriptPubKey, *spk_man)) {
WitnessV0KeyHash wpkh_dest(keyid);
- if (GetDestData(wpkh_dest, "used", nullptr)) {
+ if (IsAddressUsed(wpkh_dest)) {
return true;
}
ScriptHash sh_wpkh_dest(GetScriptForDestination(wpkh_dest));
- if (GetDestData(sh_wpkh_dest, "used", nullptr)) {
+ if (IsAddressUsed(sh_wpkh_dest)) {
return true;
}
PKHash pkh_dest(keyid);
- if (GetDestData(pkh_dest, "used", nullptr)) {
+ if (IsAddressUsed(pkh_dest)) {
return true;
}
}
@@ -935,7 +945,7 @@ CWalletTx* CWallet::AddToWallet(CTransactionRef tx, const CWalletTx::Confirmatio
wtx.MarkDirty();
// Notify UI of new or updated transaction
- NotifyTransactionChanged(this, hash, fInsertedNew ? CT_NEW : CT_UPDATED);
+ NotifyTransactionChanged(hash, fInsertedNew ? CT_NEW : CT_UPDATED);
#if HAVE_SYSTEM
// notify an external script when a wallet transaction comes in or is updated
@@ -1109,7 +1119,7 @@ bool CWallet::AbandonTransaction(const uint256& hashTx)
wtx.setAbandoned();
wtx.MarkDirty();
batch.WriteTx(wtx);
- NotifyTransactionChanged(this, wtx.GetHash(), CT_UPDATED);
+ NotifyTransactionChanged(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(now, 0));
while (iter != mapTxSpends.end() && iter->first.hash == now) {
@@ -1281,20 +1291,6 @@ void CWallet::BlockUntilSyncedToCurrentChain() const {
chain().waitForNotificationsIfTipChanged(last_block_hash);
}
-
-isminetype CWallet::IsMine(const CTxIn &txin) const
-{
- AssertLockHeld(cs_wallet);
- std::map<uint256, CWalletTx>::const_iterator mi = mapWallet.find(txin.prevout.hash);
- if (mi != mapWallet.end())
- {
- const CWalletTx& prev = (*mi).second;
- if (txin.prevout.n < prev.tx->vout.size())
- return IsMine(prev.tx->vout[txin.prevout.n]);
- }
- return ISMINE_NO;
-}
-
// Note that this function doesn't distinguish between a 0-valued input,
// and a not-"is mine" (according to the filter) input.
CAmount CWallet::GetDebit(const CTxIn &txin, const isminefilter& filter) const
@@ -1335,49 +1331,6 @@ isminetype CWallet::IsMine(const CScript& script) const
return result;
}
-CAmount CWallet::GetCredit(const CTxOut& txout, const isminefilter& filter) const
-{
- if (!MoneyRange(txout.nValue))
- throw std::runtime_error(std::string(__func__) + ": value out of range");
- LOCK(cs_wallet);
- return ((IsMine(txout) & filter) ? txout.nValue : 0);
-}
-
-bool CWallet::IsChange(const CTxOut& txout) const
-{
- return IsChange(txout.scriptPubKey);
-}
-
-bool CWallet::IsChange(const CScript& script) const
-{
- // TODO: fix handling of 'change' outputs. The assumption is that any
- // payment to a script that is ours, but is not in the address book
- // is change. That assumption is likely to break when we implement multisignature
- // wallets that return change back into a multi-signature-protected address;
- // a better way of identifying which outputs are 'the send' and which are
- // 'the change' will need to be implemented (maybe extend CWalletTx to remember
- // which output, if any, was change).
- AssertLockHeld(cs_wallet);
- if (IsMine(script))
- {
- CTxDestination address;
- if (!ExtractDestination(script, address))
- return true;
- if (!FindAddressBookEntry(address)) {
- return true;
- }
- }
- return false;
-}
-
-CAmount CWallet::GetChange(const CTxOut& txout) const
-{
- AssertLockHeld(cs_wallet);
- if (!MoneyRange(txout.nValue))
- throw std::runtime_error(std::string(__func__) + ": value out of range");
- return (IsChange(txout) ? txout.nValue : 0);
-}
-
bool CWallet::IsMine(const CTransaction& tx) const
{
AssertLockHeld(cs_wallet);
@@ -1404,52 +1357,6 @@ CAmount CWallet::GetDebit(const CTransaction& tx, const isminefilter& filter) co
return nDebit;
}
-bool CWallet::IsAllFromMe(const CTransaction& tx, const isminefilter& filter) const
-{
- LOCK(cs_wallet);
-
- for (const CTxIn& txin : tx.vin)
- {
- auto mi = mapWallet.find(txin.prevout.hash);
- if (mi == mapWallet.end())
- return false; // any unknown inputs can't be from us
-
- const CWalletTx& prev = (*mi).second;
-
- if (txin.prevout.n >= prev.tx->vout.size())
- return false; // invalid input!
-
- if (!(IsMine(prev.tx->vout[txin.prevout.n]) & filter))
- return false;
- }
- return true;
-}
-
-CAmount CWallet::GetCredit(const CTransaction& tx, const isminefilter& filter) const
-{
- CAmount nCredit = 0;
- for (const CTxOut& txout : tx.vout)
- {
- nCredit += GetCredit(txout, filter);
- if (!MoneyRange(nCredit))
- throw std::runtime_error(std::string(__func__) + ": value out of range");
- }
- return nCredit;
-}
-
-CAmount CWallet::GetChange(const CTransaction& tx) const
-{
- LOCK(cs_wallet);
- CAmount nChange = 0;
- for (const CTxOut& txout : tx.vout)
- {
- nChange += GetChange(txout);
- if (!MoneyRange(nChange))
- throw std::runtime_error(std::string(__func__) + ": value out of range");
- }
- return nChange;
-}
-
bool CWallet::IsHDEnabled() const
{
// All Active ScriptPubKeyMans must be HD for this to be true
@@ -1529,12 +1436,6 @@ bool CWallet::AddWalletFlags(uint64_t flags)
return LoadWalletFlags(flags);
}
-int64_t CWalletTx::GetTxTime() const
-{
- int64_t n = nTimeSmart;
- return n ? n : nTimeReceived;
-}
-
// Helper for producing a max-sized low-S low-R signature (eg 71 bytes)
// or a max-sized low-S signature (e.g. 72 bytes) if use_max_sig is true
bool CWallet::DummySignInput(CTxIn &tx_in, const CTxOut &txout, bool use_max_sig) const
@@ -1625,101 +1526,6 @@ bool CWallet::ImportScriptPubKeys(const std::string& label, const std::set<CScri
return true;
}
-// Returns pair of vsize and weight
-std::pair<int64_t, int64_t> CalculateMaximumSignedTxSize(const CTransaction &tx, const CWallet *wallet, bool use_max_sig)
-{
- std::vector<CTxOut> txouts;
- for (const CTxIn& input : tx.vin) {
- const auto mi = wallet->mapWallet.find(input.prevout.hash);
- // Can not estimate size without knowing the input details
- if (mi == wallet->mapWallet.end()) {
- return std::make_pair(-1, -1);
- }
- assert(input.prevout.n < mi->second.tx->vout.size());
- txouts.emplace_back(mi->second.tx->vout[input.prevout.n]);
- }
- return CalculateMaximumSignedTxSize(tx, wallet, txouts, use_max_sig);
-}
-
-// txouts needs to be in the order of tx.vin
-std::pair<int64_t, int64_t> CalculateMaximumSignedTxSize(const CTransaction &tx, const CWallet *wallet, const std::vector<CTxOut>& txouts, bool use_max_sig)
-{
- CMutableTransaction txNew(tx);
- if (!wallet->DummySignTx(txNew, txouts, use_max_sig)) {
- return std::make_pair(-1, -1);
- }
- CTransaction ctx(txNew);
- int64_t vsize = GetVirtualTransactionSize(ctx);
- int64_t weight = GetTransactionWeight(ctx);
- return std::make_pair(vsize, weight);
-}
-
-int CalculateMaximumSignedInputSize(const CTxOut& txout, const CWallet* wallet, bool use_max_sig)
-{
- CMutableTransaction txn;
- txn.vin.push_back(CTxIn(COutPoint()));
- if (!wallet->DummySignInput(txn.vin[0], txout, use_max_sig)) {
- return -1;
- }
- return GetVirtualTransactionInputSize(txn.vin[0]);
-}
-
-void CWalletTx::GetAmounts(std::list<COutputEntry>& listReceived,
- std::list<COutputEntry>& listSent, CAmount& nFee, const isminefilter& filter) const
-{
- nFee = 0;
- listReceived.clear();
- listSent.clear();
-
- // Compute fee:
- CAmount nDebit = GetDebit(filter);
- if (nDebit > 0) // debit>0 means we signed/sent this transaction
- {
- CAmount nValueOut = tx->GetValueOut();
- nFee = nDebit - nValueOut;
- }
-
- LOCK(pwallet->cs_wallet);
- // Sent/received.
- for (unsigned int i = 0; i < tx->vout.size(); ++i)
- {
- const CTxOut& txout = tx->vout[i];
- isminetype fIsMine = pwallet->IsMine(txout);
- // Only need to handle txouts if AT LEAST one of these is true:
- // 1) they debit from us (sent)
- // 2) the output is to us (received)
- if (nDebit > 0)
- {
- // Don't report 'change' txouts
- if (pwallet->IsChange(txout))
- continue;
- }
- else if (!(fIsMine & filter))
- continue;
-
- // In either case, we need to get the destination address
- CTxDestination address;
-
- if (!ExtractDestination(txout.scriptPubKey, address) && !txout.scriptPubKey.IsUnspendable())
- {
- pwallet->WalletLogPrintf("CWalletTx::GetAmounts: Unknown transaction type found, txid %s\n",
- this->GetHash().ToString());
- address = CNoDestination();
- }
-
- COutputEntry output = {address, txout.nValue, (int)i};
-
- // If we are debited by the transaction, add the output as a "sent" entry
- if (nDebit > 0)
- listSent.push_back(output);
-
- // If we are receiving the output, add it as a "received" entry
- if (fIsMine & filter)
- listReceived.push_back(output);
- }
-
-}
-
/**
* Scan active chain for relevant transactions after importing keys. This should
* be called whenever new keys are added to the wallet, with the oldest key
@@ -1942,165 +1748,6 @@ std::set<uint256> CWalletTx::GetConflicts() const
return result;
}
-CAmount CWalletTx::GetCachableAmount(AmountType type, const isminefilter& filter, bool recalculate) const
-{
- auto& amount = m_amounts[type];
- if (recalculate || !amount.m_cached[filter]) {
- amount.Set(filter, type == DEBIT ? pwallet->GetDebit(*tx, filter) : pwallet->GetCredit(*tx, filter));
- m_is_cache_empty = false;
- }
- return amount.m_value[filter];
-}
-
-CAmount CWalletTx::GetDebit(const isminefilter& filter) const
-{
- if (tx->vin.empty())
- return 0;
-
- CAmount debit = 0;
- if (filter & ISMINE_SPENDABLE) {
- debit += GetCachableAmount(DEBIT, ISMINE_SPENDABLE);
- }
- if (filter & ISMINE_WATCH_ONLY) {
- debit += GetCachableAmount(DEBIT, ISMINE_WATCH_ONLY);
- }
- return debit;
-}
-
-CAmount CWalletTx::GetCredit(const isminefilter& filter) const
-{
- // Must wait until coinbase is safely deep enough in the chain before valuing it
- if (IsImmatureCoinBase())
- return 0;
-
- CAmount credit = 0;
- if (filter & ISMINE_SPENDABLE) {
- // GetBalance can assume transactions in mapWallet won't change
- credit += GetCachableAmount(CREDIT, ISMINE_SPENDABLE);
- }
- if (filter & ISMINE_WATCH_ONLY) {
- credit += GetCachableAmount(CREDIT, ISMINE_WATCH_ONLY);
- }
- return credit;
-}
-
-CAmount CWalletTx::GetImmatureCredit(bool fUseCache) const
-{
- if (IsImmatureCoinBase() && IsInMainChain()) {
- return GetCachableAmount(IMMATURE_CREDIT, ISMINE_SPENDABLE, !fUseCache);
- }
-
- return 0;
-}
-
-CAmount CWalletTx::GetAvailableCredit(bool fUseCache, const isminefilter& filter) const
-{
- if (pwallet == nullptr)
- return 0;
-
- // Avoid caching ismine for NO or ALL cases (could remove this check and simplify in the future).
- bool allow_cache = (filter & ISMINE_ALL) && (filter & ISMINE_ALL) != ISMINE_ALL;
-
- // Must wait until coinbase is safely deep enough in the chain before valuing it
- if (IsImmatureCoinBase())
- return 0;
-
- if (fUseCache && allow_cache && m_amounts[AVAILABLE_CREDIT].m_cached[filter]) {
- return m_amounts[AVAILABLE_CREDIT].m_value[filter];
- }
-
- bool allow_used_addresses = (filter & ISMINE_USED) || !pwallet->IsWalletFlagSet(WALLET_FLAG_AVOID_REUSE);
- CAmount nCredit = 0;
- uint256 hashTx = GetHash();
- for (unsigned int i = 0; i < tx->vout.size(); i++)
- {
- if (!pwallet->IsSpent(hashTx, i) && (allow_used_addresses || !pwallet->IsSpentKey(hashTx, i))) {
- const CTxOut &txout = tx->vout[i];
- nCredit += pwallet->GetCredit(txout, filter);
- if (!MoneyRange(nCredit))
- throw std::runtime_error(std::string(__func__) + " : value out of range");
- }
- }
-
- if (allow_cache) {
- m_amounts[AVAILABLE_CREDIT].Set(filter, nCredit);
- m_is_cache_empty = false;
- }
-
- return nCredit;
-}
-
-CAmount CWalletTx::GetImmatureWatchOnlyCredit(const bool fUseCache) const
-{
- if (IsImmatureCoinBase() && IsInMainChain()) {
- return GetCachableAmount(IMMATURE_CREDIT, ISMINE_WATCH_ONLY, !fUseCache);
- }
-
- return 0;
-}
-
-CAmount CWalletTx::GetChange() const
-{
- if (fChangeCached)
- return nChangeCached;
- nChangeCached = pwallet->GetChange(*tx);
- fChangeCached = true;
- return nChangeCached;
-}
-
-bool CWalletTx::InMempool() const
-{
- return fInMempool;
-}
-
-bool CWalletTx::IsTrusted() const
-{
- std::set<uint256> trusted_parents;
- LOCK(pwallet->cs_wallet);
- return pwallet->IsTrusted(*this, trusted_parents);
-}
-
-bool CWallet::IsTrusted(const CWalletTx& wtx, std::set<uint256>& trusted_parents) const
-{
- AssertLockHeld(cs_wallet);
- // Quick answer in most cases
- if (!chain().checkFinalTx(*wtx.tx)) return false;
- int nDepth = wtx.GetDepthInMainChain();
- if (nDepth >= 1) return true;
- if (nDepth < 0) return false;
- // using wtx's cached debit
- if (!m_spend_zero_conf_change || !wtx.IsFromMe(ISMINE_ALL)) return false;
-
- // Don't trust unconfirmed transactions from us unless they are in the mempool.
- if (!wtx.InMempool()) return false;
-
- // Trusted if all inputs are from us and are in the mempool:
- for (const CTxIn& txin : wtx.tx->vin)
- {
- // Transactions not sent by us: not trusted
- const CWalletTx* parent = GetWalletTx(txin.prevout.hash);
- if (parent == nullptr) return false;
- const CTxOut& parentOut = parent->tx->vout[txin.prevout.n];
- // Check that this specific input being spent is trusted
- if (IsMine(parentOut) != ISMINE_SPENDABLE) return false;
- // If we've already trusted this parent, continue
- if (trusted_parents.count(parent->GetHash())) continue;
- // Recurse to check that the parent is also trusted
- if (!IsTrusted(*parent, trusted_parents)) return false;
- trusted_parents.insert(parent->GetHash());
- }
- return true;
-}
-
-bool CWalletTx::IsEquivalentTo(const CWalletTx& _tx) const
-{
- CMutableTransaction tx1 {*this->tx};
- CMutableTransaction tx2 {*_tx.tx};
- for (auto& txin : tx1.vin) txin.scriptSig = CScript();
- for (auto& txin : tx2.vin) txin.scriptSig = CScript();
- return CTransaction(tx1) == CTransaction(tx2);
-}
-
// Rebroadcast transactions from the wallet. We do this on a random timer
// to slightly obfuscate which transactions come from our wallet.
//
@@ -2161,410 +1808,6 @@ void MaybeResendWalletTxs()
* @{
*/
-
-CWallet::Balance CWallet::GetBalance(const int min_depth, bool avoid_reuse) const
-{
- Balance ret;
- isminefilter reuse_filter = avoid_reuse ? ISMINE_NO : ISMINE_USED;
- {
- LOCK(cs_wallet);
- std::set<uint256> trusted_parents;
- for (const auto& entry : mapWallet)
- {
- const CWalletTx& wtx = entry.second;
- const bool is_trusted{IsTrusted(wtx, trusted_parents)};
- const int tx_depth{wtx.GetDepthInMainChain()};
- const CAmount tx_credit_mine{wtx.GetAvailableCredit(/* fUseCache */ true, ISMINE_SPENDABLE | reuse_filter)};
- const CAmount tx_credit_watchonly{wtx.GetAvailableCredit(/* fUseCache */ true, ISMINE_WATCH_ONLY | reuse_filter)};
- if (is_trusted && tx_depth >= min_depth) {
- ret.m_mine_trusted += tx_credit_mine;
- ret.m_watchonly_trusted += tx_credit_watchonly;
- }
- if (!is_trusted && tx_depth == 0 && wtx.InMempool()) {
- ret.m_mine_untrusted_pending += tx_credit_mine;
- ret.m_watchonly_untrusted_pending += tx_credit_watchonly;
- }
- ret.m_mine_immature += wtx.GetImmatureCredit();
- ret.m_watchonly_immature += wtx.GetImmatureWatchOnlyCredit();
- }
- }
- return ret;
-}
-
-CAmount CWallet::GetAvailableBalance(const CCoinControl* coinControl) const
-{
- LOCK(cs_wallet);
-
- CAmount balance = 0;
- std::vector<COutput> vCoins;
- AvailableCoins(vCoins, coinControl);
- for (const COutput& out : vCoins) {
- if (out.fSpendable) {
- balance += out.tx->tx->vout[out.i].nValue;
- }
- }
- return balance;
-}
-
-void CWallet::AvailableCoins(std::vector<COutput>& vCoins, const CCoinControl* coinControl, const CAmount& nMinimumAmount, const CAmount& nMaximumAmount, const CAmount& nMinimumSumAmount, const uint64_t nMaximumCount) const
-{
- AssertLockHeld(cs_wallet);
-
- vCoins.clear();
- CAmount nTotal = 0;
- // Either the WALLET_FLAG_AVOID_REUSE flag is not set (in which case we always allow), or we default to avoiding, and only in the case where
- // a coin control object is provided, and has the avoid address reuse flag set to false, do we allow already used addresses
- bool allow_used_addresses = !IsWalletFlagSet(WALLET_FLAG_AVOID_REUSE) || (coinControl && !coinControl->m_avoid_address_reuse);
- const int min_depth = {coinControl ? coinControl->m_min_depth : DEFAULT_MIN_DEPTH};
- const int max_depth = {coinControl ? coinControl->m_max_depth : DEFAULT_MAX_DEPTH};
- const bool only_safe = {coinControl ? !coinControl->m_include_unsafe_inputs : true};
-
- std::set<uint256> trusted_parents;
- for (const auto& entry : mapWallet)
- {
- const uint256& wtxid = entry.first;
- const CWalletTx& wtx = entry.second;
-
- if (!chain().checkFinalTx(*wtx.tx)) {
- continue;
- }
-
- if (wtx.IsImmatureCoinBase())
- continue;
-
- int nDepth = wtx.GetDepthInMainChain();
- if (nDepth < 0)
- continue;
-
- // We should not consider coins which aren't at least in our mempool
- // It's possible for these to be conflicted via ancestors which we may never be able to detect
- if (nDepth == 0 && !wtx.InMempool())
- continue;
-
- bool safeTx = IsTrusted(wtx, trusted_parents);
-
- // We should not consider coins from transactions that are replacing
- // other transactions.
- //
- // Example: There is a transaction A which is replaced by bumpfee
- // transaction B. In this case, we want to prevent creation of
- // a transaction B' which spends an output of B.
- //
- // Reason: If transaction A were initially confirmed, transactions B
- // and B' would no longer be valid, so the user would have to create
- // a new transaction C to replace B'. However, in the case of a
- // one-block reorg, transactions B' and C might BOTH be accepted,
- // when the user only wanted one of them. Specifically, there could
- // be a 1-block reorg away from the chain where transactions A and C
- // were accepted to another chain where B, B', and C were all
- // accepted.
- if (nDepth == 0 && wtx.mapValue.count("replaces_txid")) {
- safeTx = false;
- }
-
- // Similarly, we should not consider coins from transactions that
- // have been replaced. In the example above, we would want to prevent
- // creation of a transaction A' spending an output of A, because if
- // transaction B were initially confirmed, conflicting with A and
- // A', we wouldn't want to the user to create a transaction D
- // intending to replace A', but potentially resulting in a scenario
- // where A, A', and D could all be accepted (instead of just B and
- // D, or just A and A' like the user would want).
- if (nDepth == 0 && wtx.mapValue.count("replaced_by_txid")) {
- safeTx = false;
- }
-
- if (only_safe && !safeTx) {
- continue;
- }
-
- if (nDepth < min_depth || nDepth > max_depth) {
- continue;
- }
-
- for (unsigned int i = 0; i < wtx.tx->vout.size(); i++) {
- // Only consider selected coins if add_inputs is false
- if (coinControl && !coinControl->m_add_inputs && !coinControl->IsSelected(COutPoint(entry.first, i))) {
- continue;
- }
-
- if (wtx.tx->vout[i].nValue < nMinimumAmount || wtx.tx->vout[i].nValue > nMaximumAmount)
- continue;
-
- if (coinControl && coinControl->HasSelected() && !coinControl->fAllowOtherInputs && !coinControl->IsSelected(COutPoint(entry.first, i)))
- continue;
-
- if (IsLockedCoin(entry.first, i))
- continue;
-
- if (IsSpent(wtxid, i))
- continue;
-
- isminetype mine = IsMine(wtx.tx->vout[i]);
-
- if (mine == ISMINE_NO) {
- continue;
- }
-
- if (!allow_used_addresses && IsSpentKey(wtxid, i)) {
- continue;
- }
-
- std::unique_ptr<SigningProvider> provider = GetSolvingProvider(wtx.tx->vout[i].scriptPubKey);
-
- bool solvable = provider ? IsSolvable(*provider, wtx.tx->vout[i].scriptPubKey) : false;
- bool spendable = ((mine & ISMINE_SPENDABLE) != ISMINE_NO) || (((mine & ISMINE_WATCH_ONLY) != ISMINE_NO) && (coinControl && coinControl->fAllowWatchOnly && solvable));
-
- vCoins.push_back(COutput(&wtx, i, nDepth, spendable, solvable, safeTx, (coinControl && coinControl->fAllowWatchOnly)));
-
- // Checks the sum amount of all UTXO's.
- if (nMinimumSumAmount != MAX_MONEY) {
- nTotal += wtx.tx->vout[i].nValue;
-
- if (nTotal >= nMinimumSumAmount) {
- return;
- }
- }
-
- // Checks the maximum number of UTXO's.
- if (nMaximumCount > 0 && vCoins.size() >= nMaximumCount) {
- return;
- }
- }
- }
-}
-
-std::map<CTxDestination, std::vector<COutput>> CWallet::ListCoins() const
-{
- AssertLockHeld(cs_wallet);
-
- std::map<CTxDestination, std::vector<COutput>> result;
- std::vector<COutput> availableCoins;
-
- AvailableCoins(availableCoins);
-
- for (const COutput& coin : availableCoins) {
- CTxDestination address;
- if ((coin.fSpendable || (IsWalletFlagSet(WALLET_FLAG_DISABLE_PRIVATE_KEYS) && coin.fSolvable)) &&
- ExtractDestination(FindNonChangeParentOutput(*coin.tx->tx, coin.i).scriptPubKey, address)) {
- result[address].emplace_back(std::move(coin));
- }
- }
-
- std::vector<COutPoint> lockedCoins;
- ListLockedCoins(lockedCoins);
- // Include watch-only for LegacyScriptPubKeyMan wallets without private keys
- const bool include_watch_only = GetLegacyScriptPubKeyMan() && IsWalletFlagSet(WALLET_FLAG_DISABLE_PRIVATE_KEYS);
- const isminetype is_mine_filter = include_watch_only ? ISMINE_WATCH_ONLY : ISMINE_SPENDABLE;
- for (const COutPoint& output : lockedCoins) {
- auto it = mapWallet.find(output.hash);
- if (it != mapWallet.end()) {
- int depth = it->second.GetDepthInMainChain();
- if (depth >= 0 && output.n < it->second.tx->vout.size() &&
- IsMine(it->second.tx->vout[output.n]) == is_mine_filter
- ) {
- CTxDestination address;
- if (ExtractDestination(FindNonChangeParentOutput(*it->second.tx, output.n).scriptPubKey, address)) {
- result[address].emplace_back(
- &it->second, output.n, depth, true /* spendable */, true /* solvable */, false /* safe */);
- }
- }
- }
- }
-
- return result;
-}
-
-const CTxOut& CWallet::FindNonChangeParentOutput(const CTransaction& tx, int output) const
-{
- AssertLockHeld(cs_wallet);
- const CTransaction* ptx = &tx;
- int n = output;
- while (IsChange(ptx->vout[n]) && ptx->vin.size() > 0) {
- const COutPoint& prevout = ptx->vin[0].prevout;
- auto it = mapWallet.find(prevout.hash);
- if (it == mapWallet.end() || it->second.tx->vout.size() <= prevout.n ||
- !IsMine(it->second.tx->vout[prevout.n])) {
- break;
- }
- ptx = it->second.tx.get();
- n = prevout.n;
- }
- return ptx->vout[n];
-}
-
-bool CWallet::SelectCoinsMinConf(const CAmount& nTargetValue, const CoinEligibilityFilter& eligibility_filter, std::vector<COutput> coins,
- std::set<CInputCoin>& setCoinsRet, CAmount& nValueRet, const CoinSelectionParams& coin_selection_params, bool& bnb_used) const
-{
- setCoinsRet.clear();
- nValueRet = 0;
-
- if (coin_selection_params.use_bnb) {
- // Get the feerate for effective value.
- // When subtracting the fee from the outputs, we want the effective feerate to be 0
- CFeeRate effective_feerate{0};
- if (!coin_selection_params.m_subtract_fee_outputs) {
- effective_feerate = coin_selection_params.m_effective_feerate;
- }
-
- std::vector<OutputGroup> groups = GroupOutputs(coins, !coin_selection_params.m_avoid_partial_spends, effective_feerate, coin_selection_params.m_long_term_feerate, eligibility_filter, true /* positive_only */);
-
- // Calculate cost of change
- CAmount cost_of_change = coin_selection_params.m_discard_feerate.GetFee(coin_selection_params.change_spend_size) + coin_selection_params.m_effective_feerate.GetFee(coin_selection_params.change_output_size);
-
- // Calculate the fees for things that aren't inputs
- CAmount not_input_fees = coin_selection_params.m_effective_feerate.GetFee(coin_selection_params.tx_noinputs_size);
- bnb_used = true;
- return SelectCoinsBnB(groups, nTargetValue, cost_of_change, setCoinsRet, nValueRet, not_input_fees);
- } else {
- std::vector<OutputGroup> groups = GroupOutputs(coins, !coin_selection_params.m_avoid_partial_spends, CFeeRate(0), CFeeRate(0), eligibility_filter, false /* positive_only */);
-
- bnb_used = false;
- return KnapsackSolver(nTargetValue, groups, setCoinsRet, nValueRet);
- }
-}
-
-bool CWallet::SelectCoins(const std::vector<COutput>& vAvailableCoins, const CAmount& nTargetValue, std::set<CInputCoin>& setCoinsRet, CAmount& nValueRet, const CCoinControl& coin_control, CoinSelectionParams& coin_selection_params, bool& bnb_used) const
-{
- std::vector<COutput> vCoins(vAvailableCoins);
- CAmount value_to_select = nTargetValue;
-
- // Default to bnb was not used. If we use it, we set it later
- bnb_used = false;
-
- // coin control -> return all selected outputs (we want all selected to go into the transaction for sure)
- if (coin_control.HasSelected() && !coin_control.fAllowOtherInputs)
- {
- for (const COutput& out : vCoins)
- {
- if (!out.fSpendable)
- continue;
- nValueRet += out.tx->tx->vout[out.i].nValue;
- setCoinsRet.insert(out.GetInputCoin());
- }
- return (nValueRet >= nTargetValue);
- }
-
- // calculate value from preset inputs and store them
- std::set<CInputCoin> setPresetCoins;
- CAmount nValueFromPresetInputs = 0;
-
- std::vector<COutPoint> vPresetInputs;
- coin_control.ListSelected(vPresetInputs);
- for (const COutPoint& outpoint : vPresetInputs)
- {
- std::map<uint256, CWalletTx>::const_iterator it = mapWallet.find(outpoint.hash);
- if (it != mapWallet.end())
- {
- const CWalletTx& wtx = it->second;
- // Clearly invalid input, fail
- if (wtx.tx->vout.size() <= outpoint.n) {
- return false;
- }
- // Just to calculate the marginal byte size
- CInputCoin coin(wtx.tx, outpoint.n, wtx.GetSpendSize(outpoint.n, false));
- nValueFromPresetInputs += coin.txout.nValue;
- if (coin.m_input_bytes <= 0) {
- return false; // Not solvable, can't estimate size for fee
- }
- coin.effective_value = coin.txout.nValue - coin_selection_params.m_effective_feerate.GetFee(coin.m_input_bytes);
- if (coin_selection_params.use_bnb) {
- value_to_select -= coin.effective_value;
- } else {
- value_to_select -= coin.txout.nValue;
- }
- setPresetCoins.insert(coin);
- } else {
- return false; // TODO: Allow non-wallet inputs
- }
- }
-
- // remove preset inputs from vCoins so that Coin Selection doesn't pick them.
- for (std::vector<COutput>::iterator it = vCoins.begin(); it != vCoins.end() && coin_control.HasSelected();)
- {
- if (setPresetCoins.count(it->GetInputCoin()))
- it = vCoins.erase(it);
- else
- ++it;
- }
-
- unsigned int limit_ancestor_count = 0;
- unsigned int limit_descendant_count = 0;
- chain().getPackageLimits(limit_ancestor_count, limit_descendant_count);
- const size_t max_ancestors = (size_t)std::max<int64_t>(1, limit_ancestor_count);
- const size_t max_descendants = (size_t)std::max<int64_t>(1, limit_descendant_count);
- const bool fRejectLongChains = gArgs.GetBoolArg("-walletrejectlongchains", DEFAULT_WALLET_REJECT_LONG_CHAINS);
-
- // form groups from remaining coins; note that preset coins will not
- // automatically have their associated (same address) coins included
- if (coin_control.m_avoid_partial_spends && vCoins.size() > OUTPUT_GROUP_MAX_ENTRIES) {
- // Cases where we have 11+ outputs all pointing to the same destination may result in
- // privacy leaks as they will potentially be deterministically sorted. We solve that by
- // explicitly shuffling the outputs before processing
- Shuffle(vCoins.begin(), vCoins.end(), FastRandomContext());
- }
-
- // Coin Selection attempts to select inputs from a pool of eligible UTXOs to fund the
- // transaction at a target feerate. If an attempt fails, more attempts may be made using a more
- // permissive CoinEligibilityFilter.
- const bool res = [&] {
- // Pre-selected inputs already cover the target amount.
- if (value_to_select <= 0) return true;
-
- // If possible, fund the transaction with confirmed UTXOs only. Prefer at least six
- // confirmations on outputs received from other wallets and only spend confirmed change.
- if (SelectCoinsMinConf(value_to_select, CoinEligibilityFilter(1, 6, 0), vCoins, setCoinsRet, nValueRet, coin_selection_params, bnb_used)) return true;
- if (SelectCoinsMinConf(value_to_select, CoinEligibilityFilter(1, 1, 0), vCoins, setCoinsRet, nValueRet, coin_selection_params, bnb_used)) return true;
-
- // Fall back to using zero confirmation change (but with as few ancestors in the mempool as
- // possible) if we cannot fund the transaction otherwise.
- if (m_spend_zero_conf_change) {
- if (SelectCoinsMinConf(value_to_select, CoinEligibilityFilter(0, 1, 2), vCoins, setCoinsRet, nValueRet, coin_selection_params, bnb_used)) return true;
- if (SelectCoinsMinConf(value_to_select, 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)) {
- return true;
- }
- if (SelectCoinsMinConf(value_to_select, CoinEligibilityFilter(0, 1, max_ancestors/2, max_descendants/2),
- vCoins, setCoinsRet, nValueRet, coin_selection_params, bnb_used)) {
- return true;
- }
- // If partial groups are allowed, relax the requirement of spending OutputGroups (groups
- // of UTXOs sent to the same address, which are obviously controlled by a single wallet)
- // in their entirety.
- if (SelectCoinsMinConf(value_to_select, CoinEligibilityFilter(0, 1, max_ancestors-1, max_descendants-1, true /* include_partial_groups */),
- vCoins, setCoinsRet, nValueRet, coin_selection_params, bnb_used)) {
- return true;
- }
- // Try with unsafe inputs if they are allowed. This may spend unconfirmed outputs
- // received from other wallets.
- if (coin_control.m_include_unsafe_inputs
- && SelectCoinsMinConf(value_to_select,
- CoinEligibilityFilter(0 /* conf_mine */, 0 /* conf_theirs */, max_ancestors-1, max_descendants-1, true /* include_partial_groups */),
- vCoins, setCoinsRet, nValueRet, coin_selection_params, bnb_used)) {
- return true;
- }
- // Try with unlimited ancestors/descendants. The transaction will still need to meet
- // mempool ancestor/descendant policy to be accepted to mempool and broadcasted, but
- // OutputGroups use heuristics that may overestimate ancestor/descendant counts.
- if (!fRejectLongChains && SelectCoinsMinConf(value_to_select,
- CoinEligibilityFilter(0, 1, std::numeric_limits<uint64_t>::max(), std::numeric_limits<uint64_t>::max(), true /* include_partial_groups */),
- vCoins, setCoinsRet, nValueRet, coin_selection_params, bnb_used)) {
- return true;
- }
- }
- // Coin Selection failed.
- return false;
- }();
-
- // SelectCoinsMinConf clears setCoinsRet, so add the preset inputs from coin_control to the coinset
- util::insert(setCoinsRet, setPresetCoins);
-
- // add preset inputs to the total value selected
- nValueRet += nValueFromPresetInputs;
-
- return res;
-}
-
bool CWallet::SignTransaction(CMutableTransaction& tx) const
{
AssertLockHeld(cs_wallet);
@@ -2580,7 +1823,7 @@ bool CWallet::SignTransaction(CMutableTransaction& tx) const
coins[input.prevout] = Coin(wtx.tx->vout[input.prevout.n], wtx.m_confirm.block_height, wtx.IsCoinBase());
}
std::map<int, std::string> input_errors;
- return SignTransaction(tx, coins, SIGHASH_ALL, input_errors);
+ return SignTransaction(tx, coins, SIGHASH_DEFAULT, input_errors);
}
bool CWallet::SignTransaction(CMutableTransaction& tx, const std::map<COutPoint, Coin>& coins, int sighash, std::map<int, std::string>& input_errors) const
@@ -2603,6 +1846,7 @@ TransactionError CWallet::FillPSBT(PartiallySignedTransaction& psbtx, bool& comp
if (n_signed) {
*n_signed = 0;
}
+ const PrecomputedTransactionData txdata = PrecomputePSBTData(psbtx);
LOCK(cs_wallet);
// Get all of the previous transactions
for (unsigned int i = 0; i < psbtx.tx->vin.size(); ++i) {
@@ -2629,7 +1873,7 @@ TransactionError CWallet::FillPSBT(PartiallySignedTransaction& psbtx, bool& comp
// Fill in information from ScriptPubKeyMans
for (ScriptPubKeyMan* spk_man : GetAllScriptPubKeyMans()) {
int n_signed_this_spkm = 0;
- TransactionError res = spk_man->FillPSBT(psbtx, sighash_type, sign, bip32derivs, &n_signed_this_spkm);
+ TransactionError res = spk_man->FillPSBT(psbtx, txdata, sighash_type, sign, bip32derivs, &n_signed_this_spkm);
if (res != TransactionError::OK) {
return res;
}
@@ -2660,118 +1904,6 @@ SigningResult CWallet::SignMessage(const std::string& message, const PKHash& pkh
return SigningResult::PRIVATE_KEY_NOT_AVAILABLE;
}
-bool CWallet::FundTransaction(CMutableTransaction& tx, CAmount& nFeeRet, int& nChangePosInOut, bilingual_str& error, bool lockUnspents, const std::set<int>& setSubtractFeeFromOutputs, CCoinControl coinControl)
-{
- std::vector<CRecipient> vecSend;
-
- // Turn the txout set into a CRecipient vector.
- for (size_t idx = 0; idx < tx.vout.size(); idx++) {
- const CTxOut& txOut = tx.vout[idx];
- CRecipient recipient = {txOut.scriptPubKey, txOut.nValue, setSubtractFeeFromOutputs.count(idx) == 1};
- vecSend.push_back(recipient);
- }
-
- coinControl.fAllowOtherInputs = true;
-
- for (const CTxIn& txin : tx.vin) {
- coinControl.Select(txin.prevout);
- }
-
- // Acquire the locks to prevent races to the new locked unspents between the
- // CreateTransaction call and LockCoin calls (when lockUnspents is true).
- LOCK(cs_wallet);
-
- CTransactionRef tx_new;
- FeeCalculation fee_calc_out;
- if (!CreateTransaction(vecSend, tx_new, nFeeRet, nChangePosInOut, error, coinControl, fee_calc_out, false)) {
- return false;
- }
-
- if (nChangePosInOut != -1) {
- tx.vout.insert(tx.vout.begin() + nChangePosInOut, tx_new->vout[nChangePosInOut]);
- }
-
- // Copy output sizes from new transaction; they may have had the fee
- // subtracted from them.
- for (unsigned int idx = 0; idx < tx.vout.size(); idx++) {
- tx.vout[idx].nValue = tx_new->vout[idx].nValue;
- }
-
- // Add new txins while keeping original txin scriptSig/order.
- for (const CTxIn& txin : tx_new->vin) {
- if (!coinControl.IsSelected(txin.prevout)) {
- tx.vin.push_back(txin);
-
- }
- if (lockUnspents) {
- LockCoin(txin.prevout);
- }
-
- }
-
- return true;
-}
-
-static bool IsCurrentForAntiFeeSniping(interfaces::Chain& chain, const uint256& block_hash)
-{
- if (chain.isInitialBlockDownload()) {
- return false;
- }
- constexpr int64_t MAX_ANTI_FEE_SNIPING_TIP_AGE = 8 * 60 * 60; // in seconds
- int64_t block_time;
- CHECK_NONFATAL(chain.findBlock(block_hash, FoundBlock().time(block_time)));
- if (block_time < (GetTime() - MAX_ANTI_FEE_SNIPING_TIP_AGE)) {
- return false;
- }
- return true;
-}
-
-/**
- * Return a height-based locktime for new transactions (uses the height of the
- * current chain tip unless we are not synced with the current chain
- */
-static uint32_t GetLocktimeForNewTransaction(interfaces::Chain& chain, const uint256& block_hash, int block_height)
-{
- uint32_t locktime;
- // Discourage fee sniping.
- //
- // For a large miner the value of the transactions in the best block and
- // the mempool can exceed the cost of deliberately attempting to mine two
- // blocks to orphan the current best block. By setting nLockTime such that
- // only the next block can include the transaction, we discourage this
- // practice as the height restricted and limited blocksize gives miners
- // considering fee sniping fewer options for pulling off this attack.
- //
- // A simple way to think about this is from the wallet's point of view we
- // always want the blockchain to move forward. By setting nLockTime this
- // way we're basically making the statement that we only want this
- // transaction to appear in the next block; we don't want to potentially
- // encourage reorgs by allowing transactions to appear at lower heights
- // than the next block in forks of the best chain.
- //
- // Of course, the subsidy is high enough, and transaction volume low
- // enough, that fee sniping isn't a problem yet, but by implementing a fix
- // now we ensure code won't be written that makes assumptions about
- // nLockTime that preclude a fix later.
- if (IsCurrentForAntiFeeSniping(chain, block_hash)) {
- locktime = block_height;
-
- // Secondly occasionally randomly pick a nLockTime even further back, so
- // that transactions that are delayed after signing for whatever reason,
- // e.g. high-latency mix networks and some CoinJoin implementations, have
- // better privacy.
- if (GetRandInt(10) == 0)
- locktime = std::max(0, (int)locktime - GetRandInt(100));
- } else {
- // If our chain is lagging behind, we can't discourage fee sniping nor help
- // the privacy of high-latency transactions. To avoid leaking a potentially
- // unique "nLockTime fingerprint", set nLockTime to a constant.
- locktime = 0;
- }
- assert(locktime < LOCKTIME_THRESHOLD);
- return locktime;
-}
-
OutputType CWallet::TransactionChangeType(const std::optional<OutputType>& change_type, const std::vector<CRecipient>& vecSend) const
{
// If -changetype is specified, always use that change type.
@@ -2792,7 +1924,13 @@ OutputType CWallet::TransactionChangeType(const std::optional<OutputType>& chang
int witnessversion = 0;
std::vector<unsigned char> witnessprogram;
if (recipient.scriptPubKey.IsWitnessProgram(witnessversion, witnessprogram)) {
- return OutputType::BECH32;
+ if (GetScriptPubKeyMan(OutputType::BECH32M, true)) {
+ return OutputType::BECH32M;
+ } else if (GetScriptPubKeyMan(OutputType::BECH32, true)) {
+ return OutputType::BECH32;
+ } else {
+ return m_default_address_type;
+ }
}
}
@@ -2800,413 +1938,6 @@ OutputType CWallet::TransactionChangeType(const std::optional<OutputType>& chang
return m_default_address_type;
}
-bool CWallet::CreateTransactionInternal(
- const std::vector<CRecipient>& vecSend,
- CTransactionRef& tx,
- CAmount& nFeeRet,
- int& nChangePosInOut,
- bilingual_str& error,
- const CCoinControl& coin_control,
- FeeCalculation& fee_calc_out,
- bool sign)
-{
- CAmount nValue = 0;
- const OutputType change_type = TransactionChangeType(coin_control.m_change_type ? *coin_control.m_change_type : m_default_change_type, vecSend);
- ReserveDestination reservedest(this, change_type);
- int nChangePosRequest = nChangePosInOut;
- unsigned int nSubtractFeeFromAmount = 0;
- for (const auto& recipient : vecSend)
- {
- if (nValue < 0 || recipient.nAmount < 0)
- {
- error = _("Transaction amounts must not be negative");
- return false;
- }
- nValue += recipient.nAmount;
-
- if (recipient.fSubtractFeeFromAmount)
- nSubtractFeeFromAmount++;
- }
- if (vecSend.empty())
- {
- error = _("Transaction must have at least one recipient");
- return false;
- }
-
- CMutableTransaction txNew;
- FeeCalculation feeCalc;
- CAmount nFeeNeeded;
- std::pair<int64_t, int64_t> tx_sizes;
- int nBytes;
- {
- std::set<CInputCoin> setCoins;
- LOCK(cs_wallet);
- txNew.nLockTime = GetLocktimeForNewTransaction(chain(), GetLastBlockHash(), GetLastBlockHeight());
- {
- std::vector<COutput> vAvailableCoins;
- AvailableCoins(vAvailableCoins, &coin_control, 1, MAX_MONEY, MAX_MONEY, 0);
- CoinSelectionParams coin_selection_params; // Parameters for coin selection, init with dummy
- coin_selection_params.m_avoid_partial_spends = coin_control.m_avoid_partial_spends;
-
- // Create change script that will be used if we need change
- // TODO: pass in scriptChange instead of reservedest so
- // change transaction isn't always pay-to-bitcoin-address
- CScript scriptChange;
-
- // coin control: send change to custom address
- if (!std::get_if<CNoDestination>(&coin_control.destChange)) {
- scriptChange = GetScriptForDestination(coin_control.destChange);
- } else { // no coin control: send change to newly generated address
- // Note: We use a new key here to keep it from being obvious which side is the change.
- // The drawback is that by not reusing a previous key, the change may be lost if a
- // backup is restored, if the backup doesn't have the new private key for the change.
- // If we reused the old key, it would be possible to add code to look for and
- // rediscover unknown transactions that were written with keys of ours to recover
- // post-backup change.
-
- // Reserve a new key pair from key pool. If it fails, provide a dummy
- // destination in case we don't need change.
- CTxDestination dest;
- if (!reservedest.GetReservedDestination(dest, true)) {
- error = _("Transaction needs a change address, but we can't generate it. Please call keypoolrefill first.");
- }
- scriptChange = GetScriptForDestination(dest);
- // A valid destination implies a change script (and
- // vice-versa). An empty change script will abort later, if the
- // change keypool ran out, but change is required.
- CHECK_NONFATAL(IsValidDestination(dest) != scriptChange.empty());
- }
- CTxOut change_prototype_txout(0, scriptChange);
- coin_selection_params.change_output_size = GetSerializeSize(change_prototype_txout);
-
- // Set discard feerate
- coin_selection_params.m_discard_feerate = GetDiscardRate(*this);
-
- // Get the fee rate to use effective values in coin selection
- coin_selection_params.m_effective_feerate = GetMinimumFeeRate(*this, coin_control, &feeCalc);
- // Do not, ever, assume that it's fine to change the fee rate if the user has explicitly
- // provided one
- if (coin_control.m_feerate && coin_selection_params.m_effective_feerate > *coin_control.m_feerate) {
- error = strprintf(_("Fee rate (%s) is lower than the minimum fee rate setting (%s)"), coin_control.m_feerate->ToString(FeeEstimateMode::SAT_VB), coin_selection_params.m_effective_feerate.ToString(FeeEstimateMode::SAT_VB));
- return false;
- }
- if (feeCalc.reason == FeeReason::FALLBACK && !m_allow_fallback_fee) {
- // eventually allow a fallback fee
- error = _("Fee estimation failed. Fallbackfee is disabled. Wait a few blocks or enable -fallbackfee.");
- return false;
- }
-
- // Get long term estimate
- CCoinControl cc_temp;
- cc_temp.m_confirm_target = chain().estimateMaxBlocks();
- coin_selection_params.m_long_term_feerate = GetMinimumFeeRate(*this, cc_temp, nullptr);
-
- nFeeRet = 0;
- bool pick_new_inputs = true;
- CAmount nValueIn = 0;
-
- // BnB selector is the only selector used when this is true.
- // That should only happen on the first pass through the loop.
- coin_selection_params.use_bnb = true;
- coin_selection_params.m_subtract_fee_outputs = nSubtractFeeFromAmount != 0; // If we are doing subtract fee from recipient, don't use effective values
- // Start with no fee and loop until there is enough fee
- while (true)
- {
- nChangePosInOut = nChangePosRequest;
- txNew.vin.clear();
- txNew.vout.clear();
- bool fFirst = true;
-
- CAmount nValueToSelect = nValue;
- if (nSubtractFeeFromAmount == 0)
- nValueToSelect += nFeeRet;
-
- // vouts to the payees
- if (!coin_selection_params.m_subtract_fee_outputs) {
- coin_selection_params.tx_noinputs_size = 11; // Static vsize overhead + outputs vsize. 4 nVersion, 4 nLocktime, 1 input count, 1 output count, 1 witness overhead (dummy, flag, stack size)
- }
- for (const auto& recipient : vecSend)
- {
- CTxOut txout(recipient.nAmount, recipient.scriptPubKey);
-
- if (recipient.fSubtractFeeFromAmount)
- {
- assert(nSubtractFeeFromAmount != 0);
- txout.nValue -= nFeeRet / nSubtractFeeFromAmount; // Subtract fee equally from each selected recipient
-
- if (fFirst) // first receiver pays the remainder not divisible by output count
- {
- fFirst = false;
- txout.nValue -= nFeeRet % nSubtractFeeFromAmount;
- }
- }
- // Include the fee cost for outputs. Note this is only used for BnB right now
- if (!coin_selection_params.m_subtract_fee_outputs) {
- coin_selection_params.tx_noinputs_size += ::GetSerializeSize(txout, PROTOCOL_VERSION);
- }
-
- if (IsDust(txout, chain().relayDustFee()))
- {
- if (recipient.fSubtractFeeFromAmount && nFeeRet > 0)
- {
- if (txout.nValue < 0)
- error = _("The transaction amount is too small to pay the fee");
- else
- error = _("The transaction amount is too small to send after the fee has been deducted");
- }
- else
- error = _("Transaction amount too small");
- return false;
- }
- txNew.vout.push_back(txout);
- }
-
- // Choose coins to use
- bool bnb_used = false;
- if (pick_new_inputs) {
- nValueIn = 0;
- setCoins.clear();
- int change_spend_size = CalculateMaximumSignedInputSize(change_prototype_txout, this);
- // If the wallet doesn't know how to sign change output, assume p2sh-p2wpkh
- // as lower-bound to allow BnB to do it's thing
- if (change_spend_size == -1) {
- coin_selection_params.change_spend_size = DUMMY_NESTED_P2WPKH_INPUT_SIZE;
- } else {
- coin_selection_params.change_spend_size = (size_t)change_spend_size;
- }
- if (!SelectCoins(vAvailableCoins, nValueToSelect, setCoins, nValueIn, coin_control, coin_selection_params, bnb_used))
- {
- // If BnB was used, it was the first pass. No longer the first pass and continue loop with knapsack.
- if (bnb_used) {
- coin_selection_params.use_bnb = false;
- continue;
- }
- else {
- error = _("Insufficient funds");
- return false;
- }
- }
- } else {
- bnb_used = false;
- }
-
- const CAmount nChange = nValueIn - nValueToSelect;
- if (nChange > 0)
- {
- // Fill a vout to ourself
- CTxOut newTxOut(nChange, scriptChange);
-
- // Never create dust outputs; if we would, just
- // add the dust to the fee.
- // The nChange when BnB is used is always going to go to fees.
- if (IsDust(newTxOut, coin_selection_params.m_discard_feerate) || bnb_used)
- {
- nChangePosInOut = -1;
- nFeeRet += nChange;
- }
- else
- {
- if (nChangePosInOut == -1)
- {
- // Insert change txn at random position:
- nChangePosInOut = GetRandInt(txNew.vout.size()+1);
- }
- else if ((unsigned int)nChangePosInOut > txNew.vout.size())
- {
- error = _("Change index out of range");
- return false;
- }
-
- std::vector<CTxOut>::iterator position = txNew.vout.begin()+nChangePosInOut;
- txNew.vout.insert(position, newTxOut);
- }
- } else {
- nChangePosInOut = -1;
- }
-
- // Dummy fill vin for maximum size estimation
- //
- for (const auto& coin : setCoins) {
- txNew.vin.push_back(CTxIn(coin.outpoint,CScript()));
- }
-
- tx_sizes = CalculateMaximumSignedTxSize(CTransaction(txNew), this, coin_control.fAllowWatchOnly);
- nBytes = tx_sizes.first;
- if (nBytes < 0) {
- error = _("Signing transaction failed");
- return false;
- }
-
- nFeeNeeded = coin_selection_params.m_effective_feerate.GetFee(nBytes);
- if (nFeeRet >= nFeeNeeded) {
- // Reduce fee to only the needed amount if possible. This
- // prevents potential overpayment in fees if the coins
- // selected to meet nFeeNeeded result in a transaction that
- // requires less fee than the prior iteration.
-
- // If we have no change and a big enough excess fee, then
- // try to construct transaction again only without picking
- // new inputs. We now know we only need the smaller fee
- // (because of reduced tx size) and so we should add a
- // change output. Only try this once.
- if (nChangePosInOut == -1 && nSubtractFeeFromAmount == 0 && pick_new_inputs) {
- unsigned int tx_size_with_change = nBytes + coin_selection_params.change_output_size + 2; // Add 2 as a buffer in case increasing # of outputs changes compact size
- CAmount fee_needed_with_change = coin_selection_params.m_effective_feerate.GetFee(tx_size_with_change);
- CAmount minimum_value_for_change = GetDustThreshold(change_prototype_txout, coin_selection_params.m_discard_feerate);
- if (nFeeRet >= fee_needed_with_change + minimum_value_for_change) {
- pick_new_inputs = false;
- nFeeRet = fee_needed_with_change;
- continue;
- }
- }
-
- // If we have change output already, just increase it
- if (nFeeRet > nFeeNeeded && nChangePosInOut != -1 && nSubtractFeeFromAmount == 0) {
- CAmount extraFeePaid = nFeeRet - nFeeNeeded;
- std::vector<CTxOut>::iterator change_position = txNew.vout.begin()+nChangePosInOut;
- change_position->nValue += extraFeePaid;
- nFeeRet -= extraFeePaid;
- }
- break; // Done, enough fee included.
- }
- else if (!pick_new_inputs) {
- // This shouldn't happen, we should have had enough excess
- // fee to pay for the new output and still meet nFeeNeeded
- // Or we should have just subtracted fee from recipients and
- // nFeeNeeded should not have changed
- error = _("Transaction fee and change calculation failed");
- return false;
- }
-
- // Try to reduce change to include necessary fee
- if (nChangePosInOut != -1 && nSubtractFeeFromAmount == 0) {
- CAmount additionalFeeNeeded = nFeeNeeded - nFeeRet;
- std::vector<CTxOut>::iterator change_position = txNew.vout.begin()+nChangePosInOut;
- // Only reduce change if remaining amount is still a large enough output.
- if (change_position->nValue >= MIN_FINAL_CHANGE + additionalFeeNeeded) {
- change_position->nValue -= additionalFeeNeeded;
- nFeeRet += additionalFeeNeeded;
- break; // Done, able to increase fee from change
- }
- }
-
- // If subtracting fee from recipients, we now know what fee we
- // need to subtract, we have no reason to reselect inputs
- if (nSubtractFeeFromAmount > 0) {
- pick_new_inputs = false;
- }
-
- // Include more fee and try again.
- nFeeRet = nFeeNeeded;
- coin_selection_params.use_bnb = false;
- continue;
- }
-
- // Give up if change keypool ran out and change is required
- if (scriptChange.empty() && nChangePosInOut != -1) {
- return false;
- }
- }
-
- // Shuffle selected coins and fill in final vin
- txNew.vin.clear();
- std::vector<CInputCoin> selected_coins(setCoins.begin(), setCoins.end());
- Shuffle(selected_coins.begin(), selected_coins.end(), FastRandomContext());
-
- // Note how the sequence number is set to non-maxint so that
- // the nLockTime set above actually works.
- //
- // BIP125 defines opt-in RBF as any nSequence < maxint-1, so
- // we use the highest possible value in that range (maxint-2)
- // to avoid conflicting with other possible uses of nSequence,
- // and in the spirit of "smallest possible change from prior
- // behavior."
- const uint32_t nSequence = coin_control.m_signal_bip125_rbf.value_or(m_signal_rbf) ? MAX_BIP125_RBF_SEQUENCE : (CTxIn::SEQUENCE_FINAL - 1);
- for (const auto& coin : selected_coins) {
- txNew.vin.push_back(CTxIn(coin.outpoint, CScript(), nSequence));
- }
-
- if (sign && !SignTransaction(txNew)) {
- error = _("Signing transaction failed");
- return false;
- }
-
- // Return the constructed transaction data.
- tx = MakeTransactionRef(std::move(txNew));
-
- // Limit size
- if ((sign && GetTransactionWeight(*tx) > MAX_STANDARD_TX_WEIGHT) ||
- (!sign && tx_sizes.second > MAX_STANDARD_TX_WEIGHT))
- {
- error = _("Transaction too large");
- return false;
- }
- }
-
- if (nFeeRet > m_default_max_tx_fee) {
- error = TransactionErrorString(TransactionError::MAX_FEE_EXCEEDED);
- return false;
- }
-
- if (gArgs.GetBoolArg("-walletrejectlongchains", DEFAULT_WALLET_REJECT_LONG_CHAINS)) {
- // Lastly, ensure this tx will pass the mempool's chain limits
- if (!chain().checkChainLimits(tx)) {
- error = _("Transaction has too long of a mempool chain");
- return false;
- }
- }
-
- // Before we return success, we assume any change key will be used to prevent
- // accidental re-use.
- reservedest.KeepDestination();
- fee_calc_out = feeCalc;
-
- 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,
- (feeCalc.est.pass.totalConfirmed + feeCalc.est.pass.inMempool + feeCalc.est.pass.leftMempool) > 0.0 ? 100 * feeCalc.est.pass.withinTarget / (feeCalc.est.pass.totalConfirmed + feeCalc.est.pass.inMempool + feeCalc.est.pass.leftMempool) : 0.0,
- feeCalc.est.pass.withinTarget, feeCalc.est.pass.totalConfirmed, feeCalc.est.pass.inMempool, feeCalc.est.pass.leftMempool,
- feeCalc.est.fail.start, feeCalc.est.fail.end,
- (feeCalc.est.fail.totalConfirmed + feeCalc.est.fail.inMempool + feeCalc.est.fail.leftMempool) > 0.0 ? 100 * feeCalc.est.fail.withinTarget / (feeCalc.est.fail.totalConfirmed + feeCalc.est.fail.inMempool + feeCalc.est.fail.leftMempool) : 0.0,
- feeCalc.est.fail.withinTarget, feeCalc.est.fail.totalConfirmed, feeCalc.est.fail.inMempool, feeCalc.est.fail.leftMempool);
- return true;
-}
-
-bool CWallet::CreateTransaction(
- const std::vector<CRecipient>& vecSend,
- CTransactionRef& tx,
- CAmount& nFeeRet,
- int& nChangePosInOut,
- bilingual_str& error,
- const CCoinControl& coin_control,
- FeeCalculation& fee_calc_out,
- bool sign)
-{
- int nChangePosIn = nChangePosInOut;
- Assert(!tx); // tx is an out-param. TODO change the return type from bool to tx (or nullptr)
- bool res = CreateTransactionInternal(vecSend, tx, nFeeRet, nChangePosInOut, error, coin_control, fee_calc_out, sign);
- // try with avoidpartialspends unless it's enabled already
- if (res && nFeeRet > 0 /* 0 means non-functional fee rate estimation */ && m_max_aps_fee > -1 && !coin_control.m_avoid_partial_spends) {
- CCoinControl tmp_cc = coin_control;
- tmp_cc.m_avoid_partial_spends = true;
- CAmount nFeeRet2;
- CTransactionRef tx2;
- int nChangePosInOut2 = nChangePosIn;
- bilingual_str error2; // fired and forgotten; if an error occurs, we discard the results
- if (CreateTransactionInternal(vecSend, tx2, nFeeRet2, nChangePosInOut2, error2, tmp_cc, fee_calc_out, sign)) {
- // if fee of this alternative one is within the range of the max fee, we use this one
- const bool use_aps = nFeeRet2 <= nFeeRet + m_max_aps_fee;
- WalletLogPrintf("Fee non-grouped = %lld, grouped = %lld, using %s\n", nFeeRet, nFeeRet2, use_aps ? "grouped" : "non-grouped");
- if (use_aps) {
- tx = tx2;
- nFeeRet = nFeeRet2;
- nChangePosInOut = nChangePosInOut2;
- }
- }
- }
- return res;
-}
-
void CWallet::CommitTransaction(CTransactionRef tx, mapValue_t mapValue, std::vector<std::pair<std::string, std::string>> orderForm)
{
LOCK(cs_wallet);
@@ -3228,7 +1959,7 @@ void CWallet::CommitTransaction(CTransactionRef tx, mapValue_t mapValue, std::ve
for (const CTxIn& txin : tx->vin) {
CWalletTx &coin = mapWallet.at(txin.prevout.hash);
coin.MarkDirty();
- NotifyTransactionChanged(this, coin.GetHash(), CT_UPDATED);
+ NotifyTransactionChanged(coin.GetHash(), CT_UPDATED);
}
// Get the inserted-CWalletTx from mapWallet so that the
@@ -3247,11 +1978,10 @@ void CWallet::CommitTransaction(CTransactionRef tx, mapValue_t mapValue, std::ve
}
}
-DBErrors CWallet::LoadWallet(bool& fFirstRunRet)
+DBErrors CWallet::LoadWallet()
{
LOCK(cs_wallet);
- fFirstRunRet = false;
DBErrors nLoadWalletRet = WalletBatch(GetDatabase()).LoadWallet(this);
if (nLoadWalletRet == DBErrors::NEED_REWRITE)
{
@@ -3263,9 +1993,7 @@ DBErrors CWallet::LoadWallet(bool& fFirstRunRet)
}
}
- // This wallet is in its first run if there are no ScriptPubKeyMans and it isn't blank or no privkeys
- fFirstRunRet = m_spk_managers.empty() && !IsWalletFlagSet(WALLET_FLAG_DISABLE_PRIVATE_KEYS) && !IsWalletFlagSet(WALLET_FLAG_BLANK_WALLET);
- if (fFirstRunRet) {
+ if (m_spk_managers.empty()) {
assert(m_external_spk_managers.empty());
assert(m_internal_spk_managers.empty());
}
@@ -3286,7 +2014,7 @@ DBErrors CWallet::ZapSelectTx(std::vector<uint256>& vHashIn, std::vector<uint256
for (const auto& txin : it->second.tx->vin)
mapTxSpends.erase(txin.prevout);
mapWallet.erase(it);
- NotifyTransactionChanged(this, hash, CT_DELETED);
+ NotifyTransactionChanged(hash, CT_DELETED);
}
if (nZapSelectTxRet == DBErrors::NEED_REWRITE)
@@ -3320,8 +2048,8 @@ bool CWallet::SetAddressBookWithDB(WalletBatch& batch, const CTxDestination& add
m_address_book[address].purpose = strPurpose;
is_mine = IsMine(address) != ISMINE_NO;
}
- NotifyAddressBookChanged(this, address, strName, is_mine,
- strPurpose, (fUpdated ? CT_UPDATED : CT_NEW) );
+ NotifyAddressBookChanged(address, strName, is_mine,
+ strPurpose, (fUpdated ? CT_UPDATED : CT_NEW));
if (!strPurpose.empty() && !batch.WritePurpose(EncodeDestination(address), strPurpose))
return false;
return batch.WriteName(EncodeDestination(address), strName);
@@ -3356,7 +2084,7 @@ bool CWallet::DelAddressBook(const CTxDestination& address)
is_mine = IsMine(address) != ISMINE_NO;
}
- NotifyAddressBookChanged(this, address, "", is_mine, "", CT_DELETED);
+ NotifyAddressBookChanged(address, "", is_mine, "", CT_DELETED);
batch.ErasePurpose(EncodeDestination(address));
return batch.EraseName(EncodeDestination(address));
@@ -3366,9 +2094,14 @@ size_t CWallet::KeypoolCountExternalKeys() const
{
AssertLockHeld(cs_wallet);
+ auto legacy_spk_man = GetLegacyScriptPubKeyMan();
+ if (legacy_spk_man) {
+ return legacy_spk_man->KeypoolCountExternalKeys();
+ }
+
unsigned int count = 0;
- for (auto spk_man : GetActiveScriptPubKeyMans()) {
- count += spk_man->KeypoolCountExternalKeys();
+ for (auto spk_man : m_external_spk_managers) {
+ count += spk_man.second->GetKeyPoolSize();
}
return count;
@@ -3405,7 +2138,7 @@ bool CWallet::GetNewDestination(const OutputType type, const std::string label,
spk_man->TopUp();
result = spk_man->GetNewDestination(type, dest, error);
} else {
- error = strprintf("Error: No %s addresses available.", FormatOutputType(type));
+ error = strprintf(_("Error: No %s addresses available."), FormatOutputType(type)).translated;
}
if (result) {
SetAddressBook(dest, label, "receive");
@@ -3420,8 +2153,7 @@ bool CWallet::GetNewChangeDestination(const OutputType type, CTxDestination& des
error.clear();
ReserveDestination reservedest(this, type);
- if (!reservedest.GetReservedDestination(dest, true)) {
- error = _("Error: Keypool ran out, please call keypoolrefill first").translated;
+ if (!reservedest.GetReservedDestination(dest, true, error)) {
return false;
}
@@ -3453,137 +2185,6 @@ void CWallet::MarkDestinationsDirty(const std::set<CTxDestination>& destinations
}
}
-std::map<CTxDestination, CAmount> CWallet::GetAddressBalances() const
-{
- std::map<CTxDestination, CAmount> balances;
-
- {
- LOCK(cs_wallet);
- std::set<uint256> trusted_parents;
- for (const auto& walletEntry : mapWallet)
- {
- const CWalletTx& wtx = walletEntry.second;
-
- if (!IsTrusted(wtx, trusted_parents))
- continue;
-
- if (wtx.IsImmatureCoinBase())
- continue;
-
- int nDepth = wtx.GetDepthInMainChain();
- if (nDepth < (wtx.IsFromMe(ISMINE_ALL) ? 0 : 1))
- continue;
-
- for (unsigned int i = 0; i < wtx.tx->vout.size(); i++)
- {
- CTxDestination addr;
- if (!IsMine(wtx.tx->vout[i]))
- continue;
- if(!ExtractDestination(wtx.tx->vout[i].scriptPubKey, addr))
- continue;
-
- CAmount n = IsSpent(walletEntry.first, i) ? 0 : wtx.tx->vout[i].nValue;
- balances[addr] += n;
- }
- }
- }
-
- return balances;
-}
-
-std::set< std::set<CTxDestination> > CWallet::GetAddressGroupings() const
-{
- AssertLockHeld(cs_wallet);
- std::set< std::set<CTxDestination> > groupings;
- std::set<CTxDestination> grouping;
-
- for (const auto& walletEntry : mapWallet)
- {
- const CWalletTx& wtx = walletEntry.second;
-
- if (wtx.tx->vin.size() > 0)
- {
- bool any_mine = false;
- // group all input addresses with each other
- for (const CTxIn& txin : wtx.tx->vin)
- {
- CTxDestination address;
- if(!IsMine(txin)) /* If this input isn't mine, ignore it */
- continue;
- if(!ExtractDestination(mapWallet.at(txin.prevout.hash).tx->vout[txin.prevout.n].scriptPubKey, address))
- continue;
- grouping.insert(address);
- any_mine = true;
- }
-
- // group change with input addresses
- if (any_mine)
- {
- for (const CTxOut& txout : wtx.tx->vout)
- if (IsChange(txout))
- {
- CTxDestination txoutAddr;
- if(!ExtractDestination(txout.scriptPubKey, txoutAddr))
- continue;
- grouping.insert(txoutAddr);
- }
- }
- if (grouping.size() > 0)
- {
- groupings.insert(grouping);
- grouping.clear();
- }
- }
-
- // group lone addrs by themselves
- for (const auto& txout : wtx.tx->vout)
- if (IsMine(txout))
- {
- CTxDestination address;
- if(!ExtractDestination(txout.scriptPubKey, address))
- continue;
- grouping.insert(address);
- groupings.insert(grouping);
- grouping.clear();
- }
- }
-
- std::set< std::set<CTxDestination>* > uniqueGroupings; // a set of pointers to groups of addresses
- std::map< CTxDestination, std::set<CTxDestination>* > setmap; // map addresses to the unique group containing it
- for (std::set<CTxDestination> _grouping : groupings)
- {
- // make a set of all the groups hit by this new group
- std::set< std::set<CTxDestination>* > hits;
- std::map< CTxDestination, std::set<CTxDestination>* >::iterator it;
- for (const CTxDestination& address : _grouping)
- if ((it = setmap.find(address)) != setmap.end())
- hits.insert((*it).second);
-
- // merge all hit groups into a new single group and delete old groups
- std::set<CTxDestination>* merged = new std::set<CTxDestination>(_grouping);
- for (std::set<CTxDestination>* hit : hits)
- {
- merged->insert(hit->begin(), hit->end());
- uniqueGroupings.erase(hit);
- delete hit;
- }
- uniqueGroupings.insert(merged);
-
- // update setmap
- for (const CTxDestination& element : *merged)
- setmap[element] = merged;
- }
-
- std::set< std::set<CTxDestination> > ret;
- for (const std::set<CTxDestination>* uniqueGrouping : uniqueGroupings)
- {
- ret.insert(*uniqueGrouping);
- delete uniqueGrouping;
- }
-
- return ret;
-}
-
std::set<CTxDestination> CWallet::GetLabelAddresses(const std::string& label) const
{
LOCK(cs_wallet);
@@ -3599,10 +2200,11 @@ std::set<CTxDestination> CWallet::GetLabelAddresses(const std::string& label) co
return result;
}
-bool ReserveDestination::GetReservedDestination(CTxDestination& dest, bool internal)
+bool ReserveDestination::GetReservedDestination(CTxDestination& dest, bool internal, std::string& error)
{
m_spk_man = pwallet->GetScriptPubKeyMan(type, internal);
if (!m_spk_man) {
+ error = strprintf(_("Error: No %s addresses available."), FormatOutputType(type)).translated;
return false;
}
@@ -3612,7 +2214,7 @@ bool ReserveDestination::GetReservedDestination(CTxDestination& dest, bool inter
m_spk_man->TopUp();
CKeyPool keypool;
- if (!m_spk_man->GetReservedDestination(type, internal, address, nIndex, keypool)) {
+ if (!m_spk_man->GetReservedDestination(type, internal, address, nIndex, keypool, error)) {
return false;
}
fInternal = keypool.fInternal;
@@ -3641,7 +2243,6 @@ void ReserveDestination::ReturnDestination()
bool CWallet::DisplayAddress(const CTxDestination& dest)
{
-#ifdef ENABLE_EXTERNAL_SIGNER
CScript scriptPubKey = GetScriptForDestination(dest);
const auto spk_man = GetScriptPubKeyMan(scriptPubKey);
if (spk_man == nullptr) {
@@ -3653,9 +2254,6 @@ bool CWallet::DisplayAddress(const CTxDestination& dest)
}
ExternalSigner signer = ExternalSignerScriptPubKeyMan::GetExternalSigner();
return signer_spk_man->DisplayAddress(scriptPubKey, signer);
-#else
- return false;
-#endif
}
void CWallet::LockCoin(const COutPoint& output)
@@ -3700,44 +2298,48 @@ void CWallet::GetKeyBirthTimes(std::map<CKeyID, int64_t>& mapKeyBirth) const {
AssertLockHeld(cs_wallet);
mapKeyBirth.clear();
- LegacyScriptPubKeyMan* spk_man = GetLegacyScriptPubKeyMan();
- assert(spk_man != nullptr);
- LOCK(spk_man->cs_KeyStore);
-
- // get birth times for keys with metadata
- for (const auto& entry : spk_man->mapKeyMetadata) {
- if (entry.second.nCreateTime) {
- mapKeyBirth[entry.first] = entry.second.nCreateTime;
- }
- }
-
// map in which we'll infer heights of other keys
std::map<CKeyID, const CWalletTx::Confirmation*> mapKeyFirstBlock;
CWalletTx::Confirmation max_confirm;
max_confirm.block_height = GetLastBlockHeight() > 144 ? GetLastBlockHeight() - 144 : 0; // the tip can be reorganized; use a 144-block safety margin
CHECK_NONFATAL(chain().findAncestorByHeight(GetLastBlockHash(), max_confirm.block_height, FoundBlock().hash(max_confirm.hashBlock)));
- for (const CKeyID &keyid : spk_man->GetKeys()) {
- if (mapKeyBirth.count(keyid) == 0)
- mapKeyFirstBlock[keyid] = &max_confirm;
- }
- // if there are no such keys, we're done
- if (mapKeyFirstBlock.empty())
- return;
+ {
+ LegacyScriptPubKeyMan* spk_man = GetLegacyScriptPubKeyMan();
+ assert(spk_man != nullptr);
+ LOCK(spk_man->cs_KeyStore);
- // find first block that affects those keys, if there are any left
- for (const auto& entry : mapWallet) {
- // iterate over all wallet transactions...
- const CWalletTx &wtx = entry.second;
- if (wtx.m_confirm.status == CWalletTx::CONFIRMED) {
- // ... which are already in a block
- for (const CTxOut &txout : wtx.tx->vout) {
- // iterate over all their outputs
- for (const auto &keyid : GetAffectedKeys(txout.scriptPubKey, *spk_man)) {
- // ... and all their affected keys
- auto rit = mapKeyFirstBlock.find(keyid);
- if (rit != mapKeyFirstBlock.end() && wtx.m_confirm.block_height < rit->second->block_height) {
- rit->second = &wtx.m_confirm;
+ // get birth times for keys with metadata
+ for (const auto& entry : spk_man->mapKeyMetadata) {
+ if (entry.second.nCreateTime) {
+ mapKeyBirth[entry.first] = entry.second.nCreateTime;
+ }
+ }
+
+ // Prepare to infer birth heights for keys without metadata
+ for (const CKeyID &keyid : spk_man->GetKeys()) {
+ if (mapKeyBirth.count(keyid) == 0)
+ mapKeyFirstBlock[keyid] = &max_confirm;
+ }
+
+ // if there are no such keys, we're done
+ if (mapKeyFirstBlock.empty())
+ return;
+
+ // find first block that affects those keys, if there are any left
+ for (const auto& entry : mapWallet) {
+ // iterate over all wallet transactions...
+ const CWalletTx &wtx = entry.second;
+ if (wtx.m_confirm.status == CWalletTx::CONFIRMED) {
+ // ... which are already in a block
+ for (const CTxOut &txout : wtx.tx->vout) {
+ // iterate over all their outputs
+ for (const auto &keyid : GetAffectedKeys(txout.scriptPubKey, *spk_man)) {
+ // ... and all their affected keys
+ auto rit = mapKeyFirstBlock.find(keyid);
+ if (rit != mapKeyFirstBlock.end() && wtx.m_confirm.block_height < rit->second->block_height) {
+ rit->second = &wtx.m_confirm;
+ }
}
}
}
@@ -3812,45 +2414,45 @@ unsigned int CWallet::ComputeTimeSmart(const CWalletTx& wtx) const
return nTimeSmart;
}
-bool CWallet::AddDestData(WalletBatch& batch, const CTxDestination &dest, const std::string &key, const std::string &value)
+bool CWallet::SetAddressUsed(WalletBatch& batch, const CTxDestination& dest, bool used)
{
+ const std::string key{"used"};
if (std::get_if<CNoDestination>(&dest))
return false;
+ if (!used) {
+ if (auto* data = util::FindKey(m_address_book, dest)) data->destdata.erase(key);
+ return batch.EraseDestData(EncodeDestination(dest), key);
+ }
+
+ const std::string value{"1"};
m_address_book[dest].destdata.insert(std::make_pair(key, value));
return batch.WriteDestData(EncodeDestination(dest), key, value);
}
-bool CWallet::EraseDestData(WalletBatch& batch, const CTxDestination &dest, const std::string &key)
-{
- if (!m_address_book[dest].destdata.erase(key))
- return false;
- return batch.EraseDestData(EncodeDestination(dest), key);
-}
-
void CWallet::LoadDestData(const CTxDestination &dest, const std::string &key, const std::string &value)
{
m_address_book[dest].destdata.insert(std::make_pair(key, value));
}
-bool CWallet::GetDestData(const CTxDestination &dest, const std::string &key, std::string *value) const
+bool CWallet::IsAddressUsed(const CTxDestination& dest) const
{
+ const std::string key{"used"};
std::map<CTxDestination, CAddressBookData>::const_iterator i = m_address_book.find(dest);
if(i != m_address_book.end())
{
CAddressBookData::StringMap::const_iterator j = i->second.destdata.find(key);
if(j != i->second.destdata.end())
{
- if(value)
- *value = j->second;
return true;
}
}
return false;
}
-std::vector<std::string> CWallet::GetDestValues(const std::string& prefix) const
+std::vector<std::string> CWallet::GetAddressReceiveRequests() const
{
+ const std::string prefix{"rr"};
std::vector<std::string> values;
for (const auto& address : m_address_book) {
for (const auto& data : address.second.destdata) {
@@ -3862,6 +2464,20 @@ std::vector<std::string> CWallet::GetDestValues(const std::string& prefix) const
return values;
}
+bool CWallet::SetAddressReceiveRequest(WalletBatch& batch, const CTxDestination& dest, const std::string& id, const std::string& value)
+{
+ const std::string key{"rr" + id}; // "rr" prefix = "receive request" in destdata
+ CAddressBookData& data = m_address_book.at(dest);
+ if (value.empty()) {
+ if (!batch.EraseDestData(EncodeDestination(dest), key)) return false;
+ data.destdata.erase(key);
+ } else {
+ if (!batch.WriteDestData(EncodeDestination(dest), key, value)) return false;
+ data.destdata[key] = value;
+ }
+ return true;
+}
+
std::unique_ptr<WalletDatabase> MakeWalletDatabase(const std::string& name, const DatabaseOptions& options, DatabaseStatus& status, bilingual_str& error_string)
{
// Do some checking on wallet path. It should be either a:
@@ -3886,18 +2502,15 @@ std::unique_ptr<WalletDatabase> MakeWalletDatabase(const std::string& name, cons
return MakeDatabase(wallet_path, options, status, error_string);
}
-std::shared_ptr<CWallet> CWallet::Create(interfaces::Chain& chain, const std::string& name, std::unique_ptr<WalletDatabase> database, uint64_t wallet_creation_flags, bilingual_str& error, std::vector<bilingual_str>& warnings)
+std::shared_ptr<CWallet> CWallet::Create(interfaces::Chain* chain, const std::string& name, std::unique_ptr<WalletDatabase> database, uint64_t wallet_creation_flags, bilingual_str& error, std::vector<bilingual_str>& warnings)
{
const std::string& walletFile = database->Filename();
- chain.initMessage(_("Loading wallet…").translated);
-
int64_t nStart = GetTimeMillis();
- bool fFirstRun = true;
// TODO: Can't use std::make_shared because we need a custom deleter but
// should be possible to use std::allocate_shared.
- std::shared_ptr<CWallet> walletInstance(new CWallet(&chain, name, std::move(database)), ReleaseWallet);
- DBErrors nLoadWalletRet = walletInstance->LoadWallet(fFirstRun);
+ std::shared_ptr<CWallet> walletInstance(new CWallet(chain, name, std::move(database)), ReleaseWallet);
+ DBErrors nLoadWalletRet = walletInstance->LoadWallet();
if (nLoadWalletRet != DBErrors::LOAD_OK) {
if (nLoadWalletRet == DBErrors::CORRUPT) {
error = strprintf(_("Error loading %s: Wallet corrupted"), walletFile);
@@ -3924,6 +2537,10 @@ std::shared_ptr<CWallet> CWallet::Create(interfaces::Chain& chain, const std::st
}
}
+ // This wallet is in its first run if there are no ScriptPubKeyMans and it isn't blank or no privkeys
+ const bool fFirstRun = walletInstance->m_spk_managers.empty() &&
+ !walletInstance->IsWalletFlagSet(WALLET_FLAG_DISABLE_PRIVATE_KEYS) &&
+ !walletInstance->IsWalletFlagSet(WALLET_FLAG_BLANK_WALLET);
if (fFirstRun)
{
// ensure this wallet.dat can only be opened by clients supporting HD with chain split and expects no default key
@@ -3952,7 +2569,9 @@ std::shared_ptr<CWallet> CWallet::Create(interfaces::Chain& chain, const std::st
}
}
- walletInstance->chainStateFlushed(chain.getTipLocator());
+ if (chain) {
+ walletInstance->chainStateFlushed(chain->getTipLocator());
+ }
} else if (wallet_creation_flags & WALLET_FLAG_DISABLE_PRIVATE_KEYS) {
// Make it impossible to disable private keys after creation
error = strprintf(_("Error loading %s: Private keys can only be disabled during creation"), walletFile);
@@ -4049,9 +2668,9 @@ std::shared_ptr<CWallet> CWallet::Create(interfaces::Chain& chain, const std::st
_("This is the transaction fee you will pay if you send a transaction."));
}
walletInstance->m_pay_tx_fee = CFeeRate(nFeePerK, 1000);
- if (walletInstance->m_pay_tx_fee < chain.relayMinFee()) {
+ if (chain && walletInstance->m_pay_tx_fee < chain->relayMinFee()) {
error = strprintf(_("Invalid amount for -paytxfee=<amount>: '%s' (must be at least %s)"),
- gArgs.GetArg("-paytxfee", ""), chain.relayMinFee().ToString());
+ gArgs.GetArg("-paytxfee", ""), chain->relayMinFee().ToString());
return nullptr;
}
}
@@ -4065,15 +2684,15 @@ std::shared_ptr<CWallet> CWallet::Create(interfaces::Chain& chain, const std::st
if (nMaxFee > HIGH_MAX_TX_FEE) {
warnings.push_back(_("-maxtxfee is set very high! Fees this large could be paid on a single transaction."));
}
- if (CFeeRate(nMaxFee, 1000) < chain.relayMinFee()) {
+ if (chain && CFeeRate(nMaxFee, 1000) < chain->relayMinFee()) {
error = strprintf(_("Invalid amount for -maxtxfee=<amount>: '%s' (must be at least the minrelay fee of %s to prevent stuck transactions)"),
- gArgs.GetArg("-maxtxfee", ""), chain.relayMinFee().ToString());
+ gArgs.GetArg("-maxtxfee", ""), chain->relayMinFee().ToString());
return nullptr;
}
walletInstance->m_default_max_tx_fee = nMaxFee;
}
- if (chain.relayMinFee().GetFeePerK() > HIGH_TX_FEE_PER_KB) {
+ if (chain && chain->relayMinFee().GetFeePerK() > HIGH_TX_FEE_PER_KB) {
warnings.push_back(AmountHighWarn("-minrelaytxfee") + Untranslated(" ") +
_("The wallet will avoid paying less than the minimum relay fee."));
}
@@ -4089,6 +2708,35 @@ std::shared_ptr<CWallet> CWallet::Create(interfaces::Chain& chain, const std::st
LOCK(walletInstance->cs_wallet);
+ if (chain && !AttachChain(walletInstance, *chain, error, warnings)) {
+ return nullptr;
+ }
+
+ {
+ LOCK(cs_wallets);
+ for (auto& load_wallet : g_load_wallet_fns) {
+ load_wallet(interfaces::MakeWallet(walletInstance));
+ }
+ }
+
+ walletInstance->SetBroadcastTransactions(gArgs.GetBoolArg("-walletbroadcast", DEFAULT_WALLETBROADCAST));
+
+ {
+ walletInstance->WalletLogPrintf("setKeyPool.size() = %u\n", walletInstance->GetKeyPoolSize());
+ walletInstance->WalletLogPrintf("mapWallet.size() = %u\n", walletInstance->mapWallet.size());
+ walletInstance->WalletLogPrintf("m_address_book.size() = %u\n", walletInstance->m_address_book.size());
+ }
+
+ return walletInstance;
+}
+
+bool CWallet::AttachChain(const std::shared_ptr<CWallet>& walletInstance, interfaces::Chain& chain, bilingual_str& error, std::vector<bilingual_str>& warnings)
+{
+ LOCK(walletInstance->cs_wallet);
+ // allow setting the chain if it hasn't been set already but prevent changing it
+ assert(!walletInstance->m_chain || walletInstance->m_chain == &chain);
+ walletInstance->m_chain = &chain;
+
// Register wallet with validationinterface. It's done before rescan to avoid
// missing block connections between end of rescan and validation subscribing.
// Because of wallet lock being hold, block connection notifications are going to
@@ -4122,21 +2770,21 @@ std::shared_ptr<CWallet> CWallet::Create(interfaces::Chain& chain, const std::st
if (tip_height && *tip_height != rescan_height)
{
- // We can't rescan beyond non-pruned blocks, stop and throw an error.
- // This might happen if a user uses an old wallet within a pruned node
- // or if they ran -disablewallet for a longer time, then decided to re-enable
if (chain.havePruned()) {
- // Exit early and print an error.
- // If a block is pruned after this check, we will load the wallet,
- // but fail the rescan with a generic error.
int block_height = *tip_height;
while (block_height > 0 && chain.haveBlockOnDisk(block_height - 1) && rescan_height != block_height) {
--block_height;
}
if (rescan_height != block_height) {
+ // We can't rescan beyond non-pruned blocks, stop and throw an error.
+ // This might happen if a user uses an old wallet within a pruned node
+ // or if they ran -disablewallet for a longer time, then decided to re-enable
+ // Exit early and print an error.
+ // If a block is pruned after this check, we will load the wallet,
+ // but fail the rescan with a generic error.
error = _("Prune: last wallet synchronisation goes beyond pruned data. You need to -reindex (download the whole blockchain again in case of pruned node)");
- return nullptr;
+ return false;
}
}
@@ -4158,29 +2806,14 @@ std::shared_ptr<CWallet> CWallet::Create(interfaces::Chain& chain, const std::st
WalletRescanReserver reserver(*walletInstance);
if (!reserver.reserve() || (ScanResult::SUCCESS != walletInstance->ScanForWalletTransactions(chain.getBlockHash(rescan_height), rescan_height, {} /* max height */, reserver, true /* update */).status)) {
error = _("Failed to rescan the wallet during initialization");
- return nullptr;
+ return false;
}
}
walletInstance->chainStateFlushed(chain.getTipLocator());
walletInstance->GetDatabase().IncrementUpdateCounter();
}
- {
- LOCK(cs_wallets);
- for (auto& load_wallet : g_load_wallet_fns) {
- load_wallet(interfaces::MakeWallet(walletInstance));
- }
- }
-
- walletInstance->SetBroadcastTransactions(gArgs.GetBoolArg("-walletbroadcast", DEFAULT_WALLETBROADCAST));
-
- {
- walletInstance->WalletLogPrintf("setKeyPool.size() = %u\n", walletInstance->GetKeyPoolSize());
- walletInstance->WalletLogPrintf("mapWallet.size() = %u\n", walletInstance->mapWallet.size());
- walletInstance->WalletLogPrintf("m_address_book.size() = %u\n", walletInstance->m_address_book.size());
- }
-
- return walletInstance;
+ return true;
}
const CAddressBookData* CWallet::FindAddressBookEntry(const CTxDestination& dest, bool allow_change) const
@@ -4282,92 +2915,6 @@ bool CWalletTx::IsImmatureCoinBase() const
return GetBlocksToMaturity() > 0;
}
-std::vector<OutputGroup> CWallet::GroupOutputs(const std::vector<COutput>& outputs, bool separate_coins, const CFeeRate& effective_feerate, const CFeeRate& long_term_feerate, const CoinEligibilityFilter& filter, bool positive_only) const
-{
- std::vector<OutputGroup> groups_out;
-
- if (separate_coins) {
- // Single coin means no grouping. Each COutput gets its own OutputGroup.
- for (const COutput& output : outputs) {
- // Skip outputs we cannot spend
- if (!output.fSpendable) continue;
-
- size_t ancestors, descendants;
- chain().getTransactionAncestry(output.tx->GetHash(), ancestors, descendants);
- CInputCoin input_coin = output.GetInputCoin();
-
- // Make an OutputGroup containing just this output
- OutputGroup group{effective_feerate, long_term_feerate};
- group.Insert(input_coin, output.nDepth, output.tx->IsFromMe(ISMINE_ALL), ancestors, descendants, positive_only);
-
- // Check the OutputGroup's eligibility. Only add the eligible ones.
- if (positive_only && group.effective_value <= 0) continue;
- if (group.m_outputs.size() > 0 && group.EligibleForSpending(filter)) groups_out.push_back(group);
- }
- return groups_out;
- }
-
- // We want to combine COutputs that have the same scriptPubKey into single OutputGroups
- // except when there are more than OUTPUT_GROUP_MAX_ENTRIES COutputs grouped in an OutputGroup.
- // To do this, we maintain a map where the key is the scriptPubKey and the value is a vector of OutputGroups.
- // For each COutput, we check if the scriptPubKey is in the map, and if it is, the COutput's CInputCoin is added
- // to the last OutputGroup in the vector for the scriptPubKey. When the last OutputGroup has
- // OUTPUT_GROUP_MAX_ENTRIES CInputCoins, a new OutputGroup is added to the end of the vector.
- std::map<CScript, std::vector<OutputGroup>> spk_to_groups_map;
- for (const auto& output : outputs) {
- // Skip outputs we cannot spend
- if (!output.fSpendable) continue;
-
- size_t ancestors, descendants;
- chain().getTransactionAncestry(output.tx->GetHash(), ancestors, descendants);
- CInputCoin input_coin = output.GetInputCoin();
- CScript spk = input_coin.txout.scriptPubKey;
-
- std::vector<OutputGroup>& groups = spk_to_groups_map[spk];
-
- if (groups.size() == 0) {
- // No OutputGroups for this scriptPubKey yet, add one
- groups.emplace_back(effective_feerate, long_term_feerate);
- }
-
- // Get the last OutputGroup in the vector so that we can add the CInputCoin to it
- // A pointer is used here so that group can be reassigned later if it is full.
- OutputGroup* group = &groups.back();
-
- // Check if this OutputGroup is full. We limit to OUTPUT_GROUP_MAX_ENTRIES when using -avoidpartialspends
- // to avoid surprising users with very high fees.
- if (group->m_outputs.size() >= OUTPUT_GROUP_MAX_ENTRIES) {
- // The last output group is full, add a new group to the vector and use that group for the insertion
- groups.emplace_back(effective_feerate, long_term_feerate);
- group = &groups.back();
- }
-
- // Add the input_coin to group
- group->Insert(input_coin, output.nDepth, output.tx->IsFromMe(ISMINE_ALL), ancestors, descendants, positive_only);
- }
-
- // Now we go through the entire map and pull out the OutputGroups
- for (const auto& spk_and_groups_pair: spk_to_groups_map) {
- const std::vector<OutputGroup>& groups_per_spk= spk_and_groups_pair.second;
-
- // Go through the vector backwards. This allows for the first item we deal with being the partial group.
- for (auto group_it = groups_per_spk.rbegin(); group_it != groups_per_spk.rend(); group_it++) {
- const OutputGroup& group = *group_it;
-
- // Don't include partial groups if there are full groups too and we don't want partial groups
- if (group_it == groups_per_spk.rbegin() && groups_per_spk.size() > 1 && !filter.m_include_partial_groups) {
- continue;
- }
-
- // Check the OutputGroup's eligibility. Only add the eligible ones.
- if (positive_only && group.effective_value <= 0) continue;
- if (group.m_outputs.size() > 0 && group.EligibleForSpending(filter)) groups_out.push_back(group);
- }
- }
-
- return groups_out;
-}
-
bool CWallet::IsCrypted() const
{
return HasEncryptionKeys();
@@ -4439,7 +2986,6 @@ ScriptPubKeyMan* CWallet::GetScriptPubKeyMan(const OutputType& type, bool intern
const std::map<OutputType, ScriptPubKeyMan*>& spk_managers = internal ? m_internal_spk_managers : m_external_spk_managers;
std::map<OutputType, ScriptPubKeyMan*>::const_iterator it = spk_managers.find(type);
if (it == spk_managers.end()) {
- WalletLogPrintf("%s scriptPubKey Manager for output type %d does not exist\n", internal ? "Internal" : "External", static_cast<int>(type));
return nullptr;
}
return it->second;
@@ -4516,7 +3062,7 @@ void CWallet::SetupLegacyScriptPubKeyMan()
}
auto spk_manager = std::unique_ptr<ScriptPubKeyMan>(new LegacyScriptPubKeyMan(*this));
- for (const auto& type : OUTPUT_TYPES) {
+ for (const auto& type : LEGACY_OUTPUT_TYPES) {
m_internal_spk_managers[type] = spk_manager.get();
m_external_spk_managers[type] = spk_manager.get();
}
@@ -4544,12 +3090,8 @@ void CWallet::ConnectScriptPubKeyManNotifiers()
void CWallet::LoadDescriptorScriptPubKeyMan(uint256 id, WalletDescriptor& desc)
{
if (IsWalletFlagSet(WALLET_FLAG_EXTERNAL_SIGNER)) {
-#ifdef ENABLE_EXTERNAL_SIGNER
auto spk_manager = std::unique_ptr<ScriptPubKeyMan>(new ExternalSignerScriptPubKeyMan(*this, desc));
m_spk_managers[id] = std::move(spk_manager);
-#else
- throw std::runtime_error(std::string(__func__) + ": Compiled without external signing support (required for external signing)");
-#endif
} else {
auto spk_manager = std::unique_ptr<ScriptPubKeyMan>(new DescriptorScriptPubKeyMan(*this, desc));
m_spk_managers[id] = std::move(spk_manager);
@@ -4573,7 +3115,12 @@ void CWallet::SetupDescriptorScriptPubKeyMans()
for (bool internal : {false, true}) {
for (OutputType t : OUTPUT_TYPES) {
- auto spk_manager = std::unique_ptr<DescriptorScriptPubKeyMan>(new DescriptorScriptPubKeyMan(*this, internal));
+ if (t == OutputType::BECH32M) {
+ // Skip taproot (bech32m) for now
+ // TODO: Setup taproot (bech32m) descriptors by default
+ continue;
+ }
+ auto spk_manager = std::unique_ptr<DescriptorScriptPubKeyMan>(new DescriptorScriptPubKeyMan(*this));
if (IsCrypted()) {
if (IsLocked()) {
throw std::runtime_error(std::string(__func__) + ": Wallet is locked, cannot setup new descriptors");
@@ -4582,14 +3129,13 @@ void CWallet::SetupDescriptorScriptPubKeyMans()
throw std::runtime_error(std::string(__func__) + ": Could not encrypt new descriptors");
}
}
- spk_manager->SetupDescriptorGeneration(master_key, t);
+ spk_manager->SetupDescriptorGeneration(master_key, t, internal);
uint256 id = spk_manager->GetID();
m_spk_managers[id] = std::move(spk_manager);
AddActiveScriptPubKeyMan(id, t, internal);
}
}
} else {
-#ifdef ENABLE_EXTERNAL_SIGNER
ExternalSigner signer = ExternalSignerScriptPubKeyMan::GetExternalSigner();
// TODO: add account parameter
@@ -4609,16 +3155,13 @@ void CWallet::SetupDescriptorScriptPubKeyMans()
continue;
}
OutputType t = *desc->GetOutputType();
- auto spk_manager = std::unique_ptr<ExternalSignerScriptPubKeyMan>(new ExternalSignerScriptPubKeyMan(*this, internal));
+ auto spk_manager = std::unique_ptr<ExternalSignerScriptPubKeyMan>(new ExternalSignerScriptPubKeyMan(*this));
spk_manager->SetupDescriptor(std::move(desc));
uint256 id = spk_manager->GetID();
m_spk_managers[id] = std::move(spk_manager);
AddActiveScriptPubKeyMan(id, t, internal);
}
}
-#else
- throw std::runtime_error(std::string(__func__) + ": Compiled without external signing support (required for external signing)");
-#endif // ENABLE_EXTERNAL_SIGNER
}
}
@@ -4633,12 +3176,37 @@ void CWallet::AddActiveScriptPubKeyMan(uint256 id, OutputType type, bool interna
void CWallet::LoadActiveScriptPubKeyMan(uint256 id, OutputType type, bool internal)
{
+ // Activating ScriptPubKeyManager for a given output and change type is incompatible with legacy wallets.
+ // Legacy wallets have only one ScriptPubKeyManager and it's active for all output and change types.
+ Assert(IsWalletFlagSet(WALLET_FLAG_DESCRIPTORS));
+
WalletLogPrintf("Setting spkMan to active: id = %s, type = %d, internal = %d\n", id.ToString(), static_cast<int>(type), static_cast<int>(internal));
auto& spk_mans = internal ? m_internal_spk_managers : m_external_spk_managers;
+ auto& spk_mans_other = internal ? m_external_spk_managers : m_internal_spk_managers;
auto spk_man = m_spk_managers.at(id).get();
- spk_man->SetInternal(internal);
spk_mans[type] = spk_man;
+ if (spk_mans_other[type] == spk_man) {
+ spk_mans_other.erase(type);
+ }
+
+ NotifyCanGetAddressesChanged();
+}
+
+void CWallet::DeactivateScriptPubKeyMan(uint256 id, OutputType type, bool internal)
+{
+ auto spk_man = GetScriptPubKeyMan(type, internal);
+ if (spk_man != nullptr && spk_man->GetID() == id) {
+ WalletLogPrintf("Deactivate spkMan: id = %s, type = %d, internal = %d\n", id.ToString(), static_cast<int>(type), static_cast<int>(internal));
+ WalletBatch batch(GetDatabase());
+ if (!batch.EraseActiveScriptPubKeyMan(static_cast<uint8_t>(type), internal)) {
+ throw std::runtime_error(std::string(__func__) + ": erasing active ScriptPubKeyMan id failed");
+ }
+
+ auto& spk_mans = internal ? m_internal_spk_managers : m_external_spk_managers;
+ spk_mans.erase(type);
+ }
+
NotifyCanGetAddressesChanged();
}
@@ -4672,44 +3240,26 @@ ScriptPubKeyMan* CWallet::AddWalletDescriptor(WalletDescriptor& desc, const Flat
}
LOCK(cs_wallet);
- auto new_spk_man = std::unique_ptr<DescriptorScriptPubKeyMan>(new DescriptorScriptPubKeyMan(*this, desc));
-
- // If we already have this descriptor, remove it from the maps but add the existing cache to desc
- auto old_spk_man = GetDescriptorScriptPubKeyMan(desc);
- if (old_spk_man) {
+ auto spk_man = GetDescriptorScriptPubKeyMan(desc);
+ if (spk_man) {
WalletLogPrintf("Update existing descriptor: %s\n", desc.descriptor->ToString());
+ spk_man->UpdateWalletDescriptor(desc);
+ } else {
+ auto new_spk_man = std::unique_ptr<DescriptorScriptPubKeyMan>(new DescriptorScriptPubKeyMan(*this, desc));
+ spk_man = new_spk_man.get();
- {
- LOCK(old_spk_man->cs_desc_man);
- new_spk_man->SetCache(old_spk_man->GetWalletDescriptor().cache);
- }
-
- // Remove from maps of active spkMans
- auto old_spk_man_id = old_spk_man->GetID();
- for (bool internal : {false, true}) {
- for (OutputType t : OUTPUT_TYPES) {
- auto active_spk_man = GetScriptPubKeyMan(t, internal);
- if (active_spk_man && active_spk_man->GetID() == old_spk_man_id) {
- if (internal) {
- m_internal_spk_managers.erase(t);
- } else {
- m_external_spk_managers.erase(t);
- }
- break;
- }
- }
- }
- m_spk_managers.erase(old_spk_man_id);
+ // Save the descriptor to memory
+ m_spk_managers[new_spk_man->GetID()] = std::move(new_spk_man);
}
// Add the private keys to the descriptor
for (const auto& entry : signing_provider.keys) {
const CKey& key = entry.second;
- new_spk_man->AddDescriptorKey(key, key.GetPubKey());
+ spk_man->AddDescriptorKey(key, key.GetPubKey());
}
// Top up key pool, the manager will generate new scriptPubKeys internally
- if (!new_spk_man->TopUp()) {
+ if (!spk_man->TopUp()) {
WalletLogPrintf("Could not top up scriptPubKeys\n");
return nullptr;
}
@@ -4717,7 +3267,7 @@ ScriptPubKeyMan* CWallet::AddWalletDescriptor(WalletDescriptor& desc, const Flat
// Apply the label if necessary
// Note: we disable labels for ranged descriptors
if (!desc.descriptor->IsRange()) {
- auto script_pub_keys = new_spk_man->GetScriptPubKeys();
+ auto script_pub_keys = spk_man->GetScriptPubKeys();
if (script_pub_keys.empty()) {
WalletLogPrintf("Could not generate scriptPubKeys (cache is empty)\n");
return nullptr;
@@ -4729,12 +3279,8 @@ ScriptPubKeyMan* CWallet::AddWalletDescriptor(WalletDescriptor& desc, const Flat
}
}
- // Save the descriptor to memory
- auto ret = new_spk_man.get();
- m_spk_managers[new_spk_man->GetID()] = std::move(new_spk_man);
-
// Save the descriptor to DB
- ret->WriteDescriptor();
+ spk_man->WriteDescriptor();
- return ret;
+ return spk_man;
}