This commit is contained in:
Luke Dashjr 2024-04-29 04:35:21 +02:00 committed by GitHub
commit d16785681c
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
5 changed files with 104 additions and 6 deletions

View File

@ -545,6 +545,10 @@ RPCHelpMan getaddressinfo()
{
{RPCResult::Type::STR, "label name", "Label name (defaults to \"\")."},
}},
{RPCResult::Type::ARR, "use_txids", "",
{
{RPCResult::Type::STR_HEX, "txid", "The ids of transactions involving this wallet which received with the address"},
}},
}
},
RPCExamples{
@ -638,6 +642,15 @@ RPCHelpMan getaddressinfo()
}
ret.pushKV("labels", std::move(labels));
// NOTE: Intentionally not special-casing a single txid: while addresses
// should never be reused, it's not unexpected to have RBF result in
// multiple txids for a single use.
UniValue use_txids(UniValue::VARR);
pwallet->FindScriptPubKeyUsed(std::set<CScript>{scriptPubKey}, [&use_txids](const CWalletTx&wtx) {
use_txids.push_back(wtx.GetHash().GetHex());
});
ret.pushKV("use_txids", std::move(use_txids));
return ret;
},
};

View File

@ -786,6 +786,60 @@ void CWallet::AddToSpends(const CWalletTx& wtx, WalletBatch* batch)
AddToSpends(txin.prevout, wtx.GetHash(), batch);
}
void CWallet::InitialiseAddressBookUsed()
{
for (const auto& entry : mapWallet) {
const CWalletTx& wtx = entry.second;
UpdateAddressBookUsed(wtx);
}
}
void CWallet::UpdateAddressBookUsed(const CWalletTx& wtx)
{
for (const auto& output : wtx.tx->vout) {
CTxDestination dest;
if (!ExtractDestination(output.scriptPubKey, dest)) continue;
m_address_book[dest].m_used = true;
}
}
bool CWallet::FindScriptPubKeyUsed(const std::set<CScript>& keys, const std::variant<std::monostate, std::function<void(const CWalletTx&)>, std::function<void(const CWalletTx&, uint32_t)>>& callback) const
{
AssertLockHeld(cs_wallet);
bool found_any = false;
for (const auto& key : keys) {
CTxDestination dest;
if (!ExtractDestination(key, dest)) continue;
const auto& address_book_it = m_address_book.find(dest);
if (address_book_it == m_address_book.end()) continue;
if (address_book_it->second.m_used) {
found_any = true;
break;
}
}
if (!found_any) return false;
if (std::holds_alternative<std::monostate>(callback)) return true;
found_any = false;
for (const auto& entry : mapWallet) {
const CWalletTx& wtx = entry.second;
for (size_t i = 0; i < wtx.tx->vout.size(); ++i) {
const auto& output = wtx.tx->vout[i];
if (keys.count(output.scriptPubKey)) {
found_any = true;
const auto callback_type = callback.index();
if (callback_type == 1) {
std::get<std::function<void(const CWalletTx&)>>(callback)(wtx);
break;
}
std::get<std::function<void(const CWalletTx&, uint32_t)>>(callback)(wtx, i);
}
}
}
return found_any;
}
bool CWallet::EncryptWallet(const SecureString& strWalletPassphrase)
{
if (IsCrypted())
@ -1083,6 +1137,7 @@ CWalletTx* CWallet::AddToWallet(CTransactionRef tx, const TxState& state, const
// Update birth time when tx time is older than it.
MaybeUpdateBirthTime(wtx.GetTxTime());
UpdateAddressBookUsed(wtx);
}
if (!fInsertedNew)
@ -2338,7 +2393,7 @@ void CWallet::CommitTransaction(CTransactionRef tx, mapValue_t mapValue, std::ve
}
}
DBErrors CWallet::LoadWallet()
DBErrors CWallet::LoadWallet(const do_init_used_flag do_init_used_flag_val)
{
LOCK(cs_wallet);
@ -2360,7 +2415,13 @@ DBErrors CWallet::LoadWallet()
assert(m_internal_spk_managers.empty());
}
return nLoadWalletRet;
if (nLoadWalletRet != DBErrors::LOAD_OK) {
return nLoadWalletRet;
}
if (do_init_used_flag_val == do_init_used_flag::Init) InitialiseAddressBookUsed();
return DBErrors::LOAD_OK;
}
util::Result<void> CWallet::RemoveTxs(std::vector<uint256>& txs_to_remove)

View File

