aboutsummaryrefslogtreecommitdiff
path: root/src/wallet
diff options
context:
space:
mode:
Diffstat (limited to 'src/wallet')
-rw-r--r--src/wallet/bdb.cpp9
-rw-r--r--src/wallet/init.cpp4
-rw-r--r--src/wallet/interfaces.cpp4
-rw-r--r--src/wallet/receive.cpp7
-rw-r--r--src/wallet/rpc/addresses.cpp4
-rw-r--r--src/wallet/rpc/backup.cpp14
-rw-r--r--src/wallet/rpc/transactions.cpp8
-rw-r--r--src/wallet/rpc/wallet.cpp221
-rw-r--r--src/wallet/scriptpubkeyman.cpp91
-rw-r--r--src/wallet/scriptpubkeyman.h8
-rw-r--r--src/wallet/sqlite.cpp4
-rw-r--r--src/wallet/test/db_tests.cpp37
-rw-r--r--src/wallet/test/fuzz/coinselection.cpp2
-rw-r--r--src/wallet/test/util.cpp3
-rw-r--r--src/wallet/test/util.h4
-rw-r--r--src/wallet/test/wallet_tests.cpp22
-rw-r--r--src/wallet/test/walletload_tests.cpp1
-rw-r--r--src/wallet/transaction.cpp2
-rw-r--r--src/wallet/transaction.h27
-rw-r--r--src/wallet/wallet.cpp286
-rw-r--r--src/wallet/wallet.h30
-rw-r--r--src/wallet/walletdb.cpp147
-rw-r--r--src/wallet/walletdb.h16
-rw-r--r--src/wallet/walletutil.cpp56
-rw-r--r--src/wallet/walletutil.h2
25 files changed, 718 insertions, 291 deletions
diff --git a/src/wallet/bdb.cpp b/src/wallet/bdb.cpp
index cbf6c9b1ea..38cca32f80 100644
--- a/src/wallet/bdb.cpp
+++ b/src/wallet/bdb.cpp
@@ -887,7 +887,12 @@ bool BerkeleyBatch::HasKey(DataStream&& key)
bool BerkeleyBatch::ErasePrefix(Span<const std::byte> prefix)
{
- if (!TxnBegin()) return false;
+ // Because this function erases records one by one, ensure that it is executed within a txn context.
+ // Otherwise, consistency is at risk; it's possible that certain records are removed while others
+ // remain due to an internal failure during the procedure.
+ // Additionally, the Dbc::del() cursor delete call below would fail without an active transaction.
+ if (!Assume(activeTxn)) return false;
+
auto cursor{std::make_unique<BerkeleyCursor>(m_database, *this)};
// const_cast is safe below even though prefix_key is an in/out parameter,
// because we are not using the DB_DBT_USERMEM flag, so BDB will allocate
@@ -901,7 +906,7 @@ bool BerkeleyBatch::ErasePrefix(Span<const std::byte> prefix)
ret = cursor->dbc()->del(0);
}
cursor.reset();
- return TxnCommit() && (ret == 0 || ret == DB_NOTFOUND);
+ return ret == 0 || ret == DB_NOTFOUND;
}
void BerkeleyDatabase::AddRef()
diff --git a/src/wallet/init.cpp b/src/wallet/init.cpp
index 088343458c..f151fad740 100644
--- a/src/wallet/init.cpp
+++ b/src/wallet/init.cpp
@@ -3,6 +3,10 @@
// Distributed under the MIT software license, see the accompanying
// file COPYING or http://www.opensource.org/licenses/mit-license.php.
+#if defined(HAVE_CONFIG_H)
+#include <config/bitcoin-config.h>
+#endif
+
#include <common/args.h>
#include <init.h>
#include <interfaces/chain.h>
diff --git a/src/wallet/interfaces.cpp b/src/wallet/interfaces.cpp
index d15273dfc9..d33e6f3873 100644
--- a/src/wallet/interfaces.cpp
+++ b/src/wallet/interfaces.cpp
@@ -92,7 +92,7 @@ WalletTxStatus MakeWalletTxStatus(const CWallet& wallet, const CWalletTx& wtx)
WalletTxStatus result;
result.block_height =
wtx.state<TxStateConfirmed>() ? wtx.state<TxStateConfirmed>()->confirmed_block_height :
- wtx.state<TxStateConflicted>() ? wtx.state<TxStateConflicted>()->conflicting_block_height :
+ wtx.state<TxStateBlockConflicted>() ? wtx.state<TxStateBlockConflicted>()->conflicting_block_height :
std::numeric_limits<int>::max();
result.blocks_to_maturity = wallet.GetTxBlocksToMaturity(wtx);
result.depth_in_main_chain = wallet.GetTxDepthInMainChain(wtx);
@@ -101,7 +101,7 @@ WalletTxStatus MakeWalletTxStatus(const CWallet& wallet, const CWalletTx& wtx)
result.is_trusted = CachedTxIsTrusted(wallet, wtx);
result.is_abandoned = wtx.isAbandoned();
result.is_coinbase = wtx.IsCoinBase();
- result.is_in_main_chain = wallet.IsTxInMainChain(wtx);
+ result.is_in_main_chain = wtx.isConfirmed();
return result;
}
diff --git a/src/wallet/receive.cpp b/src/wallet/receive.cpp
index b9d8d9abc9..ea3ffff549 100644
--- a/src/wallet/receive.cpp
+++ b/src/wallet/receive.cpp
@@ -149,7 +149,7 @@ CAmount CachedTxGetImmatureCredit(const CWallet& wallet, const CWalletTx& wtx, c
{
AssertLockHeld(wallet.cs_wallet);
- if (wallet.IsTxImmatureCoinBase(wtx) && wallet.IsTxInMainChain(wtx)) {
+ if (wallet.IsTxImmatureCoinBase(wtx) && wtx.isConfirmed()) {
return GetCachableAmount(wallet, wtx, CWalletTx::IMMATURE_CREDIT, filter);
}
@@ -256,9 +256,8 @@ bool CachedTxIsFromMe(const CWallet& wallet, const CWalletTx& wtx, const isminef
bool CachedTxIsTrusted(const CWallet& wallet, const CWalletTx& wtx, std::set<uint256>& trusted_parents)
{
AssertLockHeld(wallet.cs_wallet);
- int nDepth = wallet.GetTxDepthInMainChain(wtx);
- if (nDepth >= 1) return true;
- if (nDepth < 0) return false;
+ if (wtx.isConfirmed()) return true;
+ if (wtx.isBlockConflicted()) return false;
// using wtx's cached debit
if (!wallet.m_spend_zero_conf_change || !CachedTxIsFromMe(wallet, wtx, ISMINE_ALL)) return false;
diff --git a/src/wallet/rpc/addresses.cpp b/src/wallet/rpc/addresses.cpp
index e9b93afc30..acaa2d8b15 100644
--- a/src/wallet/rpc/addresses.cpp
+++ b/src/wallet/rpc/addresses.cpp
@@ -2,6 +2,10 @@
// Distributed under the MIT software license, see the accompanying
// file COPYING or http://www.opensource.org/licenses/mit-license.php.
+#if defined(HAVE_CONFIG_H)
+#include <config/bitcoin-config.h>
+#endif
+
#include <core_io.h>
#include <key_io.h>
#include <rpc/util.h>
diff --git a/src/wallet/rpc/backup.cpp b/src/wallet/rpc/backup.cpp
index 99c548bf1d..5167e986b1 100644
--- a/src/wallet/rpc/backup.cpp
+++ b/src/wallet/rpc/backup.cpp
@@ -2,6 +2,10 @@
// Distributed under the MIT software license, see the accompanying
// file COPYING or http://www.opensource.org/licenses/mit-license.php.
+#if defined(HAVE_CONFIG_H)
+#include <config/bitcoin-config.h>
+#endif
+
#include <chain.h>
#include <clientversion.h>
#include <core_io.h>
@@ -394,14 +398,8 @@ RPCHelpMan removeprunedfunds()
uint256 hash(ParseHashV(request.params[0], "txid"));
std::vector<uint256> vHash;
vHash.push_back(hash);
- std::vector<uint256> vHashOut;
-
- if (pwallet->ZapSelectTx(vHash, vHashOut) != DBErrors::LOAD_OK) {
- throw JSONRPCError(RPC_WALLET_ERROR, "Could not properly delete the transaction.");
- }
-
- if(vHashOut.empty()) {
- throw JSONRPCError(RPC_INVALID_PARAMETER, "Transaction does not exist in wallet.");
+ if (auto res = pwallet->RemoveTxs(vHash); !res) {
+ throw JSONRPCError(RPC_WALLET_ERROR, util::ErrorString(res).original);
}
return UniValue::VNULL;
diff --git a/src/wallet/rpc/transactions.cpp b/src/wallet/rpc/transactions.cpp
index e6c021d426..05b340995d 100644
--- a/src/wallet/rpc/transactions.cpp
+++ b/src/wallet/rpc/transactions.cpp
@@ -40,6 +40,10 @@ static void WalletTxToJSON(const CWallet& wallet, const CWalletTx& wtx, UniValue
for (const uint256& conflict : wallet.GetTxConflicts(wtx))
conflicts.push_back(conflict.GetHex());
entry.pushKV("walletconflicts", conflicts);
+ UniValue mempool_conflicts(UniValue::VARR);
+ for (const Txid& mempool_conflict : wtx.mempool_conflicts)
+ mempool_conflicts.push_back(mempool_conflict.GetHex());
+ entry.pushKV("mempoolconflicts", mempool_conflicts);
entry.pushKV("time", wtx.GetTxTime());
entry.pushKV("timereceived", int64_t{wtx.nTimeReceived});
@@ -417,6 +421,10 @@ static std::vector<RPCResult> TransactionDescriptionString()
}},
{RPCResult::Type::STR_HEX, "replaced_by_txid", /*optional=*/true, "Only if 'category' is 'send'. The txid if this tx was replaced."},
{RPCResult::Type::STR_HEX, "replaces_txid", /*optional=*/true, "Only if 'category' is 'send'. The txid if this tx replaces another."},
+ {RPCResult::Type::ARR, "mempoolconflicts", "Transactions that directly conflict with either this transaction or an ancestor transaction",
+ {
+ {RPCResult::Type::STR_HEX, "txid", "The transaction id."},
+ }},
{RPCResult::Type::STR, "to", /*optional=*/true, "If a comment to is associated with the transaction."},
{RPCResult::Type::NUM_TIME, "time", "The transaction time expressed in " + UNIX_EPOCH_TIME + "."},
{RPCResult::Type::NUM_TIME, "timereceived", "The time received expressed in " + UNIX_EPOCH_TIME + "."},
diff --git a/src/wallet/rpc/wallet.cpp b/src/wallet/rpc/wallet.cpp
index 391153a2a1..a684d4e191 100644
--- a/src/wallet/rpc/wallet.cpp
+++ b/src/wallet/rpc/wallet.cpp
@@ -3,6 +3,10 @@
// Distributed under the MIT software license, see the accompanying
// file COPYING or http://www.opensource.org/licenses/mit-license.php.
+#if defined(HAVE_CONFIG_H)
+#include <config/bitcoin-config.h>
+#endif
+
#include <core_io.h>
#include <key_io.h>
#include <rpc/server.h>
@@ -748,13 +752,13 @@ RPCHelpMan simulaterawtransaction()
static RPCHelpMan migratewallet()
{
return RPCHelpMan{"migratewallet",
- "EXPERIMENTAL warning: This call may not work as expected and may be changed in future releases\n"
"\nMigrate the wallet to a descriptor wallet.\n"
"A new wallet backup will need to be made.\n"
"\nThe migration process will create a backup of the wallet before migrating. This backup\n"
"file will be named <wallet name>-<timestamp>.legacy.bak and can be found in the directory\n"
"for this wallet. In the event of an incorrect migration, the backup can be restored using restorewallet."
- "\nEncrypted wallets must have the passphrase provided as an argument to this call.",
+ "\nEncrypted wallets must have the passphrase provided as an argument to this call.\n"
+ "\nThis RPC may take a long time to complete. Increasing the RPC client timeout is recommended.",
{
{"wallet_name", RPCArg::Type::STR, RPCArg::DefaultHint{"the wallet name from the RPC endpoint"}, "The name of the wallet to migrate. If provided both here and in the RPC endpoint, the two must be identical."},
{"passphrase", RPCArg::Type::STR, RPCArg::Optional::OMITTED, "The wallet passphrase"},
@@ -813,6 +817,217 @@ static RPCHelpMan migratewallet()
};
}
+RPCHelpMan gethdkeys()
+{
+ return RPCHelpMan{
+ "gethdkeys",
+ "\nList all BIP 32 HD keys in the wallet and which descriptors use them.\n",
+ {
+ {"options", RPCArg::Type::OBJ_NAMED_PARAMS, RPCArg::Optional::OMITTED, "", {
+ {"active_only", RPCArg::Type::BOOL, RPCArg::Default{false}, "Show the keys for only active descriptors"},
+ {"private", RPCArg::Type::BOOL, RPCArg::Default{false}, "Show private keys"}
+ }},
+ },
+ RPCResult{RPCResult::Type::ARR, "", "", {
+ {
+ {RPCResult::Type::OBJ, "", "", {
+ {RPCResult::Type::STR, "xpub", "The extended public key"},
+ {RPCResult::Type::BOOL, "has_private", "Whether the wallet has the private key for this xpub"},
+ {RPCResult::Type::STR, "xprv", /*optional=*/true, "The extended private key if \"private\" is true"},
+ {RPCResult::Type::ARR, "descriptors", "Array of descriptor objects that use this HD key",
+ {
+ {RPCResult::Type::OBJ, "", "", {
+ {RPCResult::Type::STR, "desc", "Descriptor string representation"},
+ {RPCResult::Type::BOOL, "active", "Whether this descriptor is currently used to generate new addresses"},
+ }},
+ }},
+ }},
+ }
+ }},
+ RPCExamples{
+ HelpExampleCli("gethdkeys", "") + HelpExampleRpc("gethdkeys", "")
+ + HelpExampleCliNamed("gethdkeys", {{"active_only", "true"}, {"private", "true"}}) + HelpExampleRpcNamed("gethdkeys", {{"active_only", "true"}, {"private", "true"}})
+ },
+ [&](const RPCHelpMan& self, const JSONRPCRequest& request) -> UniValue
+ {
+ const std::shared_ptr<const CWallet> wallet = GetWalletForJSONRPCRequest(request);
+ if (!wallet) return UniValue::VNULL;
+
+ if (!wallet->IsWalletFlagSet(WALLET_FLAG_DESCRIPTORS)) {
+ throw JSONRPCError(RPC_WALLET_ERROR, "gethdkeys is not available for non-descriptor wallets");
+ }
+
+ LOCK(wallet->cs_wallet);
+
+ UniValue options{request.params[0].isNull() ? UniValue::VOBJ : request.params[0]};
+ const bool active_only{options.exists("active_only") ? options["active_only"].get_bool() : false};
+ const bool priv{options.exists("private") ? options["private"].get_bool() : false};
+ if (priv) {
+ EnsureWalletIsUnlocked(*wallet);
+ }
+
+
+ std::set<ScriptPubKeyMan*> spkms;
+ if (active_only) {
+ spkms = wallet->GetActiveScriptPubKeyMans();
+ } else {
+ spkms = wallet->GetAllScriptPubKeyMans();
+ }
+
+ std::map<CExtPubKey, std::set<std::tuple<std::string, bool, bool>>> wallet_xpubs;
+ std::map<CExtPubKey, CExtKey> wallet_xprvs;
+ for (auto* spkm : spkms) {
+ auto* desc_spkm{dynamic_cast<DescriptorScriptPubKeyMan*>(spkm)};
+ CHECK_NONFATAL(desc_spkm);
+ LOCK(desc_spkm->cs_desc_man);
+ WalletDescriptor w_desc = desc_spkm->GetWalletDescriptor();
+
+ // Retrieve the pubkeys from the descriptor
+ std::set<CPubKey> desc_pubkeys;
+ std::set<CExtPubKey> desc_xpubs;
+ w_desc.descriptor->GetPubKeys(desc_pubkeys, desc_xpubs);
+ for (const CExtPubKey& xpub : desc_xpubs) {
+ std::string desc_str;
+ bool ok = desc_spkm->GetDescriptorString(desc_str, false);
+ CHECK_NONFATAL(ok);
+ wallet_xpubs[xpub].emplace(desc_str, wallet->IsActiveScriptPubKeyMan(*spkm), desc_spkm->HasPrivKey(xpub.pubkey.GetID()));
+ if (std::optional<CKey> key = priv ? desc_spkm->GetKey(xpub.pubkey.GetID()) : std::nullopt) {
+ wallet_xprvs[xpub] = CExtKey(xpub, *key);
+ }
+ }
+ }
+
+ UniValue response(UniValue::VARR);
+ for (const auto& [xpub, descs] : wallet_xpubs) {
+ bool has_xprv = false;
+ UniValue descriptors(UniValue::VARR);
+ for (const auto& [desc, active, has_priv] : descs) {
+ UniValue d(UniValue::VOBJ);
+ d.pushKV("desc", desc);
+ d.pushKV("active", active);
+ has_xprv |= has_priv;
+
+ descriptors.push_back(std::move(d));
+ }
+ UniValue xpub_info(UniValue::VOBJ);
+ xpub_info.pushKV("xpub", EncodeExtPubKey(xpub));
+ xpub_info.pushKV("has_private", has_xprv);
+ if (priv) {
+ xpub_info.pushKV("xprv", EncodeExtKey(wallet_xprvs.at(xpub)));
+ }
+ xpub_info.pushKV("descriptors", std::move(descriptors));
+
+ response.push_back(std::move(xpub_info));
+ }
+
+ return response;
+ },
+ };
+}
+
+static RPCHelpMan createwalletdescriptor()
+{
+ return RPCHelpMan{"createwalletdescriptor",
+ "Creates the wallet's descriptor for the given address type. "
+ "The address type must be one that the wallet does not already have a descriptor for."
+ + HELP_REQUIRING_PASSPHRASE,
+ {
+ {"type", RPCArg::Type::STR, RPCArg::Optional::NO, "The address type the descriptor will produce. Options are \"legacy\", \"p2sh-segwit\", \"bech32\", and \"bech32m\"."},
+ {"options", RPCArg::Type::OBJ_NAMED_PARAMS, RPCArg::Optional::OMITTED, "", {
+ {"internal", RPCArg::Type::BOOL, RPCArg::DefaultHint{"Both external and internal will be generated unless this parameter is specified"}, "Whether to only make one descriptor that is internal (if parameter is true) or external (if parameter is false)"},
+ {"hdkey", RPCArg::Type::STR, RPCArg::DefaultHint{"The HD key used by all other active descriptors"}, "The HD key that the wallet knows the private key of, listed using 'gethdkeys', to use for this descriptor's key"},
+ }},
+ },
+ RPCResult{
+ RPCResult::Type::OBJ, "", "",
+ {
+ {RPCResult::Type::ARR, "descs", "The public descriptors that were added to the wallet",
+ {{RPCResult::Type::STR, "", ""}}
+ }
+ },
+ },
+ RPCExamples{
+ HelpExampleCli("createwalletdescriptor", "bech32m")
+ + HelpExampleRpc("createwalletdescriptor", "bech32m")
+ },
+ [&](const RPCHelpMan& self, const JSONRPCRequest& request) -> UniValue
+ {
+ std::shared_ptr<CWallet> const pwallet = GetWalletForJSONRPCRequest(request);
+ if (!pwallet) return UniValue::VNULL;
+
+ // Make sure wallet is a descriptor wallet
+ if (!pwallet->IsWalletFlagSet(WALLET_FLAG_DESCRIPTORS)) {
+ throw JSONRPCError(RPC_WALLET_ERROR, "createwalletdescriptor is not available for non-descriptor wallets");
+ }
+
+ std::optional<OutputType> output_type = ParseOutputType(request.params[0].get_str());
+ if (!output_type) {
+ throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, strprintf("Unknown address type '%s'", request.params[0].get_str()));
+ }
+
+ UniValue options{request.params[1].isNull() ? UniValue::VOBJ : request.params[1]};
+ UniValue internal_only{options["internal"]};
+ UniValue hdkey{options["hdkey"]};
+
+ std::vector<bool> internals;
+ if (internal_only.isNull()) {
+ internals.push_back(false);
+ internals.push_back(true);
+ } else {
+ internals.push_back(internal_only.get_bool());
+ }
+
+ LOCK(pwallet->cs_wallet);
+ EnsureWalletIsUnlocked(*pwallet);
+
+ CExtPubKey xpub;
+ if (hdkey.isNull()) {
+ std::set<CExtPubKey> active_xpubs = pwallet->GetActiveHDPubKeys();
+ if (active_xpubs.size() != 1) {
+ throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Unable to determine which HD key to use from active descriptors. Please specify with 'hdkey'");
+ }
+ xpub = *active_xpubs.begin();
+ } else {
+ xpub = DecodeExtPubKey(hdkey.get_str());
+ if (!xpub.pubkey.IsValid()) {
+ throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Unable to parse HD key. Please provide a valid xpub");
+ }
+ }
+
+ std::optional<CKey> key = pwallet->GetKey(xpub.pubkey.GetID());
+ if (!key) {
+ throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, strprintf("Private key for %s is not known", EncodeExtPubKey(xpub)));
+ }
+ CExtKey active_hdkey(xpub, *key);
+
+ std::vector<std::reference_wrapper<DescriptorScriptPubKeyMan>> spkms;
+ WalletBatch batch{pwallet->GetDatabase()};
+ for (bool internal : internals) {
+ WalletDescriptor w_desc = GenerateWalletDescriptor(xpub, *output_type, internal);
+ uint256 w_id = DescriptorID(*w_desc.descriptor);
+ if (!pwallet->GetScriptPubKeyMan(w_id)) {
+ spkms.emplace_back(pwallet->SetupDescriptorScriptPubKeyMan(batch, active_hdkey, *output_type, internal));
+ }
+ }
+ if (spkms.empty()) {
+ throw JSONRPCError(RPC_WALLET_ERROR, "Descriptor already exists");
+ }
+
+ // Fetch each descspkm from the wallet in order to get the descriptor strings
+ UniValue descs{UniValue::VARR};
+ for (const auto& spkm : spkms) {
+ std::string desc_str;
+ bool ok = spkm.get().GetDescriptorString(desc_str, false);
+ CHECK_NONFATAL(ok);
+ descs.push_back(desc_str);
+ }
+ UniValue out{UniValue::VOBJ};
+ out.pushKV("descs", std::move(descs));
+ return out;
+ }
+ };
+}
+
// addresses
RPCHelpMan getaddressinfo();
RPCHelpMan getnewaddress();
@@ -896,6 +1111,7 @@ Span<const CRPCCommand> GetWalletRPCCommands()
{"wallet", &bumpfee},
{"wallet", &psbtbumpfee},
{"wallet", &createwallet},
+ {"wallet", &createwalletdescriptor},
{"wallet", &restorewallet},
{"wallet", &dumpprivkey},
{"wallet", &dumpwallet},
@@ -903,6 +1119,7 @@ Span<const CRPCCommand> GetWalletRPCCommands()
{"wallet", &getaddressesbylabel},
{"wallet", &getaddressinfo},
{"wallet", &getbalance},
+ {"wallet", &gethdkeys},
{"wallet", &getnewaddress},
{"wallet", &getrawchangeaddress},
{"wallet", &getreceivedbyaddress},
diff --git a/src/wallet/scriptpubkeyman.cpp b/src/wallet/scriptpubkeyman.cpp
index e07771b17f..59171f6db7 100644
--- a/src/wallet/scriptpubkeyman.cpp
+++ b/src/wallet/scriptpubkeyman.cpp
@@ -11,6 +11,7 @@
#include <script/sign.h>
#include <script/solver.h>
#include <util/bip32.h>
+#include <util/check.h>
#include <util/strencodings.h>
#include <util/string.h>
#include <util/time.h>
@@ -1288,6 +1289,9 @@ bool LegacyScriptPubKeyMan::TopUp(unsigned int kpSize)
}
if (!batch.TxnCommit()) throw std::runtime_error(strprintf("Error during keypool top up. Cannot commit changes for wallet %s", m_storage.GetDisplayName()));
NotifyCanGetAddressesChanged();
+ // Note: Unlike with DescriptorSPKM, LegacySPKM does not need to call
+ // m_storage.TopUpCallback() as we do not know what new scripts the LegacySPKM is
+ // watching for. CWallet's scriptPubKey cache is not used for LegacySPKMs.
return true;
}
@@ -2140,6 +2144,36 @@ std::map<CKeyID, CKey> DescriptorScriptPubKeyMan::GetKeys() const
return m_map_keys;
}
+bool DescriptorScriptPubKeyMan::HasPrivKey(const CKeyID& keyid) const
+{
+ AssertLockHeld(cs_desc_man);
+ return m_map_keys.contains(keyid) || m_map_crypted_keys.contains(keyid);
+}
+
+std::optional<CKey> DescriptorScriptPubKeyMan::GetKey(const CKeyID& keyid) const
+{
+ AssertLockHeld(cs_desc_man);
+ if (m_storage.HasEncryptionKeys() && !m_storage.IsLocked()) {
+ const auto& it = m_map_crypted_keys.find(keyid);
+ if (it == m_map_crypted_keys.end()) {
+ return std::nullopt;
+ }
+ const std::vector<unsigned char>& crypted_secret = it->second.second;
+ CKey key;
+ if (!Assume(m_storage.WithEncryptionKey([&](const CKeyingMaterial& encryption_key) {
+ return DecryptKey(encryption_key, crypted_secret, it->second.first, key);
+ }))) {
+ return std::nullopt;
+ }
+ return key;
+ }
+ const auto& it = m_map_keys.find(keyid);
+ if (it == m_map_keys.end()) {
+ return std::nullopt;
+ }
+ return it->second;
+}
+
bool DescriptorScriptPubKeyMan::TopUp(unsigned int size)
{
WalletBatch batch(m_storage.GetDatabase());
@@ -2152,6 +2186,7 @@ bool DescriptorScriptPubKeyMan::TopUp(unsigned int size)
bool DescriptorScriptPubKeyMan::TopUpWithDB(WalletBatch& batch, unsigned int size)
{
LOCK(cs_desc_man);
+ std::set<CScript> new_spks;
unsigned int target_size;
if (size > 0) {
target_size = size;
@@ -2182,6 +2217,7 @@ bool DescriptorScriptPubKeyMan::TopUpWithDB(WalletBatch& batch, unsigned int siz
if (!m_wallet_descriptor.descriptor->Expand(i, provider, scripts_temp, out_keys, &temp_cache)) return false;
}
// Add all of the scriptPubKeys to the scriptPubKey set
+ new_spks.insert(scripts_temp.begin(), scripts_temp.end());
for (const CScript& script : scripts_temp) {
m_map_script_pub_keys[script] = i;
}
@@ -2207,6 +2243,7 @@ bool DescriptorScriptPubKeyMan::TopUpWithDB(WalletBatch& batch, unsigned int siz
// By this point, the cache size should be the size of the entire range
assert(m_wallet_descriptor.range_end - 1 == m_max_cached_index);
+ m_storage.TopUpCallback(new_spks, this);
NotifyCanGetAddressesChanged();
return true;
}
@@ -2290,55 +2327,7 @@ bool DescriptorScriptPubKeyMan::SetupDescriptorGeneration(WalletBatch& batch, co
return false;
}
- int64_t creation_time = GetTime();
-
- std::string xpub = EncodeExtPubKey(master_key.Neuter());
-
- // Build descriptor string
- std::string desc_prefix;
- std::string desc_suffix = "/*)";
- switch (addr_type) {
- case OutputType::LEGACY: {
- desc_prefix = "pkh(" + xpub + "/44h";
- break;
- }
- case OutputType::P2SH_SEGWIT: {
- desc_prefix = "sh(wpkh(" + xpub + "/49h";
- desc_suffix += ")";
- break;
- }
- case OutputType::BECH32: {
- desc_prefix = "wpkh(" + xpub + "/84h";
- break;
- }
- case OutputType::BECH32M: {
- desc_prefix = "tr(" + xpub + "/86h";
- break;
- }
- case OutputType::UNKNOWN: {
- // We should never have a DescriptorScriptPubKeyMan for an UNKNOWN OutputType,
- // so if we get to this point something is wrong
- assert(false);
- }
- } // no default case, so the compiler can warn about missing cases
- assert(!desc_prefix.empty());
-
- // Mainnet derives at 0', testnet and regtest derive at 1'
- if (Params().IsTestChain()) {
- desc_prefix += "/1h";
- } else {
- desc_prefix += "/0h";
- }
-
- std::string internal_path = internal ? "/1" : "/0";
- std::string desc_str = desc_prefix + "/0h" + internal_path + desc_suffix;
-
- // Make the descriptor
- FlatSigningProvider keys;
- std::string error;
- std::unique_ptr<Descriptor> desc = Parse(desc_str, keys, error, false);
- WalletDescriptor w_desc(std::move(desc), creation_time, 0, 0, 0);
- m_wallet_descriptor = w_desc;
+ m_wallet_descriptor = GenerateWalletDescriptor(master_key.Neuter(), addr_type, internal);
// Store the master private key, and descriptor
if (!AddDescriptorKeyWithDB(batch, master_key.key, master_key.key.GetPubKey())) {
@@ -2620,6 +2609,7 @@ uint256 DescriptorScriptPubKeyMan::GetID() const
void DescriptorScriptPubKeyMan::SetCache(const DescriptorCache& cache)
{
LOCK(cs_desc_man);
+ std::set<CScript> new_spks;
m_wallet_descriptor.cache = cache;
for (int32_t i = m_wallet_descriptor.range_start; i < m_wallet_descriptor.range_end; ++i) {
FlatSigningProvider out_keys;
@@ -2628,6 +2618,7 @@ void DescriptorScriptPubKeyMan::SetCache(const DescriptorCache& cache)
throw std::runtime_error("Error: Unable to expand wallet descriptor from cache");
}
// Add all of the scriptPubKeys to the scriptPubKey set
+ new_spks.insert(scripts_temp.begin(), scripts_temp.end());
for (const CScript& script : scripts_temp) {
if (m_map_script_pub_keys.count(script) != 0) {
throw std::runtime_error(strprintf("Error: Already loaded script at index %d as being at index %d", i, m_map_script_pub_keys[script]));
@@ -2645,6 +2636,8 @@ void DescriptorScriptPubKeyMan::SetCache(const DescriptorCache& cache)
}
m_max_cached_index++;
}
+ // Make sure the wallet knows about our new spks
+ m_storage.TopUpCallback(new_spks, this);
}
bool DescriptorScriptPubKeyMan::AddKey(const CKeyID& key_id, const CKey& key)
diff --git a/src/wallet/scriptpubkeyman.h b/src/wallet/scriptpubkeyman.h
index 1c99e5ffcd..4575881d96 100644
--- a/src/wallet/scriptpubkeyman.h
+++ b/src/wallet/scriptpubkeyman.h
@@ -31,6 +31,7 @@ struct bilingual_str;
namespace wallet {
struct MigrationData;
+class ScriptPubKeyMan;
// Wallet storage things that ScriptPubKeyMans need in order to be able to store things to the wallet database.
// It provides access to things that are part of the entire wallet and not specific to a ScriptPubKeyMan such as
@@ -51,6 +52,8 @@ public:
virtual bool WithEncryptionKey(std::function<bool (const CKeyingMaterial&)> cb) const = 0;
virtual bool HasEncryptionKeys() const = 0;
virtual bool IsLocked() const = 0;
+ //! Callback function for after TopUp completes containing any scripts that were added by a SPKMan
+ virtual void TopUpCallback(const std::set<CScript>&, ScriptPubKeyMan*) = 0;
};
//! Constant representing an unknown spkm creation time
@@ -630,6 +633,9 @@ public:
bool SetupDescriptorGeneration(WalletBatch& batch, const CExtKey& master_key, OutputType addr_type, bool internal);
bool HavePrivateKeys() const override;
+ bool HasPrivKey(const CKeyID& keyid) const EXCLUSIVE_LOCKS_REQUIRED(cs_desc_man);
+ //! Retrieve the particular key if it is available. Returns nullopt if the key is not in the wallet, or if the wallet is locked.
+ std::optional<CKey> GetKey(const CKeyID& keyid) const EXCLUSIVE_LOCKS_REQUIRED(cs_desc_man);
std::optional<int64_t> GetOldestKeyPoolTime() const override;
unsigned int GetKeyPoolSize() const override;
@@ -666,7 +672,7 @@ public:
std::unordered_set<CScript, SaltedSipHasher> GetScriptPubKeys(int32_t minimum_index) const;
int32_t GetEndRange() const;
- bool GetDescriptorString(std::string& out, const bool priv) const;
+ [[nodiscard]] bool GetDescriptorString(std::string& out, const bool priv) const;
void UpgradeDescriptorCache();
};
diff --git a/src/wallet/sqlite.cpp b/src/wallet/sqlite.cpp
index 2cbeedbde6..34f18bf0b1 100644
--- a/src/wallet/sqlite.cpp
+++ b/src/wallet/sqlite.cpp
@@ -2,6 +2,10 @@
// Distributed under the MIT software license, see the accompanying
// file COPYING or http://www.opensource.org/licenses/mit-license.php.
+#if defined(HAVE_CONFIG_H)
+#include <config/bitcoin-config.h>
+#endif
+
#include <wallet/sqlite.h>
#include <chainparams.h>
diff --git a/src/wallet/test/db_tests.cpp b/src/wallet/test/db_tests.cpp
index adbbb94318..f783424df8 100644
--- a/src/wallet/test/db_tests.cpp
+++ b/src/wallet/test/db_tests.cpp
@@ -2,6 +2,10 @@
// Distributed under the MIT software license, see the accompanying
// file COPYING or http://www.opensource.org/licenses/mit-license.php.
+#if defined(HAVE_CONFIG_H)
+#include <config/bitcoin-config.h>
+#endif
+
#include <boost/test/unit_test.hpp>
#include <test/util/setup_common.h>
@@ -228,6 +232,39 @@ BOOST_AUTO_TEST_CASE(db_availability_after_write_error)
}
}
+// Verify 'ErasePrefix' functionality using db keys similar to the ones used by the wallet.
+// Keys are in the form of std::pair<TYPE, ENTRY_ID>
+BOOST_AUTO_TEST_CASE(erase_prefix)
+{
+ const std::string key = "key";
+ const std::string key2 = "key2";
+ const std::string value = "value";
+ const std::string value2 = "value_2";
+ auto make_key = [](std::string type, std::string id) { return std::make_pair(type, id); };
+
+ for (const auto& database : TestDatabases(m_path_root)) {
+ std::unique_ptr<DatabaseBatch> batch = database->MakeBatch();
+
+ // Write two entries with the same key type prefix, a third one with a different prefix
+ // and a fourth one with the type-id values inverted
+ BOOST_CHECK(batch->Write(make_key(key, value), value));
+ BOOST_CHECK(batch->Write(make_key(key, value2), value2));
+ BOOST_CHECK(batch->Write(make_key(key2, value), value));
+ BOOST_CHECK(batch->Write(make_key(value, key), value));
+
+ // Erase the ones with the same prefix and verify result
+ BOOST_CHECK(batch->TxnBegin());
+ BOOST_CHECK(batch->ErasePrefix(DataStream() << key));
+ BOOST_CHECK(batch->TxnCommit());
+
+ BOOST_CHECK(!batch->Exists(make_key(key, value)));
+ BOOST_CHECK(!batch->Exists(make_key(key, value2)));
+ // Also verify that entries with a different prefix were not erased
+ BOOST_CHECK(batch->Exists(make_key(key2, value)));
+ BOOST_CHECK(batch->Exists(make_key(value, key)));
+ }
+}
+
#ifdef USE_SQLITE
// Test-only statement execution error
diff --git a/src/wallet/test/fuzz/coinselection.cpp b/src/wallet/test/fuzz/coinselection.cpp
index 3ffeecdf34..297432de9e 100644
--- a/src/wallet/test/fuzz/coinselection.cpp
+++ b/src/wallet/test/fuzz/coinselection.cpp
@@ -158,7 +158,7 @@ FUZZ_TARGET(coin_grinder_is_optimal)
// Only make UTXOs with positive effective value
const CAmount input_fee = coin_params.m_effective_feerate.GetFee(n_input_bytes);
// Ensure that each UTXO has at least an effective value of 1 sat
- const CAmount eff_value{fuzzed_data_provider.ConsumeIntegralInRange<CAmount>(1, MAX_MONEY - max_spendable - max_output_groups + group_pos.size())};
+ const CAmount eff_value{fuzzed_data_provider.ConsumeIntegralInRange<CAmount>(1, MAX_MONEY + group_pos.size() - max_spendable - max_output_groups)};
const CAmount amount{eff_value + input_fee};
std::vector<COutput> temp_utxo_pool;
diff --git a/src/wallet/test/util.cpp b/src/wallet/test/util.cpp
index cbf3ccd1ec..49d206f409 100644
--- a/src/wallet/test/util.cpp
+++ b/src/wallet/test/util.cpp
@@ -71,7 +71,8 @@ std::shared_ptr<CWallet> TestLoadWallet(WalletContext& context)
void TestUnloadWallet(std::shared_ptr<CWallet>&& wallet)
{
- SyncWithValidationInterfaceQueue();
+ // Calls SyncWithValidationInterfaceQueue
+ wallet->chain().waitForNotificationsIfTipChanged({});
wallet->m_chain_notifications_handler.reset();
UnloadWallet(std::move(wallet));
}
diff --git a/src/wallet/test/util.h b/src/wallet/test/util.h
index 8bd238648f..9f2974ece6 100644
--- a/src/wallet/test/util.h
+++ b/src/wallet/test/util.h
@@ -5,6 +5,10 @@
#ifndef BITCOIN_WALLET_TEST_UTIL_H
#define BITCOIN_WALLET_TEST_UTIL_H
+#if defined(HAVE_CONFIG_H)
+#include <config/bitcoin-config.h>
+#endif
+
#include <addresstype.h>
#include <wallet/db.h>
diff --git a/src/wallet/test/wallet_tests.cpp b/src/wallet/test/wallet_tests.cpp
index 65297054df..3a67b9a433 100644
--- a/src/wallet/test/wallet_tests.cpp
+++ b/src/wallet/test/wallet_tests.cpp
@@ -445,9 +445,11 @@ BOOST_FIXTURE_TEST_CASE(LoadReceiveRequests, TestingSetup)
auto requests = wallet->GetAddressReceiveRequests();
auto erequests = {"val_rr11", "val_rr20"};
BOOST_CHECK_EQUAL_COLLECTIONS(requests.begin(), requests.end(), std::begin(erequests), std::end(erequests));
- WalletBatch batch{wallet->GetDatabase()};
- BOOST_CHECK(batch.WriteAddressPreviouslySpent(PKHash(), false));
- BOOST_CHECK(batch.EraseAddressData(ScriptHash()));
+ RunWithinTxn(wallet->GetDatabase(), /*process_desc*/"test", [](WalletBatch& batch){
+ BOOST_CHECK(batch.WriteAddressPreviouslySpent(PKHash(), false));
+ BOOST_CHECK(batch.EraseAddressData(ScriptHash()));
+ return true;
+ });
});
TestLoadWallet(name, format, [](std::shared_ptr<CWallet> wallet) EXCLUSIVE_LOCKS_REQUIRED(wallet->cs_wallet) {
BOOST_CHECK(!wallet->IsAddressPreviouslySpent(PKHash()));
@@ -812,7 +814,7 @@ BOOST_FIXTURE_TEST_CASE(CreateWallet, TestChain100Setup)
// transactionAddedToMempool notifications, and create block and mempool
// transactions paying to the wallet
std::promise<void> promise;
- CallFunctionInValidationInterfaceQueue([&promise] {
+ m_node.validation_signals->CallFunctionInValidationInterfaceQueue([&promise] {
promise.get_future().wait();
});
std::string error;
@@ -840,7 +842,7 @@ BOOST_FIXTURE_TEST_CASE(CreateWallet, TestChain100Setup)
// Unblock notification queue and make sure stale blockConnected and
// transactionAddedToMempool events are processed
promise.set_value();
- SyncWithValidationInterfaceQueue();
+ m_node.validation_signals->SyncWithValidationInterfaceQueue();
// AddToWallet events for block_tx and mempool_tx events are counted a
// second time as the notification queue is processed
BOOST_CHECK_EQUAL(addtx_count, 5);
@@ -863,7 +865,7 @@ BOOST_FIXTURE_TEST_CASE(CreateWallet, TestChain100Setup)
m_coinbase_txns.push_back(CreateAndProcessBlock({block_tx}, GetScriptForRawPubKey(coinbaseKey.GetPubKey())).vtx[0]);
mempool_tx = TestSimpleSpend(*m_coinbase_txns[3], 0, coinbaseKey, GetScriptForRawPubKey(key.GetPubKey()));
BOOST_CHECK(m_node.chain->broadcastTransaction(MakeTransactionRef(mempool_tx), DEFAULT_TRANSACTION_MAXFEE, false, error));
- SyncWithValidationInterfaceQueue();
+ m_node.validation_signals->SyncWithValidationInterfaceQueue();
});
wallet = TestLoadWallet(context);
// Since mempool transactions are requested at the end of loading, there will
@@ -888,7 +890,7 @@ BOOST_FIXTURE_TEST_CASE(CreateWalletWithoutChain, BasicTestingSetup)
UnloadWallet(std::move(wallet));
}
-BOOST_FIXTURE_TEST_CASE(ZapSelectTx, TestChain100Setup)
+BOOST_FIXTURE_TEST_CASE(RemoveTxs, TestChain100Setup)
{
m_args.ForceSetArg("-unsafesqlitesync", "1");
WalletContext context;
@@ -903,7 +905,7 @@ BOOST_FIXTURE_TEST_CASE(ZapSelectTx, TestChain100Setup)
auto block_tx = TestSimpleSpend(*m_coinbase_txns[0], 0, coinbaseKey, GetScriptForRawPubKey(key.GetPubKey()));
CreateAndProcessBlock({block_tx}, GetScriptForRawPubKey(coinbaseKey.GetPubKey()));
- SyncWithValidationInterfaceQueue();
+ m_node.validation_signals->SyncWithValidationInterfaceQueue();
{
auto block_hash = block_tx.GetHash();
@@ -913,8 +915,8 @@ BOOST_FIXTURE_TEST_CASE(ZapSelectTx, TestChain100Setup)
BOOST_CHECK(wallet->HasWalletSpend(prev_tx));
BOOST_CHECK_EQUAL(wallet->mapWallet.count(block_hash), 1u);
- std::vector<uint256> vHashIn{ block_hash }, vHashOut;
- BOOST_CHECK_EQUAL(wallet->ZapSelectTx(vHashIn, vHashOut), DBErrors::LOAD_OK);
+ std::vector<uint256> vHashIn{ block_hash };
+ BOOST_CHECK(wallet->RemoveTxs(vHashIn));
BOOST_CHECK(!wallet->HasWalletSpend(prev_tx));
BOOST_CHECK_EQUAL(wallet->mapWallet.count(block_hash), 0u);
diff --git a/src/wallet/test/walletload_tests.cpp b/src/wallet/test/walletload_tests.cpp
index 3dba2231f0..2e43eda582 100644
--- a/src/wallet/test/walletload_tests.cpp
+++ b/src/wallet/test/walletload_tests.cpp
@@ -34,6 +34,7 @@ public:
std::optional<int64_t> ScriptSize() const override { return {}; }
std::optional<int64_t> MaxSatisfactionWeight(bool) const override { return {}; }
std::optional<int64_t> MaxSatisfactionElems() const override { return {}; }
+ void GetPubKeys(std::set<CPubKey>& pubkeys, std::set<CExtPubKey>& ext_pubs) const override {}
};
BOOST_FIXTURE_TEST_CASE(wallet_load_descriptors, TestingSetup)
diff --git a/src/wallet/transaction.cpp b/src/wallet/transaction.cpp
index 6777257e53..561880482f 100644
--- a/src/wallet/transaction.cpp
+++ b/src/wallet/transaction.cpp
@@ -45,7 +45,7 @@ void CWalletTx::updateState(interfaces::Chain& chain)
};
if (auto* conf = state<TxStateConfirmed>()) {
lookup_block(conf->confirmed_block_hash, conf->confirmed_block_height, m_state);
- } else if (auto* conf = state<TxStateConflicted>()) {
+ } else if (auto* conf = state<TxStateBlockConflicted>()) {
lookup_block(conf->conflicting_block_hash, conf->conflicting_block_height, m_state);
}
}
diff --git a/src/wallet/transaction.h b/src/wallet/transaction.h
index ddeb931112..9c27574103 100644
--- a/src/wallet/transaction.h
+++ b/src/wallet/transaction.h
@@ -43,12 +43,12 @@ struct TxStateInMempool {
};
//! State of rejected transaction that conflicts with a confirmed block.
-struct TxStateConflicted {
+struct TxStateBlockConflicted {
uint256 conflicting_block_hash;
int conflicting_block_height;
- explicit TxStateConflicted(const uint256& block_hash, int height) : conflicting_block_hash(block_hash), conflicting_block_height(height) {}
- std::string toString() const { return strprintf("Conflicted (block=%s, height=%i)", conflicting_block_hash.ToString(), conflicting_block_height); }
+ explicit TxStateBlockConflicted(const uint256& block_hash, int height) : conflicting_block_hash(block_hash), conflicting_block_height(height) {}
+ std::string toString() const { return strprintf("BlockConflicted (block=%s, height=%i)", conflicting_block_hash.ToString(), conflicting_block_height); }
};
//! State of transaction not confirmed or conflicting with a known block and
@@ -75,7 +75,7 @@ struct TxStateUnrecognized {
};
//! All possible CWalletTx states
-using TxState = std::variant<TxStateConfirmed, TxStateInMempool, TxStateConflicted, TxStateInactive, TxStateUnrecognized>;
+using TxState = std::variant<TxStateConfirmed, TxStateInMempool, TxStateBlockConflicted, TxStateInactive, TxStateUnrecognized>;
//! Subset of states transaction sync logic is implemented to handle.
using SyncTxState = std::variant<TxStateConfirmed, TxStateInMempool, TxStateInactive>;
@@ -90,7 +90,7 @@ static inline TxState TxStateInterpretSerialized(TxStateUnrecognized data)
} else if (data.index >= 0) {
return TxStateConfirmed{data.block_hash, /*height=*/-1, data.index};
} else if (data.index == -1) {
- return TxStateConflicted{data.block_hash, /*height=*/-1};
+ return TxStateBlockConflicted{data.block_hash, /*height=*/-1};
}
return data;
}
@@ -102,7 +102,7 @@ static inline uint256 TxStateSerializedBlockHash(const TxState& state)
[](const TxStateInactive& inactive) { return inactive.abandoned ? uint256::ONE : uint256::ZERO; },
[](const TxStateInMempool& in_mempool) { return uint256::ZERO; },
[](const TxStateConfirmed& confirmed) { return confirmed.confirmed_block_hash; },
- [](const TxStateConflicted& conflicted) { return conflicted.conflicting_block_hash; },
+ [](const TxStateBlockConflicted& conflicted) { return conflicted.conflicting_block_hash; },
[](const TxStateUnrecognized& unrecognized) { return unrecognized.block_hash; }
}, state);
}
@@ -114,7 +114,7 @@ static inline int TxStateSerializedIndex(const TxState& state)
[](const TxStateInactive& inactive) { return inactive.abandoned ? -1 : 0; },
[](const TxStateInMempool& in_mempool) { return 0; },
[](const TxStateConfirmed& confirmed) { return confirmed.position_in_block; },
- [](const TxStateConflicted& conflicted) { return -1; },
+ [](const TxStateBlockConflicted& conflicted) { return -1; },
[](const TxStateUnrecognized& unrecognized) { return unrecognized.index; }
}, state);
}
@@ -258,6 +258,14 @@ public:
CTransactionRef tx;
TxState m_state;
+ // Set of mempool transactions that conflict
+ // directly with the transaction, or that conflict
+ // with an ancestor transaction. This set will be
+ // empty if state is InMempool or Confirmed, but
+ // can be nonempty if state is Inactive or
+ // BlockConflicted.
+ std::set<Txid> mempool_conflicts;
+
template<typename Stream>
void Serialize(Stream& s) const
{
@@ -335,9 +343,10 @@ public:
void updateState(interfaces::Chain& chain);
bool isAbandoned() const { return state<TxStateInactive>() && state<TxStateInactive>()->abandoned; }
- bool isConflicted() const { return state<TxStateConflicted>(); }
+ bool isMempoolConflicted() const { return !mempool_conflicts.empty(); }
+ bool isBlockConflicted() const { return state<TxStateBlockConflicted>(); }
bool isInactive() const { return state<TxStateInactive>(); }
- bool isUnconfirmed() const { return !isAbandoned() && !isConflicted() && !isConfirmed(); }
+ bool isUnconfirmed() const { return !isAbandoned() && !isBlockConflicted() && !isMempoolConflicted() && !isConfirmed(); }
bool isConfirmed() const { return state<TxStateConfirmed>(); }
const Txid& GetHash() const LIFETIMEBOUND { return tx->GetHash(); }
const Wtxid& GetWitnessHash() const LIFETIMEBOUND { return tx->GetWitnessHash(); }
diff --git a/src/wallet/wallet.cpp b/src/wallet/wallet.cpp
index fdf610955b..96c4397504 100644
--- a/src/wallet/wallet.cpp
+++ b/src/wallet/wallet.cpp
@@ -752,8 +752,8 @@ bool CWallet::IsSpent(const COutPoint& outpoint) const
const uint256& wtxid = it->second;
const auto mit = mapWallet.find(wtxid);
if (mit != mapWallet.end()) {
- int depth = GetTxDepthInMainChain(mit->second);
- if (depth > 0 || (depth == 0 && !mit->second.isAbandoned()))
+ const auto& wtx = mit->second;
+ if (!wtx.isAbandoned() && !wtx.isBlockConflicted() && !wtx.isMempoolConflicted())
return true; // Spent
}
}
@@ -1197,7 +1197,7 @@ bool CWallet::LoadToWallet(const uint256& hash, const UpdateWalletTxFn& fill_wtx
auto it = mapWallet.find(txin.prevout.hash);
if (it != mapWallet.end()) {
CWalletTx& prevtx = it->second;
- if (auto* prev = prevtx.state<TxStateConflicted>()) {
+ if (auto* prev = prevtx.state<TxStateBlockConflicted>()) {
MarkConflicted(prev->conflicting_block_hash, prev->conflicting_block_height, wtx.GetHash());
}
}
@@ -1309,7 +1309,7 @@ bool CWallet::AbandonTransaction(const uint256& hashTx)
assert(!wtx.isConfirmed());
assert(!wtx.InMempool());
// If already conflicted or abandoned, no need to set abandoned
- if (!wtx.isConflicted() && !wtx.isAbandoned()) {
+ if (!wtx.isBlockConflicted() && !wtx.isAbandoned()) {
wtx.m_state = TxStateInactive{/*abandoned=*/true};
return TxUpdate::NOTIFY_CHANGED;
}
@@ -1346,7 +1346,7 @@ void CWallet::MarkConflicted(const uint256& hashBlock, int conflicting_height, c
if (conflictconfirms < GetTxDepthInMainChain(wtx)) {
// Block is 'more conflicted' than current confirm; update.
// Mark transaction as conflicted with this block.
- wtx.m_state = TxStateConflicted{hashBlock, conflicting_height};
+ wtx.m_state = TxStateBlockConflicted{hashBlock, conflicting_height};
return TxUpdate::CHANGED;
}
return TxUpdate::UNCHANGED;
@@ -1360,7 +1360,10 @@ void CWallet::MarkConflicted(const uint256& hashBlock, int conflicting_height, c
void CWallet::RecursiveUpdateTxState(const uint256& tx_hash, const TryUpdatingStateFn& try_updating_state) EXCLUSIVE_LOCKS_REQUIRED(cs_wallet) {
// Do not flush the wallet here for performance reasons
WalletBatch batch(GetDatabase(), false);
+ RecursiveUpdateTxState(&batch, tx_hash, try_updating_state);
+}
+void CWallet::RecursiveUpdateTxState(WalletBatch* batch, const uint256& tx_hash, const TryUpdatingStateFn& try_updating_state) EXCLUSIVE_LOCKS_REQUIRED(cs_wallet) {
std::set<uint256> todo;
std::set<uint256> done;
@@ -1377,7 +1380,7 @@ void CWallet::RecursiveUpdateTxState(const uint256& tx_hash, const TryUpdatingSt
TxUpdate update_state = try_updating_state(wtx);
if (update_state != TxUpdate::UNCHANGED) {
wtx.MarkDirty();
- batch.WriteTx(wtx);
+ if (batch) batch->WriteTx(wtx);
// Iterate over all its outputs, and update those tx states as well (if applicable)
for (unsigned int i = 0; i < wtx.tx->vout.size(); ++i) {
std::pair<TxSpends::const_iterator, TxSpends::const_iterator> range = mapTxSpends.equal_range(COutPoint(Txid::FromUint256(now), i));
@@ -1418,6 +1421,20 @@ void CWallet::transactionAddedToMempool(const CTransactionRef& tx) {
if (it != mapWallet.end()) {
RefreshMempoolStatus(it->second, chain());
}
+
+ const Txid& txid = tx->GetHash();
+
+ for (const CTxIn& tx_in : tx->vin) {
+ // For each wallet transaction spending this prevout..
+ for (auto range = mapTxSpends.equal_range(tx_in.prevout); range.first != range.second; range.first++) {
+ const uint256& spent_id = range.first->second;
+ // Skip the recently added tx
+ if (spent_id == txid) continue;
+ RecursiveUpdateTxState(/*batch=*/nullptr, spent_id, [&txid](CWalletTx& wtx) EXCLUSIVE_LOCKS_REQUIRED(cs_wallet) {
+ return wtx.mempool_conflicts.insert(txid).second ? TxUpdate::CHANGED : TxUpdate::UNCHANGED;
+ });
+ }
+ }
}
void CWallet::transactionRemovedFromMempool(const CTransactionRef& tx, MemPoolRemovalReason reason) {
@@ -1455,6 +1472,21 @@ void CWallet::transactionRemovedFromMempool(const CTransactionRef& tx, MemPoolRe
// https://github.com/bitcoin-core/bitcoin-devwiki/wiki/Wallet-Transaction-Conflict-Tracking
SyncTransaction(tx, TxStateInactive{});
}
+
+ const Txid& txid = tx->GetHash();
+
+ for (const CTxIn& tx_in : tx->vin) {
+ // Iterate over all wallet transactions spending txin.prev
+ // and recursively mark them as no longer conflicting with
+ // txid
+ for (auto range = mapTxSpends.equal_range(tx_in.prevout); range.first != range.second; range.first++) {
+ const uint256& spent_id = range.first->second;
+
+ RecursiveUpdateTxState(/*batch=*/nullptr, spent_id, [&txid](CWalletTx& wtx) EXCLUSIVE_LOCKS_REQUIRED(cs_wallet) {
+ return wtx.mempool_conflicts.erase(txid) ? TxUpdate::CHANGED : TxUpdate::UNCHANGED;
+ });
+ }
+ }
}
void CWallet::blockConnected(ChainstateRole role, const interfaces::BlockInfo& block)
@@ -1506,11 +1538,11 @@ void CWallet::blockDisconnected(const interfaces::BlockInfo& block)
for (TxSpends::const_iterator _it = range.first; _it != range.second; ++_it) {
CWalletTx& wtx = mapWallet.find(_it->second)->second;
- if (!wtx.isConflicted()) continue;
+ if (!wtx.isBlockConflicted()) continue;
auto try_updating_state = [&](CWalletTx& tx) {
- if (!tx.isConflicted()) return TxUpdate::UNCHANGED;
- if (tx.state<TxStateConflicted>()->conflicting_block_height >= disconnect_height) {
+ if (!tx.isBlockConflicted()) return TxUpdate::UNCHANGED;
+ if (tx.state<TxStateBlockConflicted>()->conflicting_block_height >= disconnect_height) {
tx.m_state = TxStateInactive{};
return TxUpdate::CHANGED;
}
@@ -1571,11 +1603,22 @@ isminetype CWallet::IsMine(const CTxDestination& dest) const
isminetype CWallet::IsMine(const CScript& script) const
{
AssertLockHeld(cs_wallet);
- isminetype result = ISMINE_NO;
- for (const auto& spk_man_pair : m_spk_managers) {
- result = std::max(result, spk_man_pair.second->IsMine(script));
+
+ // Search the cache so that IsMine is called only on the relevant SPKMs instead of on everything in m_spk_managers
+ const auto& it = m_cached_spks.find(script);
+ if (it != m_cached_spks.end()) {
+ isminetype res = ISMINE_NO;
+ for (const auto& spkm : it->second) {
+ res = std::max(res, spkm->IsMine(script));
+ }
+ Assume(res == ISMINE_SPENDABLE);
+ return res;
}
- return result;
+
+ // Legacy wallet
+ if (IsLegacy()) return GetLegacyScriptPubKeyMan()->IsMine(script);
+
+ return ISMINE_NO;
}
bool CWallet::IsMine(const CTransaction& tx) const
@@ -2320,12 +2363,41 @@ DBErrors CWallet::LoadWallet()
return nLoadWalletRet;
}
-DBErrors CWallet::ZapSelectTx(std::vector<uint256>& vHashIn, std::vector<uint256>& vHashOut)
+util::Result<void> CWallet::RemoveTxs(std::vector<uint256>& txs_to_remove)
{
AssertLockHeld(cs_wallet);
- DBErrors nZapSelectTxRet = WalletBatch(GetDatabase()).ZapSelectTx(vHashIn, vHashOut);
- for (const uint256& hash : vHashOut) {
- const auto& it = mapWallet.find(hash);
+ WalletBatch batch(GetDatabase());
+ if (!batch.TxnBegin()) return util::Error{_("Error starting db txn for wallet transactions removal")};
+
+ // Check for transaction existence and remove entries from disk
+ using TxIterator = std::unordered_map<uint256, CWalletTx, SaltedTxidHasher>::const_iterator;
+ std::vector<TxIterator> erased_txs;
+ bilingual_str str_err;
+ for (const uint256& hash : txs_to_remove) {
+ auto it_wtx = mapWallet.find(hash);
+ if (it_wtx == mapWallet.end()) {
+ str_err = strprintf(_("Transaction %s does not belong to this wallet"), hash.GetHex());
+ break;
+ }
+ if (!batch.EraseTx(hash)) {
+ str_err = strprintf(_("Failure removing transaction: %s"), hash.GetHex());
+ break;
+ }
+ erased_txs.emplace_back(it_wtx);
+ }
+
+ // Roll back removals in case of an error
+ if (!str_err.empty()) {
+ batch.TxnAbort();
+ return util::Error{str_err};
+ }
+
+ // Dump changes to disk
+ if (!batch.TxnCommit()) return util::Error{_("Error committing db txn for wallet transactions removal")};
+
+ // Update the in-memory state and notify upper layers about the removals
+ for (const auto& it : erased_txs) {
+ const uint256 hash{it->first};
wtxOrdered.erase(it->second.m_it_wtxOrdered);
for (const auto& txin : it->second.tx->vin)
mapTxSpends.erase(txin.prevout);
@@ -2333,22 +2405,9 @@ DBErrors CWallet::ZapSelectTx(std::vector<uint256>& vHashIn, std::vector<uint256
NotifyTransactionChanged(hash, CT_DELETED);
}
- if (nZapSelectTxRet == DBErrors::NEED_REWRITE)
- {
- if (GetDatabase().Rewrite("\x04pool"))
- {
- for (const auto& spk_man_pair : m_spk_managers) {
- spk_man_pair.second->RewriteDB();
- }
- }
- }
-
- if (nZapSelectTxRet != DBErrors::LOAD_OK)
- return nZapSelectTxRet;
-
MarkDirty();
- return DBErrors::LOAD_OK;
+ return {}; // all good
}
bool CWallet::SetAddressBookWithDB(WalletBatch& batch, const CTxDestination& address, const std::string& strName, const std::optional<AddressPurpose>& new_purpose)
@@ -2395,8 +2454,9 @@ bool CWallet::SetAddressBook(const CTxDestination& address, const std::string& s
bool CWallet::DelAddressBook(const CTxDestination& address)
{
- WalletBatch batch(GetDatabase());
- return DelAddressBookWithDB(batch, address);
+ return RunWithinTxn(GetDatabase(), /*process_desc=*/"address book entry removal", [&](WalletBatch& batch){
+ return DelAddressBookWithDB(batch, address);
+ });
}
bool CWallet::DelAddressBookWithDB(WalletBatch& batch, const CTxDestination& address)
@@ -2579,8 +2639,10 @@ util::Result<CTxDestination> ReserveDestination::GetReservedDestination(bool int
if (nIndex == -1) {
CKeyPool keypool;
- auto op_address = m_spk_man->GetReservedDestination(type, internal, nIndex, keypool);
+ int64_t index;
+ auto op_address = m_spk_man->GetReservedDestination(type, internal, index, keypool);
if (!op_address) return op_address;
+ nIndex = index;
address = *op_address;
fInternal = keypool.fInternal;
}
@@ -2757,7 +2819,7 @@ unsigned int CWallet::ComputeTimeSmart(const CWalletTx& wtx, bool rescanning_old
std::optional<uint256> block_hash;
if (auto* conf = wtx.state<TxStateConfirmed>()) {
block_hash = conf->confirmed_block_hash;
- } else if (auto* conf = wtx.state<TxStateConflicted>()) {
+ } else if (auto* conf = wtx.state<TxStateBlockConflicted>()) {
block_hash = conf->conflicting_block_hash;
}
@@ -3347,7 +3409,7 @@ int CWallet::GetTxDepthInMainChain(const CWalletTx& wtx) const
if (auto* conf = wtx.state<TxStateConfirmed>()) {
assert(conf->confirmed_block_height >= 0);
return GetLastBlockHeight() - conf->confirmed_block_height + 1;
- } else if (auto* conf = wtx.state<TxStateConflicted>()) {
+ } else if (auto* conf = wtx.state<TxStateBlockConflicted>()) {
assert(conf->conflicting_block_height >= 0);
return -1 * (GetLastBlockHeight() - conf->conflicting_block_height + 1);
} else {
@@ -3435,6 +3497,17 @@ std::set<ScriptPubKeyMan*> CWallet::GetActiveScriptPubKeyMans() const
return spk_mans;
}
+bool CWallet::IsActiveScriptPubKeyMan(const ScriptPubKeyMan& spkm) const
+{
+ for (const auto& [_, ext_spkm] : m_external_spk_managers) {
+ if (ext_spkm == &spkm) return true;
+ }
+ for (const auto& [_, int_spkm] : m_internal_spk_managers) {
+ if (int_spkm == &spkm) return true;
+ }
+ return false;
+}
+
std::set<ScriptPubKeyMan*> CWallet::GetAllScriptPubKeyMans() const
{
std::set<ScriptPubKeyMan*> spk_mans;
@@ -3457,12 +3530,18 @@ ScriptPubKeyMan* CWallet::GetScriptPubKeyMan(const OutputType& type, bool intern
std::set<ScriptPubKeyMan*> CWallet::GetScriptPubKeyMans(const CScript& script) const
{
std::set<ScriptPubKeyMan*> spk_mans;
- SignatureData sigdata;
- for (const auto& spk_man_pair : m_spk_managers) {
- if (spk_man_pair.second->CanProvide(script, sigdata)) {
- spk_mans.insert(spk_man_pair.second.get());
- }
+
+ // Search the cache for relevant SPKMs instead of iterating m_spk_managers
+ const auto& it = m_cached_spks.find(script);
+ if (it != m_cached_spks.end()) {
+ spk_mans.insert(it->second.begin(), it->second.end());
}
+ SignatureData sigdata;
+ Assume(std::all_of(spk_mans.begin(), spk_mans.end(), [&script, &sigdata](ScriptPubKeyMan* spkm) { return spkm->CanProvide(script, sigdata); }));
+
+ // Legacy wallet
+ if (IsLegacy() && GetLegacyScriptPubKeyMan()->CanProvide(script, sigdata)) spk_mans.insert(GetLegacyScriptPubKeyMan());
+
return spk_mans;
}
@@ -3482,11 +3561,17 @@ std::unique_ptr<SigningProvider> CWallet::GetSolvingProvider(const CScript& scri
std::unique_ptr<SigningProvider> CWallet::GetSolvingProvider(const CScript& script, SignatureData& sigdata) const
{
- for (const auto& spk_man_pair : m_spk_managers) {
- if (spk_man_pair.second->CanProvide(script, sigdata)) {
- return spk_man_pair.second->GetSolvingProvider(script);
- }
+ // Search the cache for relevant SPKMs instead of iterating m_spk_managers
+ const auto& it = m_cached_spks.find(script);
+ if (it != m_cached_spks.end()) {
+ // All spkms for a given script must already be able to make a SigningProvider for the script, so just return the first one.
+ Assume(it->second.at(0)->CanProvide(script, sigdata));
+ return it->second.at(0)->GetSolvingProvider(script);
}
+
+ // Legacy wallet
+ if (IsLegacy() && GetLegacyScriptPubKeyMan()->CanProvide(script, sigdata)) return GetLegacyScriptPubKeyMan()->GetSolvingProvider(script);
+
return nullptr;
}
@@ -3565,15 +3650,36 @@ void CWallet::ConnectScriptPubKeyManNotifiers()
}
}
-void CWallet::LoadDescriptorScriptPubKeyMan(uint256 id, WalletDescriptor& desc)
+DescriptorScriptPubKeyMan& CWallet::LoadDescriptorScriptPubKeyMan(uint256 id, WalletDescriptor& desc)
{
+ DescriptorScriptPubKeyMan* spk_manager;
if (IsWalletFlagSet(WALLET_FLAG_EXTERNAL_SIGNER)) {
- auto spk_manager = std::unique_ptr<ScriptPubKeyMan>(new ExternalSignerScriptPubKeyMan(*this, desc, m_keypool_size));
- AddScriptPubKeyMan(id, std::move(spk_manager));
+ spk_manager = new ExternalSignerScriptPubKeyMan(*this, desc, m_keypool_size);
} else {
- auto spk_manager = std::unique_ptr<ScriptPubKeyMan>(new DescriptorScriptPubKeyMan(*this, desc, m_keypool_size));
- AddScriptPubKeyMan(id, std::move(spk_manager));
+ spk_manager = new DescriptorScriptPubKeyMan(*this, desc, m_keypool_size);
+ }
+ AddScriptPubKeyMan(id, std::unique_ptr<ScriptPubKeyMan>(spk_manager));
+ return *spk_manager;
+}
+
+DescriptorScriptPubKeyMan& CWallet::SetupDescriptorScriptPubKeyMan(WalletBatch& batch, const CExtKey& master_key, const OutputType& output_type, bool internal)
+{
+ AssertLockHeld(cs_wallet);
+ auto spk_manager = std::unique_ptr<DescriptorScriptPubKeyMan>(new DescriptorScriptPubKeyMan(*this, m_keypool_size));
+ 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, &batch)) {
+ throw std::runtime_error(std::string(__func__) + ": Could not encrypt new descriptors");
+ }
}
+ spk_manager->SetupDescriptorGeneration(batch, master_key, output_type, internal);
+ DescriptorScriptPubKeyMan* out = spk_manager.get();
+ uint256 id = spk_manager->GetID();
+ AddScriptPubKeyMan(id, std::move(spk_manager));
+ AddActiveScriptPubKeyManWithDb(batch, id, output_type, internal);
+ return *out;
}
void CWallet::SetupDescriptorScriptPubKeyMans(const CExtKey& master_key)
@@ -3586,19 +3692,7 @@ void CWallet::SetupDescriptorScriptPubKeyMans(const CExtKey& master_key)
for (bool internal : {false, true}) {
for (OutputType t : OUTPUT_TYPES) {
- auto spk_manager = std::unique_ptr<DescriptorScriptPubKeyMan>(new DescriptorScriptPubKeyMan(*this, m_keypool_size));
- 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, &batch)) {
- throw std::runtime_error(std::string(__func__) + ": Could not encrypt new descriptors");
- }
- }
- spk_manager->SetupDescriptorGeneration(batch, master_key, t, internal);
- uint256 id = spk_manager->GetID();
- AddScriptPubKeyMan(id, std::move(spk_manager));
- AddActiveScriptPubKeyManWithDb(batch, id, t, internal);
+ SetupDescriptorScriptPubKeyMan(batch, master_key, t, internal);
}
}
@@ -3928,6 +4022,8 @@ bool CWallet::ApplyMigrationData(MigrationData& data, bilingual_str& error)
if (ExtractDestination(script, dest)) not_migrated_dests.emplace(dest);
}
+ Assume(!m_cached_spks.empty());
+
for (auto& desc_spkm : data.desc_spkms) {
if (m_spk_managers.count(desc_spkm->GetID()) > 0) {
error = _("Error: Duplicate descriptors created during migration. Your wallet may be corrupted.");
@@ -4025,19 +4121,10 @@ bool CWallet::ApplyMigrationData(MigrationData& data, bilingual_str& error)
watchonly_batch.reset(); // Flush
// Do the removes
if (txids_to_delete.size() > 0) {
- std::vector<uint256> deleted_txids;
- if (ZapSelectTx(txids_to_delete, deleted_txids) != DBErrors::LOAD_OK) {
- error = _("Error: Could not delete watchonly transactions");
- return false;
- }
- if (deleted_txids != txids_to_delete) {
- error = _("Error: Not all watchonly txs could be deleted");
+ if (auto res = RemoveTxs(txids_to_delete); !res) {
+ error = _("Error: Could not delete watchonly transactions. ") + util::ErrorString(res);
return false;
}
- // Tell the GUI of each tx
- for (const uint256& txid : deleted_txids) {
- NotifyTransactionChanged(txid, CT_UPDATED);
- }
}
// Pair external wallets with their corresponding db handler
@@ -4300,7 +4387,7 @@ util::Result<MigrationResult> MigrateLegacyToDescriptor(const std::string& walle
// Make a backup of the DB
fs::path this_wallet_dir = fs::absolute(fs::PathFromString(local_wallet->GetDatabase().Filename())).parent_path();
- fs::path backup_filename = fs::PathFromString(strprintf("%s-%d.legacy.bak", wallet_name, GetTime()));
+ fs::path backup_filename = fs::PathFromString(strprintf("%s_%d.legacy.bak", (wallet_name.empty() ? "default_wallet" : wallet_name), GetTime()));
fs::path backup_path = this_wallet_dir / backup_filename;
if (!local_wallet->BackupWallet(fs::PathToString(backup_path))) {
if (was_loaded) {
@@ -4420,4 +4507,53 @@ util::Result<MigrationResult> MigrateLegacyToDescriptor(const std::string& walle
}
return res;
}
+
+void CWallet::CacheNewScriptPubKeys(const std::set<CScript>& spks, ScriptPubKeyMan* spkm)
+{
+ for (const auto& script : spks) {
+ m_cached_spks[script].push_back(spkm);
+ }
+}
+
+void CWallet::TopUpCallback(const std::set<CScript>& spks, ScriptPubKeyMan* spkm)
+{
+ // Update scriptPubKey cache
+ CacheNewScriptPubKeys(spks, spkm);
+}
+
+std::set<CExtPubKey> CWallet::GetActiveHDPubKeys() const
+{
+ AssertLockHeld(cs_wallet);
+
+ Assert(IsWalletFlagSet(WALLET_FLAG_DESCRIPTORS));
+
+ std::set<CExtPubKey> active_xpubs;
+ for (const auto& spkm : GetActiveScriptPubKeyMans()) {
+ const DescriptorScriptPubKeyMan* desc_spkm = dynamic_cast<DescriptorScriptPubKeyMan*>(spkm);
+ assert(desc_spkm);
+ LOCK(desc_spkm->cs_desc_man);
+ WalletDescriptor w_desc = desc_spkm->GetWalletDescriptor();
+
+ std::set<CPubKey> desc_pubkeys;
+ std::set<CExtPubKey> desc_xpubs;
+ w_desc.descriptor->GetPubKeys(desc_pubkeys, desc_xpubs);
+ active_xpubs.merge(std::move(desc_xpubs));
+ }
+ return active_xpubs;
+}
+
+std::optional<CKey> CWallet::GetKey(const CKeyID& keyid) const
+{
+ Assert(IsWalletFlagSet(WALLET_FLAG_DESCRIPTORS));
+
+ for (const auto& spkm : GetAllScriptPubKeyMans()) {
+ const DescriptorScriptPubKeyMan* desc_spkm = dynamic_cast<DescriptorScriptPubKeyMan*>(spkm);
+ assert(desc_spkm);
+ LOCK(desc_spkm->cs_desc_man);
+ if (std::optional<CKey> key = desc_spkm->GetKey(keyid)) {
+ return key;
+ }
+ }
+ return std::nullopt;
+}
} // namespace wallet
diff --git a/src/wallet/wallet.h b/src/wallet/wallet.h
index c0d10ab7fc..b49b5a7d0d 100644
--- a/src/wallet/wallet.h
+++ b/src/wallet/wallet.h
@@ -364,6 +364,7 @@ private:
/** Mark a transaction (and its in-wallet descendants) as a particular tx state. */
void RecursiveUpdateTxState(const uint256& tx_hash, const TryUpdatingStateFn& try_updating_state) EXCLUSIVE_LOCKS_REQUIRED(cs_wallet);
+ void RecursiveUpdateTxState(WalletBatch* batch, const uint256& tx_hash, const TryUpdatingStateFn& try_updating_state) EXCLUSIVE_LOCKS_REQUIRED(cs_wallet);
/** Mark a transaction's inputs dirty, thus forcing the outputs to be recomputed */
void MarkInputsDirty(const CTransactionRef& tx) EXCLUSIVE_LOCKS_REQUIRED(cs_wallet);
@@ -422,6 +423,9 @@ private:
// Same as 'AddActiveScriptPubKeyMan' but designed for use within a batch transaction context
void AddActiveScriptPubKeyManWithDb(WalletBatch& batch, uint256 id, OutputType type, bool internal);
+ //! Cache of descriptor ScriptPubKeys used for IsMine. Maps ScriptPubKey to set of spkms
+ std::unordered_map<CScript, std::vector<ScriptPubKeyMan*>, SaltedSipHasher> m_cached_spks;
+
/**
* Catch wallet up to current chain, scanning new blocks, updating the best
* block locator and m_last_block_processed, and registering for
@@ -515,11 +519,6 @@ public:
* referenced in transaction, and might cause assert failures.
*/
int GetTxDepthInMainChain(const CWalletTx& wtx) const EXCLUSIVE_LOCKS_REQUIRED(cs_wallet);
- bool IsTxInMainChain(const CWalletTx& wtx) const EXCLUSIVE_LOCKS_REQUIRED(cs_wallet)
- {
- AssertLockHeld(cs_wallet);
- return GetTxDepthInMainChain(wtx) > 0;
- }
/**
* @return number of blocks to maturity for this transaction:
@@ -790,7 +789,9 @@ public:
void chainStateFlushed(ChainstateRole role, const CBlockLocator& loc) override;
DBErrors LoadWallet();
- DBErrors ZapSelectTx(std::vector<uint256>& vHashIn, std::vector<uint256>& vHashOut) EXCLUSIVE_LOCKS_REQUIRED(cs_wallet);
+
+ /** Erases the provided transactions from the wallet. */
+ util::Result<void> RemoveTxs(std::vector<uint256>& txs_to_remove) EXCLUSIVE_LOCKS_REQUIRED(cs_wallet);
bool SetAddressBook(const CTxDestination& address, const std::string& strName, const std::optional<AddressPurpose>& purpose);
@@ -937,6 +938,7 @@ public:
//! Returns all unique ScriptPubKeyMans in m_internal_spk_managers and m_external_spk_managers
std::set<ScriptPubKeyMan*> GetActiveScriptPubKeyMans() const;
+ bool IsActiveScriptPubKeyMan(const ScriptPubKeyMan& spkm) const;
//! Returns all unique ScriptPubKeyMans
std::set<ScriptPubKeyMan*> GetAllScriptPubKeyMans() const;
@@ -992,7 +994,7 @@ public:
void ConnectScriptPubKeyManNotifiers();
//! Instantiate a descriptor ScriptPubKeyMan from the WalletDescriptor and load it
- void LoadDescriptorScriptPubKeyMan(uint256 id, WalletDescriptor& desc);
+ DescriptorScriptPubKeyMan& LoadDescriptorScriptPubKeyMan(uint256 id, WalletDescriptor& desc);
//! Adds the active ScriptPubKeyMan for the specified type and internal. Writes it to the wallet file
//! @param[in] id The unique id for the ScriptPubKeyMan
@@ -1012,6 +1014,8 @@ public:
//! @param[in] internal Whether this ScriptPubKeyMan provides change addresses
void DeactivateScriptPubKeyMan(uint256 id, OutputType type, bool internal);
+ //! Create new DescriptorScriptPubKeyMan and add it to the wallet
+ DescriptorScriptPubKeyMan& SetupDescriptorScriptPubKeyMan(WalletBatch& batch, const CExtKey& master_key, const OutputType& output_type, bool internal) EXCLUSIVE_LOCKS_REQUIRED(cs_wallet);
//! Create new DescriptorScriptPubKeyMans and add them to the wallet
void SetupDescriptorScriptPubKeyMans(const CExtKey& master_key) EXCLUSIVE_LOCKS_REQUIRED(cs_wallet);
void SetupDescriptorScriptPubKeyMans() EXCLUSIVE_LOCKS_REQUIRED(cs_wallet);
@@ -1043,6 +1047,18 @@ public:
//! Whether the (external) signer performs R-value signature grinding
bool CanGrindR() const;
+
+ //! Add scriptPubKeys for this ScriptPubKeyMan into the scriptPubKey cache
+ void CacheNewScriptPubKeys(const std::set<CScript>& spks, ScriptPubKeyMan* spkm);
+
+ void TopUpCallback(const std::set<CScript>& spks, ScriptPubKeyMan* spkm) override;
+
+ //! Retrieve the xpubs in use by the active descriptors
+ std::set<CExtPubKey> GetActiveHDPubKeys() const EXCLUSIVE_LOCKS_REQUIRED(cs_wallet);
+
+ //! Find the private key for the given key id from the wallet's descriptors, if available
+ //! Returns nullopt when no descriptor has the key or if the wallet is locked.
+ std::optional<CKey> GetKey(const CKeyID& keyid) const;
};
/**
diff --git a/src/wallet/walletdb.cpp b/src/wallet/walletdb.cpp
index f3dd5b328e..b1ce7ee4e7 100644
--- a/src/wallet/walletdb.cpp
+++ b/src/wallet/walletdb.cpp
@@ -3,6 +3,10 @@
// Distributed under the MIT software license, see the accompanying
// file COPYING or http://www.opensource.org/licenses/mit-license.php.
+#if defined(HAVE_CONFIG_H)
+#include <config/bitcoin-config.h>
+#endif
+
#include <wallet/walletdb.h>
#include <common/system.h>
@@ -804,10 +808,10 @@ static DBErrors LoadDescriptorWalletRecords(CWallet* pwallet, DatabaseBatch& bat
strErr = strprintf("%s\nDetails: %s", strErr, e.what());
return DBErrors::UNKNOWN_DESCRIPTOR;
}
- pwallet->LoadDescriptorScriptPubKeyMan(id, desc);
+ DescriptorScriptPubKeyMan& spkm = pwallet->LoadDescriptorScriptPubKeyMan(id, desc);
// Prior to doing anything with this spkm, verify ID compatibility
- if (id != pwallet->GetDescriptorScriptPubKeyMan(desc)->GetID()) {
+ if (id != spkm.GetID()) {
strErr = "The descriptor ID calculated by the wallet differs from the one in DB";
return DBErrors::CORRUPT;
}
@@ -1230,88 +1234,33 @@ DBErrors WalletBatch::LoadWallet(CWallet* pwallet)
return result;
}
-DBErrors WalletBatch::FindWalletTxHashes(std::vector<uint256>& tx_hashes)
+static bool RunWithinTxn(WalletBatch& batch, std::string_view process_desc, const std::function<bool(WalletBatch&)>& func)
{
- DBErrors result = DBErrors::LOAD_OK;
-
- try {
- int nMinVersion = 0;
- if (m_batch->Read(DBKeys::MINVERSION, nMinVersion)) {
- if (nMinVersion > FEATURE_LATEST)
- return DBErrors::TOO_NEW;
- }
-
- // Get cursor
- std::unique_ptr<DatabaseCursor> cursor = m_batch->GetNewCursor();
- if (!cursor)
- {
- LogPrintf("Error getting wallet database cursor\n");
- return DBErrors::CORRUPT;
- }
+ if (!batch.TxnBegin()) {
+ LogPrint(BCLog::WALLETDB, "Error: cannot create db txn for %s\n", process_desc);
+ return false;
+ }
- while (true)
- {
- // Read next record
- DataStream ssKey{};
- DataStream ssValue{};
- DatabaseCursor::Status status = cursor->Next(ssKey, ssValue);
- if (status == DatabaseCursor::Status::DONE) {
- break;
- } else if (status == DatabaseCursor::Status::FAIL) {
- LogPrintf("Error reading next record from wallet database\n");
- return DBErrors::CORRUPT;
- }
+ // Run procedure
+ if (!func(batch)) {
+ LogPrint(BCLog::WALLETDB, "Error: %s failed\n", process_desc);
+ batch.TxnAbort();
+ return false;
+ }
- std::string strType;
- ssKey >> strType;
- if (strType == DBKeys::TX) {
- uint256 hash;
- ssKey >> hash;
- tx_hashes.push_back(hash);
- }
- }
- } catch (...) {
- result = DBErrors::CORRUPT;
+ if (!batch.TxnCommit()) {
+ LogPrint(BCLog::WALLETDB, "Error: cannot commit db txn for %s\n", process_desc);
+ return false;
}
- return result;
+ // All good
+ return true;
}
-DBErrors WalletBatch::ZapSelectTx(std::vector<uint256>& vTxHashIn, std::vector<uint256>& vTxHashOut)
+bool RunWithinTxn(WalletDatabase& database, std::string_view process_desc, const std::function<bool(WalletBatch&)>& func)
{
- // build list of wallet TX hashes
- std::vector<uint256> vTxHash;
- DBErrors err = FindWalletTxHashes(vTxHash);
- if (err != DBErrors::LOAD_OK) {
- return err;
- }
-
- std::sort(vTxHash.begin(), vTxHash.end());
- std::sort(vTxHashIn.begin(), vTxHashIn.end());
-
- // erase each matching wallet TX
- bool delerror = false;
- std::vector<uint256>::iterator it = vTxHashIn.begin();
- for (const uint256& hash : vTxHash) {
- while (it < vTxHashIn.end() && (*it) < hash) {
- it++;
- }
- if (it == vTxHashIn.end()) {
- break;
- }
- else if ((*it) == hash) {
- if(!EraseTx(hash)) {
- LogPrint(BCLog::WALLETDB, "Transaction was found for deletion but returned database error: %s\n", hash.GetHex());
- delerror = true;
- }
- vTxHashOut.push_back(hash);
- }
- }
-
- if (delerror) {
- return DBErrors::CORRUPT;
- }
- return DBErrors::LOAD_OK;
+ WalletBatch batch(database);
+ return RunWithinTxn(batch, process_desc, func);
}
void MaybeCompactWalletDB(WalletContext& context)
@@ -1376,47 +1325,11 @@ bool WalletBatch::WriteWalletFlags(const uint64_t flags)
bool WalletBatch::EraseRecords(const std::unordered_set<std::string>& types)
{
- // Begin db txn
- if (!m_batch->TxnBegin()) return false;
-
- // Get cursor
- std::unique_ptr<DatabaseCursor> cursor = m_batch->GetNewCursor();
- if (!cursor)
- {
- return false;
- }
-
- // Iterate the DB and look for any records that have the type prefixes
- while (true) {
- // Read next record
- DataStream key{};
- DataStream value{};
- DatabaseCursor::Status status = cursor->Next(key, value);
- if (status == DatabaseCursor::Status::DONE) {
- break;
- } else if (status == DatabaseCursor::Status::FAIL) {
- cursor.reset(nullptr);
- m_batch->TxnAbort(); // abort db txn
- return false;
- }
-
- // Make a copy of key to avoid data being deleted by the following read of the type
- const SerializeData key_data{key.begin(), key.end()};
-
- std::string type;
- key >> type;
-
- if (types.count(type) > 0) {
- if (!m_batch->Erase(Span{key_data})) {
- cursor.reset(nullptr);
- m_batch->TxnAbort();
- return false; // erase failed
- }
- }
- }
- // Finish db txn
- cursor.reset(nullptr);
- return m_batch->TxnCommit();
+ return RunWithinTxn(*this, "erase records", [&types](WalletBatch& self) {
+ return std::all_of(types.begin(), types.end(), [&self](const std::string& type) {
+ return self.m_batch->ErasePrefix(DataStream() << type);
+ });
+ });
}
bool WalletBatch::TxnBegin()
diff --git a/src/wallet/walletdb.h b/src/wallet/walletdb.h
index dad0b18a78..9474a59660 100644
--- a/src/wallet/walletdb.h
+++ b/src/wallet/walletdb.h
@@ -275,8 +275,6 @@ public:
bool EraseActiveScriptPubKeyMan(uint8_t type, bool internal);
DBErrors LoadWallet(CWallet* pwallet);
- DBErrors FindWalletTxHashes(std::vector<uint256>& tx_hashes);
- DBErrors ZapSelectTx(std::vector<uint256>& vHashIn, std::vector<uint256>& vHashOut);
//! write the hdchain model (external chain child index counter)
bool WriteHDChain(const CHDChain& chain);
@@ -296,6 +294,20 @@ private:
WalletDatabase& m_database;
};
+/**
+ * Executes the provided function 'func' within a database transaction context.
+ *
+ * This function ensures that all db modifications performed within 'func()' are
+ * atomically committed to the db at the end of the process. And, in case of a
+ * failure during execution, all performed changes are rolled back.
+ *
+ * @param database The db connection instance to perform the transaction on.
+ * @param process_desc A description of the process being executed, used for logging purposes in the event of a failure.
+ * @param func The function to be executed within the db txn context. It returns a boolean indicating whether to commit or roll back the txn.
+ * @return true if the db txn executed successfully, false otherwise.
+ */
+bool RunWithinTxn(WalletDatabase& database, std::string_view process_desc, const std::function<bool(WalletBatch&)>& func);
+
//! Compacts BDB state so that wallet.dat is self-contained (if there are changes)
void MaybeCompactWalletDB(WalletContext& context);
diff --git a/src/wallet/walletutil.cpp b/src/wallet/walletutil.cpp
index fdd5bc36d8..0de2617d45 100644
--- a/src/wallet/walletutil.cpp
+++ b/src/wallet/walletutil.cpp
@@ -4,7 +4,9 @@
#include <wallet/walletutil.h>
+#include <chainparams.h>
#include <common/args.h>
+#include <key_io.h>
#include <logging.h>
namespace wallet {
@@ -43,4 +45,58 @@ WalletFeature GetClosestWalletFeature(int version)
}
return static_cast<WalletFeature>(0);
}
+
+WalletDescriptor GenerateWalletDescriptor(const CExtPubKey& master_key, const OutputType& addr_type, bool internal)
+{
+ int64_t creation_time = GetTime();
+
+ std::string xpub = EncodeExtPubKey(master_key);
+
+ // Build descriptor string
+ std::string desc_prefix;
+ std::string desc_suffix = "/*)";
+ switch (addr_type) {
+ case OutputType::LEGACY: {
+ desc_prefix = "pkh(" + xpub + "/44h";
+ break;
+ }
+ case OutputType::P2SH_SEGWIT: {
+ desc_prefix = "sh(wpkh(" + xpub + "/49h";
+ desc_suffix += ")";
+ break;
+ }
+ case OutputType::BECH32: {
+ desc_prefix = "wpkh(" + xpub + "/84h";
+ break;
+ }
+ case OutputType::BECH32M: {
+ desc_prefix = "tr(" + xpub + "/86h";
+ break;
+ }
+ case OutputType::UNKNOWN: {
+ // We should never have a DescriptorScriptPubKeyMan for an UNKNOWN OutputType,
+ // so if we get to this point something is wrong
+ assert(false);
+ }
+ } // no default case, so the compiler can warn about missing cases
+ assert(!desc_prefix.empty());
+
+ // Mainnet derives at 0', testnet and regtest derive at 1'
+ if (Params().IsTestChain()) {
+ desc_prefix += "/1h";
+ } else {
+ desc_prefix += "/0h";
+ }
+
+ std::string internal_path = internal ? "/1" : "/0";
+ std::string desc_str = desc_prefix + "/0h" + internal_path + desc_suffix;
+
+ // Make the descriptor
+ FlatSigningProvider keys;
+ std::string error;
+ std::unique_ptr<Descriptor> desc = Parse(desc_str, keys, error, false);
+ WalletDescriptor w_desc(std::move(desc), creation_time, 0, 0, 0);
+ return w_desc;
+}
+
} // namespace wallet
diff --git a/src/wallet/walletutil.h b/src/wallet/walletutil.h
index 7ad3ffe9e4..38926c1eb8 100644
--- a/src/wallet/walletutil.h
+++ b/src/wallet/walletutil.h
@@ -114,6 +114,8 @@ public:
WalletDescriptor() {}
WalletDescriptor(std::shared_ptr<Descriptor> descriptor, uint64_t creation_time, int32_t range_start, int32_t range_end, int32_t next_index) : descriptor(descriptor), id(DescriptorID(*descriptor)), creation_time(creation_time), range_start(range_start), range_end(range_end), next_index(next_index) { }
};
+
+WalletDescriptor GenerateWalletDescriptor(const CExtPubKey& master_key, const OutputType& output_type, bool internal);
} // namespace wallet
#endif // BITCOIN_WALLET_WALLETUTIL_H