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.cpp1740
1 files changed, 194 insertions, 1546 deletions
diff --git a/src/wallet/wallet.cpp b/src/wallet/wallet.cpp
index db80745db0..9986a02fc1 100644
--- a/src/wallet/wallet.cpp
+++ b/src/wallet/wallet.cpp
@@ -13,11 +13,12 @@
#include <interfaces/wallet.h>
#include <key.h>
#include <key_io.h>
-#include <optional.h>
+#include <outputtype.h>
#include <policy/fees.h>
#include <policy/policy.h>
#include <primitives/block.h>
#include <primitives/transaction.h>
+#include <psbt.h>
#include <script/descriptor.h>
#include <script/script.h>
#include <script/signingprovider.h>
@@ -32,11 +33,13 @@
#include <util/translation.h>
#include <wallet/coincontrol.h>
#include <wallet/fees.h>
+#include <wallet/external_signer_scriptpubkeyman.h>
#include <univalue.h>
#include <algorithm>
#include <assert.h>
+#include <optional>
#include <boost/algorithm/string/replace.hpp>
@@ -50,8 +53,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);
@@ -81,10 +82,10 @@ bool RemoveWalletSetting(interfaces::Chain& chain, const std::string& wallet_nam
static void UpdateWalletSetting(interfaces::Chain& chain,
const std::string& wallet_name,
- Optional<bool> load_on_startup,
+ std::optional<bool> load_on_startup,
std::vector<bilingual_str>& warnings)
{
- if (load_on_startup == nullopt) return;
+ if (!load_on_startup) return;
if (load_on_startup.value() && !AddWalletSetting(chain, wallet_name)) {
warnings.emplace_back(Untranslated("Wallet load on startup setting could not be updated, so wallet may not be loaded next node startup."));
} else if (!load_on_startup.value() && !RemoveWalletSetting(chain, wallet_name)) {
@@ -104,7 +105,7 @@ bool AddWallet(const std::shared_ptr<CWallet>& wallet)
return true;
}
-bool RemoveWallet(const std::shared_ptr<CWallet>& wallet, Optional<bool> load_on_start, std::vector<bilingual_str>& warnings)
+bool RemoveWallet(const std::shared_ptr<CWallet>& wallet, std::optional<bool> load_on_start, std::vector<bilingual_str>& warnings)
{
assert(wallet);
@@ -124,7 +125,7 @@ bool RemoveWallet(const std::shared_ptr<CWallet>& wallet, Optional<bool> load_on
return true;
}
-bool RemoveWallet(const std::shared_ptr<CWallet>& wallet, Optional<bool> load_on_start)
+bool RemoveWallet(const std::shared_ptr<CWallet>& wallet, std::optional<bool> load_on_start)
{
std::vector<bilingual_str> warnings;
return RemoveWallet(wallet, load_on_start, warnings);
@@ -201,7 +202,7 @@ void UnloadWallet(std::shared_ptr<CWallet>&& wallet)
}
namespace {
-std::shared_ptr<CWallet> LoadWalletInternal(interfaces::Chain& chain, const std::string& name, Optional<bool> load_on_start, const DatabaseOptions& options, DatabaseStatus& status, bilingual_str& error, std::vector<bilingual_str>& warnings)
+std::shared_ptr<CWallet> LoadWalletInternal(interfaces::Chain& chain, const std::string& name, std::optional<bool> load_on_start, const DatabaseOptions& options, DatabaseStatus& status, bilingual_str& error, std::vector<bilingual_str>& warnings)
{
try {
std::unique_ptr<WalletDatabase> database = MakeWalletDatabase(name, options, status, error);
@@ -210,7 +211,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;
@@ -231,11 +233,11 @@ std::shared_ptr<CWallet> LoadWalletInternal(interfaces::Chain& chain, const std:
}
} // namespace
-std::shared_ptr<CWallet> LoadWallet(interfaces::Chain& chain, const std::string& name, Optional<bool> load_on_start, const DatabaseOptions& options, DatabaseStatus& status, bilingual_str& error, std::vector<bilingual_str>& warnings)
+std::shared_ptr<CWallet> LoadWallet(interfaces::Chain& chain, const std::string& name, std::optional<bool> load_on_start, const DatabaseOptions& options, DatabaseStatus& status, bilingual_str& error, std::vector<bilingual_str>& warnings)
{
auto result = WITH_LOCK(g_loading_wallet_mutex, return g_loading_wallet_set.insert(name));
if (!result.second) {
- error = Untranslated("Wallet already being loading.");
+ error = Untranslated("Wallet already loading.");
status = DatabaseStatus::FAILED_LOAD;
return nullptr;
}
@@ -244,7 +246,7 @@ std::shared_ptr<CWallet> LoadWallet(interfaces::Chain& chain, const std::string&
return wallet;
}
-std::shared_ptr<CWallet> CreateWallet(interfaces::Chain& chain, const std::string& name, Optional<bool> load_on_start, DatabaseOptions& options, DatabaseStatus& status, bilingual_str& error, std::vector<bilingual_str>& warnings)
+std::shared_ptr<CWallet> CreateWallet(interfaces::Chain& chain, const std::string& name, std::optional<bool> load_on_start, DatabaseOptions& options, DatabaseStatus& status, bilingual_str& error, std::vector<bilingual_str>& warnings)
{
uint64_t wallet_creation_flags = options.create_flags;
const SecureString& passphrase = options.create_passphrase;
@@ -259,6 +261,20 @@ std::shared_ptr<CWallet> CreateWallet(interfaces::Chain& chain, const std::strin
wallet_creation_flags |= WALLET_FLAG_BLANK_WALLET;
}
+ // Private keys must be disabled for an external signer wallet
+ if ((wallet_creation_flags & WALLET_FLAG_EXTERNAL_SIGNER) && !(wallet_creation_flags & WALLET_FLAG_DISABLE_PRIVATE_KEYS)) {
+ error = Untranslated("Private keys must be disabled when using an external signer");
+ status = DatabaseStatus::FAILED_CREATE;
+ return nullptr;
+ }
+
+ // Descriptor support must be enabled for an external signer wallet
+ if ((wallet_creation_flags & WALLET_FLAG_EXTERNAL_SIGNER) && !(wallet_creation_flags & WALLET_FLAG_DESCRIPTORS)) {
+ error = Untranslated("Descriptor support must be enabled when using an external signer");
+ status = DatabaseStatus::FAILED_CREATE;
+ return nullptr;
+ }
+
// Wallet::Verify will check if we're trying to create a wallet with a duplicate name.
std::unique_ptr<WalletDatabase> database = MakeWalletDatabase(name, options, status, error);
if (!database) {
@@ -275,7 +291,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;
@@ -332,11 +349,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);
@@ -586,12 +598,12 @@ bool CWallet::EncryptWallet(const SecureString& strWalletPassphrase)
CKeyingMaterial _vMasterKey;
_vMasterKey.resize(WALLET_CRYPTO_KEY_SIZE);
- GetStrongRandBytes(&_vMasterKey[0], WALLET_CRYPTO_KEY_SIZE);
+ GetStrongRandBytes(_vMasterKey.data(), WALLET_CRYPTO_KEY_SIZE);
CMasterKey kMasterKey;
kMasterKey.vchSalt.resize(WALLET_CRYPTO_SALT_SIZE);
- GetStrongRandBytes(&kMasterKey.vchSalt[0], WALLET_CRYPTO_SALT_SIZE);
+ GetStrongRandBytes(kMasterKey.vchSalt.data(), WALLET_CRYPTO_SALT_SIZE);
CCrypter crypter;
int64_t nStartTime = GetTimeMillis();
@@ -774,6 +786,12 @@ bool CWallet::MarkReplaced(const uint256& originalHash, const uint256& newHash)
wtx.mapValue["replaced_by_txid"] = newHash.ToString();
+ // Refresh mempool status without waiting for transactionRemovedFromMempool
+ // notification so the wallet is in an internally consistent state and
+ // immediately knows the old transaction should not be considered trusted
+ // and is eligible to be abandoned
+ wtx.fInMempool = chain().isInMempool(originalHash);
+
WalletBatch batch(GetDatabase());
bool success = true;
@@ -921,6 +939,14 @@ CWalletTx* CWallet::AddToWallet(CTransactionRef tx, const CWalletTx::Confirmatio
if (!strCmd.empty())
{
boost::replace_all(strCmd, "%s", hash.GetHex());
+ if (confirm.status == CWalletTx::Status::CONFIRMED)
+ {
+ boost::replace_all(strCmd, "%b", confirm.hashBlock.GetHex());
+ boost::replace_all(strCmd, "%h", ToString(confirm.block_height));
+ } else {
+ boost::replace_all(strCmd, "%b", "unconfirmed");
+ boost::replace_all(strCmd, "%h", "-1");
+ }
#ifndef WIN32
// Substituting the wallet name isn't currently supported on windows
// because windows shell escaping has not been implemented yet:
@@ -1250,20 +1276,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
@@ -1304,49 +1316,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);
@@ -1373,52 +1342,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
@@ -1498,12 +1421,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
@@ -1594,97 +1511,6 @@ bool CWallet::ImportScriptPubKeys(const std::string& label, const std::set<CScri
return true;
}
-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 -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
-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 -1;
- }
- return GetVirtualTransactionSize(CTransaction(txNew));
-}
-
-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
@@ -1736,7 +1562,7 @@ int64_t CWallet::RescanFromTime(int64_t startTime, const WalletRescanReserver& r
* the main chain after to the addition of any new keys you want to detect
* transactions for.
*/
-CWallet::ScanResult CWallet::ScanForWalletTransactions(const uint256& start_block, int start_height, Optional<int> max_height, const WalletRescanReserver& reserver, bool fUpdate)
+CWallet::ScanResult CWallet::ScanForWalletTransactions(const uint256& start_block, int start_height, std::optional<int> max_height, const WalletRescanReserver& reserver, bool fUpdate)
{
int64_t nNow = GetTime();
int64_t start_time = GetTimeMillis();
@@ -1749,7 +1575,7 @@ CWallet::ScanResult CWallet::ScanForWalletTransactions(const uint256& start_bloc
WalletLogPrintf("Rescan started from block %s...\n", start_block.ToString());
fAbortRescan = false;
- ShowProgress(strprintf("%s " + _("Rescanning...").translated, GetDisplayName()), 0); // show rescan progress in GUI as dialog or on splashscreen, if -rescan on startup
+ ShowProgress(strprintf("%s " + _("Rescanning…").translated, GetDisplayName()), 0); // show rescan progress in GUI as dialog or on splashscreen, if -rescan on startup
uint256 tip_hash = WITH_LOCK(cs_wallet, return GetLastBlockHash());
uint256 end_hash = tip_hash;
if (max_height) chain().findAncestorByHeight(tip_hash, *max_height, FoundBlock().hash(end_hash));
@@ -1764,7 +1590,7 @@ CWallet::ScanResult CWallet::ScanForWalletTransactions(const uint256& start_bloc
m_scanning_progress = 0;
}
if (block_height % 100 == 0 && progress_end - progress_begin > 0.0) {
- ShowProgress(strprintf("%s " + _("Rescanning...").translated, GetDisplayName()), std::max(1, std::min(99, (int)(m_scanning_progress * 100))));
+ ShowProgress(strprintf("%s " + _("Rescanning…").translated, GetDisplayName()), std::max(1, std::min(99, (int)(m_scanning_progress * 100))));
}
if (GetTime() >= nNow + 60) {
nNow = GetTime();
@@ -1826,7 +1652,7 @@ CWallet::ScanResult CWallet::ScanForWalletTransactions(const uint256& start_bloc
}
}
}
- ShowProgress(strprintf("%s " + _("Rescanning...").translated, GetDisplayName()), 100); // hide progress dialog in GUI
+ ShowProgress(strprintf("%s " + _("Rescanning…").translated, GetDisplayName()), 100); // hide progress dialog in GUI
if (block_height && fAbortRescan) {
WalletLogPrintf("Rescan aborted at block %d. Progress=%f\n", block_height, progress_current);
result.status = ScanResult::USER_ABORT;
@@ -1907,165 +1733,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.
//
@@ -2126,371 +1793,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, true, 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, bool fOnlySafe, 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};
-
- 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 (fOnlySafe && !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 long term estimate
- FeeCalculation feeCalc;
- CCoinControl temp;
- temp.m_confirm_target = 1008;
- CFeeRate long_term_feerate = GetMinimumFeeRate(*this, temp, &feeCalc);
-
- // 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.effective_fee;
- }
-
- std::vector<OutputGroup> groups = GroupOutputs(coins, !coin_selection_params.m_avoid_partial_spends, effective_feerate, long_term_feerate, eligibility_filter, true /* positive_only */);
-
- // Calculate cost of change
- CAmount cost_of_change = GetDiscardRate(*this).GetFee(coin_selection_params.change_spend_size) + coin_selection_params.effective_fee.GetFee(coin_selection_params.change_output_size);
-
- // Calculate the fees for things that aren't inputs
- CAmount not_input_fees = coin_selection_params.effective_fee.GetFee(coin_selection_params.tx_noinputs_size);
- 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.effective_fee.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
- 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);
- size_t max_ancestors = (size_t)std::max<int64_t>(1, limit_ancestor_count);
- size_t max_descendants = (size_t)std::max<int64_t>(1, limit_descendant_count);
- 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());
- }
- bool res = value_to_select <= 0 ||
- SelectCoinsMinConf(value_to_select, CoinEligibilityFilter(1, 6, 0), vCoins, setCoinsRet, nValueRet, coin_selection_params, bnb_used) ||
- SelectCoinsMinConf(value_to_select, CoinEligibilityFilter(1, 1, 0), vCoins, setCoinsRet, nValueRet, coin_selection_params, bnb_used) ||
- (m_spend_zero_conf_change && SelectCoinsMinConf(value_to_select, CoinEligibilityFilter(0, 1, 2), vCoins, setCoinsRet, nValueRet, coin_selection_params, bnb_used)) ||
- (m_spend_zero_conf_change && 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)) ||
- (m_spend_zero_conf_change && SelectCoinsMinConf(value_to_select, CoinEligibilityFilter(0, 1, max_ancestors/2, max_descendants/2), vCoins, setCoinsRet, nValueRet, coin_selection_params, bnb_used)) ||
- (m_spend_zero_conf_change && 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)) ||
- (m_spend_zero_conf_change && !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));
-
- // because SelectCoinsMinConf clears the setCoinsRet, we now add the possible inputs 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);
@@ -2586,119 +1888,7 @@ 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 Optional<OutputType>& change_type, const std::vector<CRecipient>& vecSend) const
+OutputType CWallet::TransactionChangeType(const std::optional<OutputType>& change_type, const std::vector<CRecipient>& vecSend) const
{
// If -changetype is specified, always use that change type.
if (change_type) {
@@ -2726,406 +1916,6 @@ OutputType CWallet::TransactionChangeType(const Optional<OutputType>& change_typ
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;
- int nBytes;
- {
- std::set<CInputCoin> setCoins;
- LOCK(cs_wallet);
- txNew.nLockTime = GetLocktimeForNewTransaction(chain(), GetLastBlockHash(), GetLastBlockHeight());
- {
- std::vector<COutput> vAvailableCoins;
- AvailableCoins(vAvailableCoins, true, &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);
-
- CFeeRate discard_rate = GetDiscardRate(*this);
-
- // Get the fee rate to use effective values in coin selection
- CFeeRate nFeeRateNeeded = 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 && nFeeRateNeeded > *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), nFeeRateNeeded.ToString(FeeEstimateMode::SAT_VB));
- return false;
- }
-
- 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;
- }
- coin_selection_params.effective_fee = nFeeRateNeeded;
- 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, discard_rate) || 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()));
- }
-
- nBytes = CalculateMaximumSignedTxSize(CTransaction(txNew), this, coin_control.fAllowWatchOnly);
- if (nBytes < 0) {
- error = _("Signing transaction failed");
- return false;
- }
-
- nFeeNeeded = GetMinimumFee(*this, nBytes, coin_control, &feeCalc);
- 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;
- }
-
- 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 = GetMinimumFee(*this, tx_size_with_change, coin_control, nullptr);
- CAmount minimum_value_for_change = GetDustThreshold(change_prototype_txout, discard_rate);
- 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 (GetTransactionWeight(*tx) > 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);
@@ -3166,11 +1956,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)
{
@@ -3182,9 +1971,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());
}
@@ -3372,137 +2159,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);
@@ -3558,6 +2214,25 @@ void ReserveDestination::ReturnDestination()
address = CNoDestination();
}
+bool CWallet::DisplayAddress(const CTxDestination& dest)
+{
+#ifdef ENABLE_EXTERNAL_SIGNER
+ CScript scriptPubKey = GetScriptForDestination(dest);
+ const auto spk_man = GetScriptPubKeyMan(scriptPubKey);
+ if (spk_man == nullptr) {
+ return false;
+ }
+ auto signer_spk_man = dynamic_cast<ExternalSignerScriptPubKeyMan*>(spk_man);
+ if (signer_spk_man == nullptr) {
+ return false;
+ }
+ ExternalSigner signer = ExternalSignerScriptPubKeyMan::GetExternalSigner();
+ return signer_spk_man->DisplayAddress(scriptPubKey, signer);
+#else
+ return false;
+#endif
+}
+
void CWallet::LockCoin(const COutPoint& output)
{
AssertLockHeld(cs_wallet);
@@ -3786,18 +2461,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);
@@ -3824,6 +2496,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
@@ -3836,7 +2512,7 @@ std::shared_ptr<CWallet> CWallet::Create(interfaces::Chain& chain, const std::st
walletInstance->SetupLegacyScriptPubKeyMan();
}
- if (!(wallet_creation_flags & (WALLET_FLAG_DISABLE_PRIVATE_KEYS | WALLET_FLAG_BLANK_WALLET))) {
+ if ((wallet_creation_flags & WALLET_FLAG_EXTERNAL_SIGNER) || !(wallet_creation_flags & (WALLET_FLAG_DISABLE_PRIVATE_KEYS | WALLET_FLAG_BLANK_WALLET))) {
LOCK(walletInstance->cs_wallet);
if (walletInstance->IsWalletFlagSet(WALLET_FLAG_DESCRIPTORS)) {
walletInstance->SetupDescriptorScriptPubKeyMans();
@@ -3852,7 +2528,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);
@@ -3949,9 +2627,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;
}
}
@@ -3965,15 +2643,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."));
}
@@ -3989,6 +2667,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
@@ -4005,13 +2712,13 @@ std::shared_ptr<CWallet> CWallet::Create(interfaces::Chain& chain, const std::st
WalletBatch batch(walletInstance->GetDatabase());
CBlockLocator locator;
if (batch.ReadBestBlock(locator)) {
- if (const Optional<int> fork_height = chain.findLocatorFork(locator)) {
+ if (const std::optional<int> fork_height = chain.findLocatorFork(locator)) {
rescan_height = *fork_height;
}
}
}
- const Optional<int> tip_height = chain.getHeight();
+ const std::optional<int> tip_height = chain.getHeight();
if (tip_height) {
walletInstance->m_last_block_processed = chain.getBlockHash(*tip_height);
walletInstance->m_last_block_processed_height = *tip_height;
@@ -4022,30 +2729,30 @@ 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;
}
}
- chain.initMessage(_("Rescanning...").translated);
+ chain.initMessage(_("Rescanning…").translated);
walletInstance->WalletLogPrintf("Rescanning last %i blocks (from block %i)...\n", *tip_height - rescan_height, rescan_height);
// No need to read and scan block if block was created before
// our wallet birthday (as adjusted for block time variability)
- Optional<int64_t> time_first_key;
+ std::optional<int64_t> time_first_key;
for (auto spk_man : walletInstance->GetAllScriptPubKeyMans()) {
int64_t time = spk_man->GetTimeFirstKey();
if (!time_first_key || time < *time_first_key) time_first_key = time;
@@ -4058,29 +2765,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
@@ -4182,92 +2874,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();
@@ -4443,40 +3049,82 @@ void CWallet::ConnectScriptPubKeyManNotifiers()
void CWallet::LoadDescriptorScriptPubKeyMan(uint256 id, WalletDescriptor& desc)
{
- auto spk_manager = std::unique_ptr<ScriptPubKeyMan>(new DescriptorScriptPubKeyMan(*this, desc));
- m_spk_managers[id] = std::move(spk_manager);
+ 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);
+ }
}
void CWallet::SetupDescriptorScriptPubKeyMans()
{
AssertLockHeld(cs_wallet);
- // Make a seed
- CKey seed_key;
- seed_key.MakeNewKey(true);
- CPubKey seed = seed_key.GetPubKey();
- assert(seed_key.VerifyPubKey(seed));
+ if (!IsWalletFlagSet(WALLET_FLAG_EXTERNAL_SIGNER)) {
+ // Make a seed
+ CKey seed_key;
+ seed_key.MakeNewKey(true);
+ CPubKey seed = seed_key.GetPubKey();
+ assert(seed_key.VerifyPubKey(seed));
- // Get the extended key
- CExtKey master_key;
- master_key.SetSeed(seed_key.begin(), seed_key.size());
+ // Get the extended key
+ CExtKey master_key;
+ master_key.SetSeed(seed_key.begin(), seed_key.size());
- for (bool internal : {false, true}) {
- for (OutputType t : OUTPUT_TYPES) {
- auto spk_manager = std::unique_ptr<DescriptorScriptPubKeyMan>(new DescriptorScriptPubKeyMan(*this, internal));
- if (IsCrypted()) {
- if (IsLocked()) {
- throw std::runtime_error(std::string(__func__) + ": Wallet is locked, cannot setup new descriptors");
+ for (bool internal : {false, true}) {
+ for (OutputType t : OUTPUT_TYPES) {
+ auto spk_manager = std::unique_ptr<DescriptorScriptPubKeyMan>(new DescriptorScriptPubKeyMan(*this, internal));
+ if (IsCrypted()) {
+ if (IsLocked()) {
+ throw std::runtime_error(std::string(__func__) + ": Wallet is locked, cannot setup new descriptors");
+ }
+ if (!spk_manager->CheckDecryptionKey(vMasterKey) && !spk_manager->Encrypt(vMasterKey, nullptr)) {
+ throw std::runtime_error(std::string(__func__) + ": Could not encrypt new descriptors");
+ }
}
- if (!spk_manager->CheckDecryptionKey(vMasterKey) && !spk_manager->Encrypt(vMasterKey, nullptr)) {
- throw std::runtime_error(std::string(__func__) + ": Could not encrypt new descriptors");
+ spk_manager->SetupDescriptorGeneration(master_key, t);
+ 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
+ int account = 0;
+ UniValue signer_res = signer.GetDescriptors(account);
+
+ if (!signer_res.isObject()) throw std::runtime_error(std::string(__func__) + ": Unexpected result");
+ for (bool internal : {false, true}) {
+ const UniValue& descriptor_vals = find_value(signer_res, internal ? "internal" : "receive");
+ if (!descriptor_vals.isArray()) throw std::runtime_error(std::string(__func__) + ": Unexpected result");
+ for (const UniValue& desc_val : descriptor_vals.get_array().getValues()) {
+ std::string desc_str = desc_val.getValStr();
+ FlatSigningProvider keys;
+ std::string dummy_error;
+ std::unique_ptr<Descriptor> desc = Parse(desc_str, keys, dummy_error, false);
+ if (!desc->GetOutputType()) {
+ continue;
}
+ OutputType t = *desc->GetOutputType();
+ auto spk_manager = std::unique_ptr<ExternalSignerScriptPubKeyMan>(new ExternalSignerScriptPubKeyMan(*this, internal));
+ spk_manager->SetupDescriptor(std::move(desc));
+ uint256 id = spk_manager->GetID();
+ m_spk_managers[id] = std::move(spk_manager);
+ AddActiveScriptPubKeyMan(id, t, internal);
}
- spk_manager->SetupDescriptorGeneration(master_key, t);
- 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
}
}