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.cpp365
1 files changed, 221 insertions, 144 deletions
diff --git a/src/wallet/wallet.cpp b/src/wallet/wallet.cpp
index 6c333c709b..de1078e646 100644
--- a/src/wallet/wallet.cpp
+++ b/src/wallet/wallet.cpp
@@ -173,8 +173,8 @@ void NotifyWalletLoaded(WalletContext& context, const std::shared_ptr<CWallet>&
}
}
-static Mutex g_loading_wallet_mutex;
-static Mutex g_wallet_release_mutex;
+static GlobalMutex g_loading_wallet_mutex;
+static GlobalMutex g_wallet_release_mutex;
static std::condition_variable g_wallet_release_cv;
static std::set<std::string> g_loading_wallet_set GUARDED_BY(g_loading_wallet_mutex);
static std::set<std::string> g_unloading_wallet_set GUARDED_BY(g_wallet_release_mutex);
@@ -414,7 +414,7 @@ std::shared_ptr<CWallet> RestoreWallet(WalletContext& context, const fs::path& b
const CWalletTx* CWallet::GetWalletTx(const uint256& hash) const
{
AssertLockHeld(cs_wallet);
- std::map<uint256, CWalletTx>::const_iterator it = mapWallet.find(hash);
+ const auto it = mapWallet.find(hash);
if (it == mapWallet.end())
return nullptr;
return &(it->second);
@@ -551,7 +551,7 @@ std::set<uint256> CWallet::GetConflicts(const uint256& txid) const
std::set<uint256> result;
AssertLockHeld(cs_wallet);
- std::map<uint256, CWalletTx>::const_iterator it = mapWallet.find(txid);
+ const auto it = mapWallet.find(txid);
if (it == mapWallet.end())
return result;
const CWalletTx& wtx = it->second;
@@ -569,11 +569,17 @@ std::set<uint256> CWallet::GetConflicts(const uint256& txid) const
return result;
}
-bool CWallet::HasWalletSpend(const uint256& txid) const
+bool CWallet::HasWalletSpend(const CTransactionRef& tx) const
{
AssertLockHeld(cs_wallet);
- auto iter = mapTxSpends.lower_bound(COutPoint(txid, 0));
- return (iter != mapTxSpends.end() && iter->first.hash == txid);
+ const uint256& txid = tx->GetHash();
+ for (unsigned int i = 0; i < tx->vout.size(); ++i) {
+ auto iter = mapTxSpends.find(COutPoint(txid, i));
+ if (iter != mapTxSpends.end()) {
+ return true;
+ }
+ }
+ return false;
}
void CWallet::Flush()
@@ -629,16 +635,14 @@ void CWallet::SyncMetaData(std::pair<TxSpends::iterator, TxSpends::iterator> ran
* Outpoint is spent if any non-conflicted transaction
* spends it:
*/
-bool CWallet::IsSpent(const uint256& hash, unsigned int n) const
+bool CWallet::IsSpent(const COutPoint& outpoint) const
{
- const COutPoint outpoint(hash, n);
std::pair<TxSpends::const_iterator, TxSpends::const_iterator> range;
range = mapTxSpends.equal_range(outpoint);
- for (TxSpends::const_iterator it = range.first; it != range.second; ++it)
- {
+ for (TxSpends::const_iterator it = range.first; it != range.second; ++it) {
const uint256& wtxid = it->second;
- std::map<uint256, CWalletTx>::const_iterator mit = mapWallet.find(wtxid);
+ const auto mit = mapWallet.find(wtxid);
if (mit != mapWallet.end()) {
int depth = GetTxDepthInMainChain(mit->second);
if (depth > 0 || (depth == 0 && !mit->second.isAbandoned()))
@@ -665,16 +669,13 @@ void CWallet::AddToSpends(const COutPoint& outpoint, const uint256& wtxid, Walle
}
-void CWallet::AddToSpends(const uint256& wtxid, WalletBatch* batch)
+void CWallet::AddToSpends(const CWalletTx& wtx, WalletBatch* batch)
{
- auto it = mapWallet.find(wtxid);
- assert(it != mapWallet.end());
- const CWalletTx& thisTx = it->second;
- if (thisTx.IsCoinBase()) // Coinbases don't spend anything!
+ if (wtx.IsCoinBase()) // Coinbases don't spend anything!
return;
- for (const CTxIn& txin : thisTx.tx->vin)
- AddToSpends(txin.prevout, wtxid, batch);
+ for (const CTxIn& txin : wtx.tx->vin)
+ AddToSpends(txin.prevout, wtx.GetHash(), batch);
}
bool CWallet::EncryptWallet(const SecureString& strWalletPassphrase)
@@ -908,35 +909,31 @@ void CWallet::SetSpentKeyState(WalletBatch& batch, const uint256& hash, unsigned
}
}
-bool CWallet::IsSpentKey(const uint256& hash, unsigned int n) const
+bool CWallet::IsSpentKey(const CScript& scriptPubKey) const
{
AssertLockHeld(cs_wallet);
- const CWalletTx* srctx = GetWalletTx(hash);
- if (srctx) {
- assert(srctx->tx->vout.size() > n);
- CTxDestination dest;
- if (!ExtractDestination(srctx->tx->vout[n].scriptPubKey, dest)) {
- return false;
- }
- if (IsAddressUsed(dest)) {
- return true;
- }
- if (IsLegacy()) {
- LegacyScriptPubKeyMan* spk_man = GetLegacyScriptPubKeyMan();
- assert(spk_man != nullptr);
- for (const auto& keyid : GetAffectedKeys(srctx->tx->vout[n].scriptPubKey, *spk_man)) {
- WitnessV0KeyHash wpkh_dest(keyid);
- if (IsAddressUsed(wpkh_dest)) {
- return true;
- }
- ScriptHash sh_wpkh_dest(GetScriptForDestination(wpkh_dest));
- if (IsAddressUsed(sh_wpkh_dest)) {
- return true;
- }
- PKHash pkh_dest(keyid);
- if (IsAddressUsed(pkh_dest)) {
- return true;
- }
+ CTxDestination dest;
+ if (!ExtractDestination(scriptPubKey, dest)) {
+ return false;
+ }
+ if (IsAddressUsed(dest)) {
+ return true;
+ }
+ if (IsLegacy()) {
+ LegacyScriptPubKeyMan* spk_man = GetLegacyScriptPubKeyMan();
+ assert(spk_man != nullptr);
+ for (const auto& keyid : GetAffectedKeys(scriptPubKey, *spk_man)) {
+ WitnessV0KeyHash wpkh_dest(keyid);
+ if (IsAddressUsed(wpkh_dest)) {
+ return true;
+ }
+ ScriptHash sh_wpkh_dest(GetScriptForDestination(wpkh_dest));
+ if (IsAddressUsed(sh_wpkh_dest)) {
+ return true;
+ }
+ PKHash pkh_dest(keyid);
+ if (IsAddressUsed(pkh_dest)) {
+ return true;
}
}
}
@@ -973,7 +970,7 @@ CWalletTx* CWallet::AddToWallet(CTransactionRef tx, const TxState& state, const
wtx.nOrderPos = IncOrderPosNext(&batch);
wtx.m_it_wtxOrdered = wtxOrdered.insert(std::make_pair(wtx.nOrderPos, &wtx));
wtx.nTimeSmart = ComputeTimeSmart(wtx, rescanning_old_block);
- AddToSpends(hash, &batch);
+ AddToSpends(wtx, &batch);
}
if (!fInsertedNew)
@@ -1072,7 +1069,7 @@ bool CWallet::LoadToWallet(const uint256& hash, const UpdateWalletTxFn& fill_wtx
if (/* insertion took place */ ins.second) {
wtx.m_it_wtxOrdered = wtxOrdered.insert(std::make_pair(wtx.nOrderPos, &wtx));
}
- AddToSpends(hash);
+ AddToSpends(wtx);
for (const CTxIn& txin : wtx.tx->vin) {
auto it = mapWallet.find(txin.prevout.hash);
if (it != mapWallet.end()) {
@@ -1139,7 +1136,13 @@ bool CWallet::AddToWalletIfInvolvingMe(const CTransactionRef& ptx, const SyncTxS
// Block disconnection override an abandoned tx as unconfirmed
// which means user may have to call abandontransaction again
TxState tx_state = std::visit([](auto&& s) -> TxState { return s; }, state);
- return AddToWallet(MakeTransactionRef(tx), tx_state, /*update_wtx=*/nullptr, /*fFlushOnClose=*/false, rescanning_old_block);
+ CWalletTx* wtx = AddToWallet(MakeTransactionRef(tx), tx_state, /*update_wtx=*/nullptr, /*fFlushOnClose=*/false, rescanning_old_block);
+ if (!wtx) {
+ // Can only be nullptr if there was a db write error (missing db, read-only db or a db engine internal writing error).
+ // As we only store arriving transaction in this process, and we don't want an inconsistent state, let's throw an error.
+ throw std::runtime_error("DB error adding transaction to wallet, write failed");
+ }
+ return true;
}
}
return false;
@@ -1200,12 +1203,13 @@ bool CWallet::AbandonTransaction(const uint256& hashTx)
batch.WriteTx(wtx);
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) {
- if (!done.count(iter->second)) {
- todo.insert(iter->second);
+ for (unsigned int i = 0; i < wtx.tx->vout.size(); ++i) {
+ std::pair<TxSpends::const_iterator, TxSpends::const_iterator> range = mapTxSpends.equal_range(COutPoint(now, i));
+ for (TxSpends::const_iterator iter = range.first; iter != range.second; ++iter) {
+ if (!done.count(iter->second)) {
+ todo.insert(iter->second);
+ }
}
- iter++;
}
// If a transaction changes 'conflicted' state, that changes the balance
// available of the outputs it spends. So force those to be recomputed
@@ -1251,12 +1255,13 @@ void CWallet::MarkConflicted(const uint256& hashBlock, int conflicting_height, c
wtx.MarkDirty();
batch.WriteTx(wtx);
// Iterate over all its outputs, and mark transactions in the wallet that spend them conflicted too
- TxSpends::const_iterator iter = mapTxSpends.lower_bound(COutPoint(now, 0));
- while (iter != mapTxSpends.end() && iter->first.hash == now) {
- if (!done.count(iter->second)) {
- todo.insert(iter->second);
- }
- iter++;
+ for (unsigned int i = 0; i < wtx.tx->vout.size(); ++i) {
+ std::pair<TxSpends::const_iterator, TxSpends::const_iterator> range = mapTxSpends.equal_range(COutPoint(now, i));
+ for (TxSpends::const_iterator iter = range.first; iter != range.second; ++iter) {
+ if (!done.count(iter->second)) {
+ todo.insert(iter->second);
+ }
+ }
}
// If a transaction changes 'conflicted' state, that changes the balance
// available of the outputs it spends. So force those to be recomputed
@@ -1323,30 +1328,31 @@ void CWallet::transactionRemovedFromMempool(const CTransactionRef& tx, MemPoolRe
}
}
-void CWallet::blockConnected(const CBlock& block, int height)
+void CWallet::blockConnected(const interfaces::BlockInfo& block)
{
- const uint256& block_hash = block.GetHash();
+ assert(block.data);
LOCK(cs_wallet);
- m_last_block_processed_height = height;
- m_last_block_processed = block_hash;
- for (size_t index = 0; index < block.vtx.size(); index++) {
- SyncTransaction(block.vtx[index], TxStateConfirmed{block_hash, height, static_cast<int>(index)});
- transactionRemovedFromMempool(block.vtx[index], MemPoolRemovalReason::BLOCK, 0 /* mempool_sequence */);
+ m_last_block_processed_height = block.height;
+ m_last_block_processed = block.hash;
+ for (size_t index = 0; index < block.data->vtx.size(); index++) {
+ SyncTransaction(block.data->vtx[index], TxStateConfirmed{block.hash, block.height, static_cast<int>(index)});
+ transactionRemovedFromMempool(block.data->vtx[index], MemPoolRemovalReason::BLOCK, 0 /* mempool_sequence */);
}
}
-void CWallet::blockDisconnected(const CBlock& block, int height)
+void CWallet::blockDisconnected(const interfaces::BlockInfo& block)
{
+ assert(block.data);
LOCK(cs_wallet);
// At block disconnection, this will change an abandoned transaction to
// be unconfirmed, whether or not the transaction is added back to the mempool.
// User may have to call abandontransaction again. It may be addressed in the
// future with a stickier abandoned state or even removing abandontransaction call.
- m_last_block_processed_height = height - 1;
- m_last_block_processed = block.hashPrevBlock;
- for (const CTransactionRef& ptx : block.vtx) {
+ m_last_block_processed_height = block.height - 1;
+ m_last_block_processed = *Assert(block.prev_hash);
+ for (const CTransactionRef& ptx : Assert(block.data)->vtx) {
SyncTransaction(ptx, TxStateInactive{});
}
}
@@ -1372,7 +1378,7 @@ CAmount CWallet::GetDebit(const CTxIn &txin, const isminefilter& filter) const
{
{
LOCK(cs_wallet);
- std::map<uint256, CWalletTx>::const_iterator mi = mapWallet.find(txin.prevout.hash);
+ const auto mi = mapWallet.find(txin.prevout.hash);
if (mi != mapWallet.end())
{
const CWalletTx& prev = (*mi).second;
@@ -1513,13 +1519,16 @@ bool CWallet::AddWalletFlags(uint64_t flags)
}
// 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 DummySignInput(const SigningProvider& provider, CTxIn &tx_in, const CTxOut &txout, bool use_max_sig)
+// or a max-sized low-S signature (e.g. 72 bytes) depending on coin_control
+bool DummySignInput(const SigningProvider& provider, CTxIn &tx_in, const CTxOut &txout, const CCoinControl* coin_control)
{
// Fill in dummy signatures for fee calculation.
const CScript& scriptPubKey = txout.scriptPubKey;
SignatureData sigdata;
+ // Use max sig if watch only inputs were used or if this particular input is an external input
+ // to ensure a sufficient fee is attained for the requested feerate.
+ const bool use_max_sig = coin_control && (coin_control->fAllowWatchOnly || coin_control->IsExternalSelected(tx_in.prevout));
if (!ProduceSignature(provider, use_max_sig ? DUMMY_MAXIMUM_SIGNATURE_CREATOR : DUMMY_SIGNATURE_CREATOR, scriptPubKey, sigdata)) {
return false;
}
@@ -1586,12 +1595,9 @@ bool CWallet::DummySignTx(CMutableTransaction &txNew, const std::vector<CTxOut>
nIn++;
continue;
}
- // Use max sig if watch only inputs were used or if this particular input is an external input
- // to ensure a sufficient fee is attained for the requested feerate.
- const bool use_max_sig = coin_control && (coin_control->fAllowWatchOnly || coin_control->IsExternalSelected(txin.prevout));
const std::unique_ptr<SigningProvider> provider = GetSolvingProvider(txout.scriptPubKey);
- if (!provider || !DummySignInput(*provider, txin, txout, use_max_sig)) {
- if (!coin_control || !DummySignInput(coin_control->m_external_provider, txin, txout, use_max_sig)) {
+ if (!provider || !DummySignInput(*provider, txin, txout, coin_control)) {
+ if (!coin_control || !DummySignInput(coin_control->m_external_provider, txin, txout, coin_control)) {
return false;
}
}
@@ -1674,7 +1680,7 @@ int64_t CWallet::RescanFromTime(int64_t startTime, const WalletRescanReserver& r
if (start) {
// TODO: this should take into account failure by ScanResult::USER_ABORT
- ScanResult result = ScanForWalletTransactions(start_block, start_height, {} /* max_height */, reserver, update);
+ ScanResult result = ScanForWalletTransactions(start_block, start_height, /*max_height=*/{}, reserver, /*fUpdate=*/update, /*save_progress=*/false);
if (result.status == ScanResult::FAILURE) {
int64_t time_max;
CHECK_NONFATAL(chain().findBlock(result.last_failed_block, FoundBlock().maxTime(time_max)));
@@ -1687,7 +1693,8 @@ int64_t CWallet::RescanFromTime(int64_t startTime, const WalletRescanReserver& r
/**
* Scan the block chain (starting in start_block) for transactions
* from or to us. If fUpdate is true, found transactions that already
- * exist in the wallet will be updated.
+ * exist in the wallet will be updated. If max_height is not set, the
+ * mempool will be scanned as well.
*
* @param[in] start_block Scan starting block. If block is not on the active
* chain, the scan will return SUCCESS immediately.
@@ -1705,12 +1712,11 @@ 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, std::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, const bool save_progress)
{
- using Clock = std::chrono::steady_clock;
- constexpr auto LOG_INTERVAL{60s};
- auto current_time{Clock::now()};
- auto start_time{Clock::now()};
+ constexpr auto INTERVAL_TIME{60s};
+ auto current_time{reserver.now()};
+ auto start_time{reserver.now()};
assert(reserver.isReserved());
@@ -1737,8 +1743,10 @@ CWallet::ScanResult CWallet::ScanForWalletTransactions(const uint256& start_bloc
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))));
}
- if (Clock::now() >= current_time + LOG_INTERVAL) {
- current_time = Clock::now();
+
+ bool next_interval = reserver.now() >= current_time + INTERVAL_TIME;
+ if (next_interval) {
+ current_time = reserver.now();
WalletLogPrintf("Still rescanning. At block %d. Progress=%f\n", block_height, progress_current);
}
@@ -1768,6 +1776,16 @@ CWallet::ScanResult CWallet::ScanForWalletTransactions(const uint256& start_bloc
// scan succeeded, record block as most recent successfully scanned
result.last_scanned_block = block_hash;
result.last_scanned_height = block_height;
+
+ if (save_progress && next_interval) {
+ CBlockLocator loc = m_chain->getActiveChainLocator(block_hash);
+
+ if (!loc.IsNull()) {
+ WalletLogPrintf("Saving scan progress %d.\n", block_height);
+ WalletBatch batch(GetDatabase());
+ batch.WriteBestBlock(loc);
+ }
+ }
} else {
// could not scan block, keep scanning but record this block as the most recent failure
result.last_failed_block = block_hash;
@@ -1797,6 +1815,10 @@ CWallet::ScanResult CWallet::ScanForWalletTransactions(const uint256& start_bloc
}
}
}
+ if (!max_height) {
+ WalletLogPrintf("Scanning current mempool transactions.\n");
+ WITH_LOCK(cs_wallet, chain().requestMempoolTransactions(*this));
+ }
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);
@@ -1805,8 +1827,7 @@ CWallet::ScanResult CWallet::ScanForWalletTransactions(const uint256& start_bloc
WalletLogPrintf("Rescan interrupted by shutdown request at block %d. Progress=%f\n", block_height, progress_current);
result.status = ScanResult::USER_ABORT;
} else {
- auto duration_milliseconds = std::chrono::duration_cast<std::chrono::milliseconds>(Clock::now() - start_time);
- WalletLogPrintf("Rescan completed in %15dms\n", duration_milliseconds.count());
+ WalletLogPrintf("Rescan completed in %15dms\n", Ticks<std::chrono::milliseconds>(reserver.now() - start_time));
}
return result;
}
@@ -1946,7 +1967,7 @@ bool CWallet::SignTransaction(CMutableTransaction& tx) const
// Build coins map
std::map<COutPoint, Coin> coins;
for (auto& input : tx.vin) {
- std::map<uint256, CWalletTx>::const_iterator mi = mapWallet.find(input.prevout.hash);
+ const auto mi = mapWallet.find(input.prevout.hash);
if(mi == mapWallet.end() || input.prevout.n >= mi->second.tx->vout.size()) {
return false;
}
@@ -1978,7 +1999,6 @@ 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) {
@@ -2002,6 +2022,8 @@ TransactionError CWallet::FillPSBT(PartiallySignedTransaction& psbtx, bool& comp
}
}
+ const PrecomputedTransactionData txdata = PrecomputePSBTData(psbtx);
+
// Fill in information from ScriptPubKeyMans
for (ScriptPubKeyMan* spk_man : GetAllScriptPubKeyMans()) {
int n_signed_this_spkm = 0;
@@ -2015,6 +2037,35 @@ TransactionError CWallet::FillPSBT(PartiallySignedTransaction& psbtx, bool& comp
}
}
+ // Only drop non_witness_utxos if sighash_type != SIGHASH_ANYONECANPAY
+ if ((sighash_type & 0x80) != SIGHASH_ANYONECANPAY) {
+ // Figure out if any non_witness_utxos should be dropped
+ std::vector<unsigned int> to_drop;
+ for (unsigned int i = 0; i < psbtx.inputs.size(); ++i) {
+ const auto& input = psbtx.inputs.at(i);
+ int wit_ver;
+ std::vector<unsigned char> wit_prog;
+ if (input.witness_utxo.IsNull() || !input.witness_utxo.scriptPubKey.IsWitnessProgram(wit_ver, wit_prog)) {
+ // There's a non-segwit input or Segwit v0, so we cannot drop any witness_utxos
+ to_drop.clear();
+ break;
+ }
+ if (wit_ver == 0) {
+ // Segwit v0, so we cannot drop any non_witness_utxos
+ to_drop.clear();
+ break;
+ }
+ if (input.non_witness_utxo) {
+ to_drop.push_back(i);
+ }
+ }
+
+ // Drop the non_witness_utxos that we can drop
+ for (unsigned int i : to_drop) {
+ psbtx.inputs.at(i).non_witness_utxo = nullptr;
+ }
+ }
+
// Complete if every input is now signed
complete = true;
for (const auto& input : psbtx.inputs) {
@@ -2106,7 +2157,7 @@ void CWallet::CommitTransaction(CTransactionRef tx, mapValue_t mapValue, std::ve
// Add tx to wallet, because if it has change it's also ours,
// otherwise just for transaction history.
- AddToWallet(tx, TxStateInactive{}, [&](CWalletTx& wtx, bool new_tx) {
+ CWalletTx* wtx = AddToWallet(tx, TxStateInactive{}, [&](CWalletTx& wtx, bool new_tx) {
CHECK_NONFATAL(wtx.mapValue.empty());
CHECK_NONFATAL(wtx.vOrderForm.empty());
wtx.mapValue = std::move(mapValue);
@@ -2116,6 +2167,11 @@ void CWallet::CommitTransaction(CTransactionRef tx, mapValue_t mapValue, std::ve
return true;
});
+ // wtx can only be null if the db write failed.
+ if (!wtx) {
+ throw std::runtime_error(std::string(__func__) + ": Wallet db error, transaction commit failed");
+ }
+
// Notify that old coins are spent
for (const CTxIn& txin : tx->vin) {
CWalletTx &coin = mapWallet.at(txin.prevout.hash);
@@ -2123,17 +2179,13 @@ void CWallet::CommitTransaction(CTransactionRef tx, mapValue_t mapValue, std::ve
NotifyTransactionChanged(coin.GetHash(), CT_UPDATED);
}
- // Get the inserted-CWalletTx from mapWallet so that the
- // wtx cached mempool state is updated correctly
- CWalletTx& wtx = mapWallet.at(tx->GetHash());
-
if (!fBroadcastTransactions) {
// Don't submit tx to the mempool
return;
}
std::string err_string;
- if (!SubmitTxMemoryPoolAndRelay(wtx, err_string, true)) {
+ if (!SubmitTxMemoryPoolAndRelay(*wtx, err_string, true)) {
WalletLogPrintf("CommitTransaction(): Transaction cannot be broadcast immediately, %s\n", err_string);
// TODO: if we expect the failure to be long term or permanent, instead delete wtx from the wallet and return failure.
}
@@ -2286,37 +2338,32 @@ bool CWallet::TopUpKeyPool(unsigned int kpSize)
return res;
}
-bool CWallet::GetNewDestination(const OutputType type, const std::string label, CTxDestination& dest, bilingual_str& error)
+util::Result<CTxDestination> CWallet::GetNewDestination(const OutputType type, const std::string label)
{
LOCK(cs_wallet);
- error.clear();
- bool result = false;
auto spk_man = GetScriptPubKeyMan(type, false /* internal */);
- if (spk_man) {
- spk_man->TopUp();
- result = spk_man->GetNewDestination(type, dest, error);
- } else {
- error = strprintf(_("Error: No %s addresses available."), FormatOutputType(type));
+ if (!spk_man) {
+ return util::Error{strprintf(_("Error: No %s addresses available."), FormatOutputType(type))};
}
- if (result) {
- SetAddressBook(dest, label, "receive");
+
+ spk_man->TopUp();
+ auto op_dest = spk_man->GetNewDestination(type);
+ if (op_dest) {
+ SetAddressBook(*op_dest, label, "receive");
}
- return result;
+ return op_dest;
}
-bool CWallet::GetNewChangeDestination(const OutputType type, CTxDestination& dest, bilingual_str& error)
+util::Result<CTxDestination> CWallet::GetNewChangeDestination(const OutputType type)
{
LOCK(cs_wallet);
- error.clear();
ReserveDestination reservedest(this, type);
- if (!reservedest.GetReservedDestination(dest, true, error)) {
- return false;
- }
+ auto op_dest = reservedest.GetReservedDestination(true);
+ if (op_dest) reservedest.KeepDestination();
- reservedest.KeepDestination();
- return true;
+ return op_dest;
}
std::optional<int64_t> CWallet::GetOldestKeyPoolTime() const
@@ -2347,42 +2394,63 @@ void CWallet::MarkDestinationsDirty(const std::set<CTxDestination>& destinations
}
}
-std::set<CTxDestination> CWallet::GetLabelAddresses(const std::string& label) const
+void CWallet::ForEachAddrBookEntry(const ListAddrBookFunc& func) const
{
AssertLockHeld(cs_wallet);
- std::set<CTxDestination> result;
- for (const std::pair<const CTxDestination, CAddressBookData>& item : m_address_book)
- {
- if (item.second.IsChange()) continue;
- const CTxDestination& address = item.first;
- const std::string& strName = item.second.GetLabel();
- if (strName == label)
- result.insert(address);
+ for (const std::pair<const CTxDestination, CAddressBookData>& item : m_address_book) {
+ const auto& entry = item.second;
+ func(item.first, entry.GetLabel(), entry.purpose, entry.IsChange());
}
+}
+
+std::vector<CTxDestination> CWallet::ListAddrBookAddresses(const std::optional<AddrBookFilter>& _filter) const
+{
+ AssertLockHeld(cs_wallet);
+ std::vector<CTxDestination> result;
+ AddrBookFilter filter = _filter ? *_filter : AddrBookFilter();
+ ForEachAddrBookEntry([&result, &filter](const CTxDestination& dest, const std::string& label, const std::string& purpose, bool is_change) {
+ // Filter by change
+ if (filter.ignore_change && is_change) return;
+ // Filter by label
+ if (filter.m_op_label && *filter.m_op_label != label) return;
+ // All good
+ result.emplace_back(dest);
+ });
return result;
}
-bool ReserveDestination::GetReservedDestination(CTxDestination& dest, bool internal, bilingual_str& error)
+std::set<std::string> CWallet::ListAddrBookLabels(const std::string& purpose) const
+{
+ AssertLockHeld(cs_wallet);
+ std::set<std::string> label_set;
+ ForEachAddrBookEntry([&](const CTxDestination& _dest, const std::string& _label,
+ const std::string& _purpose, bool _is_change) {
+ if (_is_change) return;
+ if (purpose.empty() || _purpose == purpose) {
+ label_set.insert(_label);
+ }
+ });
+ return label_set;
+}
+
+util::Result<CTxDestination> ReserveDestination::GetReservedDestination(bool internal)
{
m_spk_man = pwallet->GetScriptPubKeyMan(type, internal);
if (!m_spk_man) {
- error = strprintf(_("Error: No %s addresses available."), FormatOutputType(type));
- return false;
+ return util::Error{strprintf(_("Error: No %s addresses available."), FormatOutputType(type))};
}
-
if (nIndex == -1)
{
m_spk_man->TopUp();
CKeyPool keypool;
- if (!m_spk_man->GetReservedDestination(type, internal, address, nIndex, keypool, error)) {
- return false;
- }
+ auto op_address = m_spk_man->GetReservedDestination(type, internal, nIndex, keypool);
+ if (!op_address) return op_address;
+ address = *op_address;
fInternal = keypool.fInternal;
}
- dest = address;
- return true;
+ return address;
}
void ReserveDestination::KeepDestination()
@@ -2449,12 +2517,10 @@ bool CWallet::UnlockAllCoins()
return success;
}
-bool CWallet::IsLockedCoin(uint256 hash, unsigned int n) const
+bool CWallet::IsLockedCoin(const COutPoint& output) const
{
AssertLockHeld(cs_wallet);
- COutPoint outpt(hash, n);
-
- return (setLockedCoins.count(outpt) > 0);
+ return setLockedCoins.count(output) > 0;
}
void CWallet::ListLockedCoins(std::vector<COutPoint>& vOutpts) const
@@ -2992,20 +3058,31 @@ bool CWallet::AttachChain(const std::shared_ptr<CWallet>& walletInstance, interf
if (tip_height && *tip_height != rescan_height)
{
- if (chain.havePruned()) {
+ // Technically we could execute the code below in any case, but performing the
+ // `while` loop below can make startup very slow, so only check blocks on disk
+ // if necessary.
+ if (chain.havePruned() || chain.hasAssumedValidChain()) {
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.
+ // We can't rescan beyond blocks we don't have data for, 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.
+ // It also may happen if an assumed-valid chain is in use and therefore not
+ // all block data is available.
// 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)");
+
+ error = chain.hasAssumedValidChain() ?
+ _(
+ "Assumed-valid: last wallet synchronisation goes beyond "
+ "available block data. You need to wait for the background "
+ "validation chain to download more blocks.") :
+ _("Prune: last wallet synchronisation goes beyond pruned data. You need to -reindex (download the whole blockchain again in case of pruned node)");
return false;
}
}
@@ -3026,7 +3103,7 @@ bool CWallet::AttachChain(const std::shared_ptr<CWallet>& walletInstance, interf
{
WalletRescanReserver reserver(*walletInstance);
- if (!reserver.reserve() || (ScanResult::SUCCESS != walletInstance->ScanForWalletTransactions(chain.getBlockHash(rescan_height), rescan_height, {} /* max height */, reserver, true /* update */).status)) {
+ if (!reserver.reserve() || (ScanResult::SUCCESS != walletInstance->ScanForWalletTransactions(chain.getBlockHash(rescan_height), rescan_height, /*max_height=*/{}, reserver, /*fUpdate=*/true, /*save_progress=*/true).status)) {
error = _("Failed to rescan the wallet during initialization");
return false;
}
@@ -3400,7 +3477,7 @@ void CWallet::LoadActiveScriptPubKeyMan(uint256 id, OutputType type, bool intern
// 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));
+ WalletLogPrintf("Setting spkMan to active: id = %s, type = %s, internal = %s\n", id.ToString(), FormatOutputType(type), internal ? "true" : "false");
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();
@@ -3418,7 +3495,7 @@ void CWallet::DeactivateScriptPubKeyMan(uint256 id, OutputType type, bool intern
{
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));
+ WalletLogPrintf("Deactivate spkMan: id = %s, type = %s, internal = %s\n", id.ToString(), FormatOutputType(type), internal ? "true" : "false");
WalletBatch batch(GetDatabase());
if (!batch.EraseActiveScriptPubKeyMan(static_cast<uint8_t>(type), internal)) {
throw std::runtime_error(std::string(__func__) + ": erasing active ScriptPubKeyMan id failed");