Merge bitcoin/bitcoin#25337: refactor: encapsulate wallet's address book access
d69045e291test: add coverage for 'listreceivedbyaddress' no change addrs return (furszy)324f00a642refactor: 'ListReceived' use optional for filtered address (furszy)b459fc122frefactor: RPC 'ListReceived', encapsulate m_address_book access (furszy)fa9f2ab8fdrefactor: RPC 'listlabels', encapsulate 'CWallet::ListAddrBookLabels' functionality (furszy)83e42c4b94refactor: use 'ForEachAddrBookEntry' in RPC 'getaddressesbylabel' (furszy)2b48642499refactor: use ForEachAddrBookEntry in interfaces::getAddresses (furszy)032842ae41wallet: implement ForEachAddrBookEntry method (furszy)09649bc95drefactor: implement general 'ListAddrBookAddresses' for addressbook destinations lookup (furszy)192eb1e61crefactor: getAddress don't access m_address_book, use FindAddressEntry function (furszy) Pull request description: ### Context The wallet's `m_address_book` field is being accessed directly from several places across the sources. ### Problem Code structure wise, we shouldn't be accessing it directly. It could end up being modified by mistake (from a place that has nothing to do with the wallet like an RPC command or the GUI) and cause a bigger issue: like an address book entry 'purpose' string change, which if done badly (from 'send' to 'receive'), could end up in a user sharing a "receive" address that he/she doesn't own. ### Solution Encapsulate `m_address_book` access inside the wallet. ------------------------------------------------------- Extra Note: This is the initial step towards decoupling the address book functionality from the wallet's sources. In other words, the creation of the `AddressBookManager` (which will be coming in a follow-up PR). ACKs for top commit: achow101: ACKd69045e291theStack: ACKd69045e291✅ w0xlt: ACK https://github.com/bitcoin/bitcoin/pull/25337/commits/d69045e291e32e02d105d1b5ff1c8b86db0ae69e Tree-SHA512: dba17acd86f171b4e9af0223bbbcad380048570f6a2f6a92732a51f01abe8806debaf65c9e9e5569fa76a541903cbb50adcb5f56ef77858151c698ae6b218e2a
This commit is contained in:
8 files changed
+108
-92
No files matched your search
@@ -114,7 +114,7 @@ public:
|
|||||||
std::string* purpose) = 0;
|
std::string* purpose) = 0;
|
||||||
|
|
||||||
//! Get wallet address list.
|
//! Get wallet address list.
|
||||||
virtual std::vector<WalletAddress> getAddresses() = 0;
|
virtual std::vector<WalletAddress> getAddresses() const = 0;
|
||||||
|
|
||||||
//! Get receive requests.
|
//! Get receive requests.
|
||||||
virtual std::vector<std::string> getAddressReceiveRequests() = 0;
|
virtual std::vector<std::string> getAddressReceiveRequests() = 0;
|
||||||
|
|||||||
@@ -191,29 +191,27 @@ public:
|
|||||||
std::string* purpose) override
|
std::string* purpose) override
|
||||||
{
|
{
|
||||||
LOCK(m_wallet->cs_wallet);
|
LOCK(m_wallet->cs_wallet);
|
||||||
auto it = m_wallet->m_address_book.find(dest);
|
const auto& entry = m_wallet->FindAddressBookEntry(dest, /*allow_change=*/false);
|
||||||
if (it == m_wallet->m_address_book.end() || it->second.IsChange()) {
|
if (!entry) return false; // addr not found
|
||||||
return false;
|
|
||||||
}
|
|
||||||
if (name) {
|
if (name) {
|
||||||
*name = it->second.GetLabel();
|
*name = entry->GetLabel();
|
||||||
}
|
}
|
||||||
if (is_mine) {
|
if (is_mine) {
|
||||||
*is_mine = m_wallet->IsMine(dest);
|
*is_mine = m_wallet->IsMine(dest);
|
||||||
}
|
}
|
||||||
if (purpose) {
|
if (purpose) {
|
||||||
*purpose = it->second.purpose;
|
*purpose = entry->purpose;
|
||||||
}
|
}
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
std::vector<WalletAddress> getAddresses() override
|
std::vector<WalletAddress> getAddresses() const override
|
||||||
{
|
{
|
||||||
LOCK(m_wallet->cs_wallet);
|
LOCK(m_wallet->cs_wallet);
|
||||||
std::vector<WalletAddress> result;
|
std::vector<WalletAddress> result;
|
||||||
for (const auto& item : m_wallet->m_address_book) {
|
m_wallet->ForEachAddrBookEntry([&](const CTxDestination& dest, const std::string& label, const std::string& purpose, bool is_change) EXCLUSIVE_LOCKS_REQUIRED(m_wallet->cs_wallet) {
|
||||||
if (item.second.IsChange()) continue;
|
if (is_change) return;
|
||||||
result.emplace_back(item.first, m_wallet->IsMine(item.first), item.second.GetLabel(), item.second.purpose);
|
result.emplace_back(dest, m_wallet->IsMine(dest), label, purpose);
|
||||||
}
|
});
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
std::vector<std::string> getAddressReceiveRequests() override {
|
std::vector<std::string> getAddressReceiveRequests() override {
|
||||||
|
|||||||
@@ -637,17 +637,6 @@ RPCHelpMan getaddressinfo()
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Convert CAddressBookData to JSON record. */
|
|
||||||
static UniValue AddressBookDataToJSON(const CAddressBookData& data, const bool verbose)
|
|
||||||
{
|
|
||||||
UniValue ret(UniValue::VOBJ);
|
|
||||||
if (verbose) {
|
|
||||||
ret.pushKV("name", data.GetLabel());
|
|
||||||
}
|
|
||||||
ret.pushKV("purpose", data.purpose);
|
|
||||||
return ret;
|
|
||||||
}
|
|
||||||
|
|
||||||
RPCHelpMan getaddressesbylabel()
|
RPCHelpMan getaddressesbylabel()
|
||||||
{
|
{
|
||||||
return RPCHelpMan{"getaddressesbylabel",
|
return RPCHelpMan{"getaddressesbylabel",
|
||||||
@@ -680,10 +669,10 @@ RPCHelpMan getaddressesbylabel()
|
|||||||
// Find all addresses that have the given label
|
// Find all addresses that have the given label
|
||||||
UniValue ret(UniValue::VOBJ);
|
UniValue ret(UniValue::VOBJ);
|
||||||
std::set<std::string> addresses;
|
std::set<std::string> addresses;
|
||||||
for (const std::pair<const CTxDestination, CAddressBookData>& item : pwallet->m_address_book) {
|
pwallet->ForEachAddrBookEntry([&](const CTxDestination& _dest, const std::string& _label, const std::string& _purpose, bool _is_change) {
|
||||||
if (item.second.IsChange()) continue;
|
if (_is_change) return;
|
||||||
if (item.second.GetLabel() == label) {
|
if (_label == label) {
|
||||||
std::string address = EncodeDestination(item.first);
|
std::string address = EncodeDestination(_dest);
|
||||||
// CWallet::m_address_book is not expected to contain duplicate
|
// CWallet::m_address_book is not expected to contain duplicate
|
||||||
// address strings, but build a separate set as a precaution just in
|
// address strings, but build a separate set as a precaution just in
|
||||||
// case it does.
|
// case it does.
|
||||||
@@ -693,9 +682,11 @@ RPCHelpMan getaddressesbylabel()
|
|||||||
// and since duplicate addresses are unexpected (checked with
|
// and since duplicate addresses are unexpected (checked with
|
||||||
// std::set in O(log(N))), UniValue::__pushKV is used instead,
|
// std::set in O(log(N))), UniValue::__pushKV is used instead,
|
||||||
// which currently is O(1).
|
// which currently is O(1).
|
||||||
ret.__pushKV(address, AddressBookDataToJSON(item.second, false));
|
UniValue value(UniValue::VOBJ);
|
||||||
|
value.pushKV("purpose", _purpose);
|
||||||
|
ret.__pushKV(address, value);
|
||||||
}
|
}
|
||||||
}
|
});
|
||||||
|
|
||||||
if (ret.empty()) {
|
if (ret.empty()) {
|
||||||
throw JSONRPCError(RPC_WALLET_INVALID_LABEL_NAME, std::string("No addresses with label " + label));
|
throw JSONRPCError(RPC_WALLET_INVALID_LABEL_NAME, std::string("No addresses with label " + label));
|
||||||
@@ -742,13 +733,7 @@ RPCHelpMan listlabels()
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Add to a set to sort by label name, then insert into Univalue array
|
// Add to a set to sort by label name, then insert into Univalue array
|
||||||
std::set<std::string> label_set;
|
std::set<std::string> label_set = pwallet->ListAddrBookLabels(purpose);
|
||||||
for (const std::pair<const CTxDestination, CAddressBookData>& entry : pwallet->m_address_book) {
|
|
||||||
if (entry.second.IsChange()) continue;
|
|
||||||
if (purpose.empty() || entry.second.purpose == purpose) {
|
|
||||||
label_set.insert(entry.second.GetLabel());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
UniValue ret(UniValue::VARR);
|
UniValue ret(UniValue::VARR);
|
||||||
for (const std::string& name : label_set) {
|
for (const std::string& name : label_set) {
|
||||||
|
|||||||
@@ -18,10 +18,10 @@
|
|||||||
namespace wallet {
|
namespace wallet {
|
||||||
static CAmount GetReceived(const CWallet& wallet, const UniValue& params, bool by_label) EXCLUSIVE_LOCKS_REQUIRED(wallet.cs_wallet)
|
static CAmount GetReceived(const CWallet& wallet, const UniValue& params, bool by_label) EXCLUSIVE_LOCKS_REQUIRED(wallet.cs_wallet)
|
||||||
{
|
{
|
||||||
std::set<CTxDestination> addresses;
|
std::vector<CTxDestination> addresses;
|
||||||
if (by_label) {
|
if (by_label) {
|
||||||
// Get the set of addresses assigned to label
|
// Get the set of addresses assigned to label
|
||||||
addresses = wallet.GetLabelAddresses(LabelFromValue(params[0]));
|
addresses = wallet.ListAddrBookAddresses(CWallet::AddrBookFilter{LabelFromValue(params[0])});
|
||||||
if (addresses.empty()) throw JSONRPCError(RPC_WALLET_ERROR, "Label not found in wallet");
|
if (addresses.empty()) throw JSONRPCError(RPC_WALLET_ERROR, "Label not found in wallet");
|
||||||
} else {
|
} else {
|
||||||
// Get the address
|
// Get the address
|
||||||
@@ -29,7 +29,7 @@ static CAmount GetReceived(const CWallet& wallet, const UniValue& params, bool b
|
|||||||
if (!IsValidDestination(dest)) {
|
if (!IsValidDestination(dest)) {
|
||||||
throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Invalid Bitcoin address");
|
throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Invalid Bitcoin address");
|
||||||
}
|
}
|
||||||
addresses.insert(dest);
|
addresses.emplace_back(dest);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Filter by own scripts only
|
// Filter by own scripts only
|
||||||
|
|||||||
@@ -85,14 +85,12 @@ static UniValue ListReceived(const CWallet& wallet, const UniValue& params, cons
|
|||||||
filter |= ISMINE_WATCH_ONLY;
|
filter |= ISMINE_WATCH_ONLY;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool has_filtered_address = false;
|
std::optional<CTxDestination> filtered_address{std::nullopt};
|
||||||
CTxDestination filtered_address = CNoDestination();
|
|
||||||
if (!by_label && !params[3].isNull() && !params[3].get_str().empty()) {
|
if (!by_label && !params[3].isNull() && !params[3].get_str().empty()) {
|
||||||
if (!IsValidDestinationString(params[3].get_str())) {
|
if (!IsValidDestinationString(params[3].get_str())) {
|
||||||
throw JSONRPCError(RPC_WALLET_ERROR, "address_filter parameter was invalid");
|
throw JSONRPCError(RPC_WALLET_ERROR, "address_filter parameter was invalid");
|
||||||
}
|
}
|
||||||
filtered_address = DecodeDestination(params[3].get_str());
|
filtered_address = DecodeDestination(params[3].get_str());
|
||||||
has_filtered_address = true;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Tally
|
// Tally
|
||||||
@@ -106,23 +104,21 @@ static UniValue ListReceived(const CWallet& wallet, const UniValue& params, cons
|
|||||||
|
|
||||||
// Coinbase with less than 1 confirmation is no longer in the main chain
|
// Coinbase with less than 1 confirmation is no longer in the main chain
|
||||||
if ((wtx.IsCoinBase() && (nDepth < 1))
|
if ((wtx.IsCoinBase() && (nDepth < 1))
|
||||||
|| (wallet.IsTxImmatureCoinBase(wtx) && !include_immature_coinbase))
|
|| (wallet.IsTxImmatureCoinBase(wtx) && !include_immature_coinbase)) {
|
||||||
{
|
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
for (const CTxOut& txout : wtx.tx->vout)
|
for (const CTxOut& txout : wtx.tx->vout) {
|
||||||
{
|
|
||||||
CTxDestination address;
|
CTxDestination address;
|
||||||
if (!ExtractDestination(txout.scriptPubKey, address))
|
if (!ExtractDestination(txout.scriptPubKey, address))
|
||||||
continue;
|
continue;
|
||||||
|
|
||||||
if (has_filtered_address && !(filtered_address == address)) {
|
if (filtered_address && !(filtered_address == address)) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
isminefilter mine = wallet.IsMine(address);
|
isminefilter mine = wallet.IsMine(address);
|
||||||
if(!(mine & filter))
|
if (!(mine & filter))
|
||||||
continue;
|
continue;
|
||||||
|
|
||||||
tallyitem& item = mapTally[address];
|
tallyitem& item = mapTally[address];
|
||||||
@@ -138,70 +134,55 @@ static UniValue ListReceived(const CWallet& wallet, const UniValue& params, cons
|
|||||||
UniValue ret(UniValue::VARR);
|
UniValue ret(UniValue::VARR);
|
||||||
std::map<std::string, tallyitem> label_tally;
|
std::map<std::string, tallyitem> label_tally;
|
||||||
|
|
||||||
// Create m_address_book iterator
|
const auto& func = [&](const CTxDestination& address, const std::string& label, const std::string& purpose, bool is_change) {
|
||||||
// If we aren't filtering, go from begin() to end()
|
if (is_change) return; // no change addresses
|
||||||
auto start = wallet.m_address_book.begin();
|
|
||||||
auto end = wallet.m_address_book.end();
|
|
||||||
// If we are filtering, find() the applicable entry
|
|
||||||
if (has_filtered_address) {
|
|
||||||
start = wallet.m_address_book.find(filtered_address);
|
|
||||||
if (start != end) {
|
|
||||||
end = std::next(start);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
for (auto item_it = start; item_it != end; ++item_it)
|
|
||||||
{
|
|
||||||
if (item_it->second.IsChange()) continue;
|
|
||||||
const CTxDestination& address = item_it->first;
|
|
||||||
const std::string& label = item_it->second.GetLabel();
|
|
||||||
auto it = mapTally.find(address);
|
auto it = mapTally.find(address);
|
||||||
if (it == mapTally.end() && !fIncludeEmpty)
|
if (it == mapTally.end() && !fIncludeEmpty)
|
||||||
continue;
|
return;
|
||||||
|
|
||||||
CAmount nAmount = 0;
|
CAmount nAmount = 0;
|
||||||
int nConf = std::numeric_limits<int>::max();
|
int nConf = std::numeric_limits<int>::max();
|
||||||
bool fIsWatchonly = false;
|
bool fIsWatchonly = false;
|
||||||
if (it != mapTally.end())
|
if (it != mapTally.end()) {
|
||||||
{
|
|
||||||
nAmount = (*it).second.nAmount;
|
nAmount = (*it).second.nAmount;
|
||||||
nConf = (*it).second.nConf;
|
nConf = (*it).second.nConf;
|
||||||
fIsWatchonly = (*it).second.fIsWatchonly;
|
fIsWatchonly = (*it).second.fIsWatchonly;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (by_label)
|
if (by_label) {
|
||||||
{
|
|
||||||
tallyitem& _item = label_tally[label];
|
tallyitem& _item = label_tally[label];
|
||||||
_item.nAmount += nAmount;
|
_item.nAmount += nAmount;
|
||||||
_item.nConf = std::min(_item.nConf, nConf);
|
_item.nConf = std::min(_item.nConf, nConf);
|
||||||
_item.fIsWatchonly = fIsWatchonly;
|
_item.fIsWatchonly = fIsWatchonly;
|
||||||
}
|
} else {
|
||||||
else
|
|
||||||
{
|
|
||||||
UniValue obj(UniValue::VOBJ);
|
UniValue obj(UniValue::VOBJ);
|
||||||
if(fIsWatchonly)
|
if (fIsWatchonly) obj.pushKV("involvesWatchonly", true);
|
||||||
obj.pushKV("involvesWatchonly", true);
|
|
||||||
obj.pushKV("address", EncodeDestination(address));
|
obj.pushKV("address", EncodeDestination(address));
|
||||||
obj.pushKV("amount", ValueFromAmount(nAmount));
|
obj.pushKV("amount", ValueFromAmount(nAmount));
|
||||||
obj.pushKV("confirmations", (nConf == std::numeric_limits<int>::max() ? 0 : nConf));
|
obj.pushKV("confirmations", (nConf == std::numeric_limits<int>::max() ? 0 : nConf));
|
||||||
obj.pushKV("label", label);
|
obj.pushKV("label", label);
|
||||||
UniValue transactions(UniValue::VARR);
|
UniValue transactions(UniValue::VARR);
|
||||||
if (it != mapTally.end())
|
if (it != mapTally.end()) {
|
||||||
{
|
for (const uint256& _item : (*it).second.txids) {
|
||||||
for (const uint256& _item : (*it).second.txids)
|
|
||||||
{
|
|
||||||
transactions.push_back(_item.GetHex());
|
transactions.push_back(_item.GetHex());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
obj.pushKV("txids", transactions);
|
obj.pushKV("txids", transactions);
|
||||||
ret.push_back(obj);
|
ret.push_back(obj);
|
||||||
}
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
if (filtered_address) {
|
||||||
|
const auto& entry = wallet.FindAddressBookEntry(*filtered_address, /*allow_change=*/false);
|
||||||
|
if (entry) func(*filtered_address, entry->GetLabel(), entry->purpose, /*is_change=*/false);
|
||||||
|
} else {
|
||||||
|
// No filtered addr, walk-through the addressbook entry
|
||||||
|
wallet.ForEachAddrBookEntry(func);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (by_label)
|
if (by_label) {
|
||||||
{
|
for (const auto& entry : label_tally) {
|
||||||
for (const auto& entry : label_tally)
|
|
||||||
{
|
|
||||||
CAmount nAmount = entry.second.nAmount;
|
CAmount nAmount = entry.second.nAmount;
|
||||||
int nConf = entry.second.nConf;
|
int nConf = entry.second.nConf;
|
||||||
UniValue obj(UniValue::VOBJ);
|
UniValue obj(UniValue::VOBJ);
|
||||||
|
|||||||
+33
-9
@@ -2368,21 +2368,45 @@ 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);
|
AssertLockHeld(cs_wallet);
|
||||||
std::set<CTxDestination> result;
|
for (const std::pair<const CTxDestination, CAddressBookData>& item : m_address_book) {
|
||||||
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());
|
||||||
if (item.second.IsChange()) continue;
|
|
||||||
const CTxDestination& address = item.first;
|
|
||||||
const std::string& strName = item.second.GetLabel();
|
|
||||||
if (strName == label)
|
|
||||||
result.insert(address);
|
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
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;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
|
||||||
bool ReserveDestination::GetReservedDestination(CTxDestination& dest, bool internal, bilingual_str& error)
|
bool ReserveDestination::GetReservedDestination(CTxDestination& dest, bool internal, bilingual_str& error)
|
||||||
{
|
{
|
||||||
m_spk_man = pwallet->GetScriptPubKeyMan(type, internal);
|
m_spk_man = pwallet->GetScriptPubKeyMan(type, internal);
|
||||||
|
|||||||
+24
-1
@@ -635,7 +635,30 @@ public:
|
|||||||
|
|
||||||
std::optional<int64_t> GetOldestKeyPoolTime() const;
|
std::optional<int64_t> GetOldestKeyPoolTime() const;
|
||||||
|
|
||||||
std::set<CTxDestination> GetLabelAddresses(const std::string& label) const EXCLUSIVE_LOCKS_REQUIRED(cs_wallet);
|
// Filter struct for 'ListAddrBookAddresses'
|
||||||
|
struct AddrBookFilter {
|
||||||
|
// Fetch addresses with the provided label
|
||||||
|
std::optional<std::string> m_op_label{std::nullopt};
|
||||||
|
// Don't include change addresses by default
|
||||||
|
bool ignore_change{true};
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Filter and retrieve destinations stored in the addressbook
|
||||||
|
*/
|
||||||
|
std::vector<CTxDestination> ListAddrBookAddresses(const std::optional<AddrBookFilter>& filter) const EXCLUSIVE_LOCKS_REQUIRED(cs_wallet);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Retrieve all the known labels in the address book
|
||||||
|
*/
|
||||||
|
std::set<std::string> ListAddrBookLabels(const std::string& purpose) const EXCLUSIVE_LOCKS_REQUIRED(cs_wallet);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Walk-through the address book entries.
|
||||||
|
* Stops when the provided 'ListAddrBookFunc' returns false.
|
||||||
|
*/
|
||||||
|
using ListAddrBookFunc = std::function<void(const CTxDestination& dest, const std::string& label, const std::string& purpose, bool is_change)>;
|
||||||
|
void ForEachAddrBookEntry(const ListAddrBookFunc& func) const EXCLUSIVE_LOCKS_REQUIRED(cs_wallet);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Marks all outputs in each one of the destinations dirty, so their cache is
|
* Marks all outputs in each one of the destinations dirty, so their cache is
|
||||||
|
|||||||
@@ -57,6 +57,11 @@ class ReceivedByTest(BitcoinTestFramework):
|
|||||||
{"address": empty_addr},
|
{"address": empty_addr},
|
||||||
{"address": empty_addr, "label": "", "amount": 0, "confirmations": 0, "txids": []})
|
{"address": empty_addr, "label": "", "amount": 0, "confirmations": 0, "txids": []})
|
||||||
|
|
||||||
|
# No returned addy should be a change addr
|
||||||
|
for node in self.nodes:
|
||||||
|
for addr_obj in node.listreceivedbyaddress():
|
||||||
|
assert_equal(node.getaddressinfo(addr_obj["address"])["ischange"], False)
|
||||||
|
|
||||||
# Test Address filtering
|
# Test Address filtering
|
||||||
# Only on addr
|
# Only on addr
|
||||||
expected = {"address": addr, "label": "", "amount": Decimal("0.1"), "confirmations": 10, "txids": [txid, ]}
|
expected = {"address": addr, "label": "", "amount": Decimal("0.1"), "confirmations": 10, "txids": [txid, ]}
|
||||||
|
|||||||
Reference in new issue
Block a user