@ -47,6 +47,7 @@
#include <string>
#include <unordered_map>
#include <utility>
#include <variant>
#include <vector>
#include <boost/signals2/signal.hpp>
@ -237,6 +238,12 @@ struct CAddressBookData
*/
std::optional<std::string> label;
/** Whether address is the destination of any wallet transation.
* Unlike other fields in address data struct, the used value is determined
* at runtime and not serialized as part of address data.
*/
bool m_used{false};
/**
* Address purpose which was originally recorded for payment protocol
* support but now serves as a cached IsMine value. Wallet code should
@ -337,6 +344,9 @@ private:
void AddToSpends(const COutPoint& outpoint, const uint256& wtxid, WalletBatch* batch = nullptr) EXCLUSIVE_LOCKS_REQUIRED(cs_wallet);
void AddToSpends(const CWalletTx& wtx, WalletBatch* batch = nullptr) EXCLUSIVE_LOCKS_REQUIRED(cs_wallet);
void InitialiseAddressBookUsed() EXCLUSIVE_LOCKS_REQUIRED(cs_wallet);
void UpdateAddressBookUsed(const CWalletTx&) EXCLUSIVE_LOCKS_REQUIRED(cs_wallet);
/**
* Add a transaction to the wallet, or update it. confirm.block_* should
* be set when the transaction was known to be included in a block. When
@ -546,6 +556,8 @@ public:
bool UnlockAllCoins() EXCLUSIVE_LOCKS_REQUIRED(cs_wallet);
void ListLockedCoins(std::vector<COutPoint>& vOutpts) const EXCLUSIVE_LOCKS_REQUIRED(cs_wallet);
bool FindScriptPubKeyUsed(const std::set<CScript>& keys, const std::variant<std::monostate, std::function<void(const CWalletTx&)>, std::function<void(const CWalletTx&, uint32_t)>>& callback = std::monostate()) const EXCLUSIVE_LOCKS_REQUIRED(cs_wallet);
/*
* Rescan abort properties
*/
@ -788,7 +800,8 @@ public:
CAmount GetDebit(const CTransaction& tx, const isminefilter& filter) const;
void chainStateFlushed(ChainstateRole role, const CBlockLocator& loc) override;
DBErrors LoadWallet();
enum class do_init_used_flag { Init, Skip };
DBErrors LoadWallet(const do_init_used_flag do_init_used_flag_val = do_init_used_flag::Init);
/** Erases the provided transactions from the wallet. */
util::Result<void> RemoveTxs(std::vector<uint256>& txs_to_remove) EXCLUSIVE_LOCKS_REQUIRED(cs_wallet);

View File

@ -47,7 +47,7 @@ static void WalletCreate(CWallet* wallet_instance, uint64_t wallet_creation_flag
wallet_instance->TopUpKeyPool();
}
static std::shared_ptr<CWallet> MakeWallet(const std::string& name, const fs::path& path, DatabaseOptions options)
static std::shared_ptr<CWallet> MakeWallet(const std::string& name, const fs::path& path, DatabaseOptions options, CWallet::do_init_used_flag do_init_used_flag_val = CWallet::do_init_used_flag::Init)
{
DatabaseStatus status;
bilingual_str error;
@ -61,7 +61,7 @@ static std::shared_ptr<CWallet> MakeWallet(const std::string& name, const fs::pa
std::shared_ptr<CWallet> wallet_instance{new CWallet(/*chain=*/nullptr, name, std::move(database)), WalletToolReleaseWallet};
DBErrors load_wallet_ret;
try {
load_wallet_ret = wallet_instance->LoadWallet();
load_wallet_ret = wallet_instance->LoadWallet(do_init_used_flag_val);
} catch (const std::runtime_error&) {
tfm::format(std::cerr, "Error loading %s. Is wallet being used by another process?\n", name);
return nullptr;
@ -167,7 +167,8 @@ bool ExecuteWalletToolFunc(const ArgsManager& args, const std::string& command)
DatabaseOptions options;
ReadDatabaseArgs(args, options);
options.require_existing = true;
const std::shared_ptr<CWallet> wallet_instance = MakeWallet(name, path, options);
// NOTE: We need to skip initialisation of the m_used flag, or else the address book count might be wrong
const std::shared_ptr<CWallet> wallet_instance = MakeWallet(name, path, options, CWallet::do_init_used_flag::Skip);
if (!wallet_instance) return false;
WalletShowInfo(wallet_instance.get());
wallet_instance->Close();

View File

@ -655,6 +655,16 @@ class WalletTest(BitcoinTestFramework):
assert not address_info["iswatchonly"]
assert not address_info["isscript"]
assert not address_info["ischange"]
assert_equal(address_info['use_txids'], [])
# Test getaddressinfo 'use_txids' field
addr = "mneYUmWYsuk7kySiURxCi3AGxrAqZxLgPZ"
txid_1 = self.nodes[0].sendtoaddress(addr, 1)
address_info = self.nodes[0].getaddressinfo(addr)
assert_equal(address_info['use_txids'], [txid_1])
txid_2 = self.nodes[0].sendtoaddress(addr, 1)
address_info = self.nodes[0].getaddressinfo(addr)
assert_equal(sorted(address_info['use_txids']), sorted([txid_1, txid_2]))
# Test getaddressinfo 'ischange' field on change address.
self.generate(self.nodes[0], 1, sync_fun=self.no_op)