Skip to content

Commit

Permalink
wallet/rpc: Add silent payment labels support
Browse files Browse the repository at this point in the history
Update the following rpc functions to ignore silent payment destinations in the wallet address book
 - getaddressbylabel
 - getreceivedbylabel
 - listreceivedbylabel
  • Loading branch information
Eunovo committed Oct 2, 2024
1 parent 708733f commit c445b64
Show file tree
Hide file tree
Showing 7 changed files with 86 additions and 32 deletions.
2 changes: 1 addition & 1 deletion src/wallet/interfaces.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -224,7 +224,7 @@ class WalletImpl : public Wallet
isminetype is_mine = m_wallet->IsMine(dest);
// In very old wallets, address purpose may not be recorded so we derive it from IsMine
result.emplace_back(dest, is_mine, purpose.value_or(is_mine ? AddressPurpose::RECEIVE : AddressPurpose::SEND), label);
});
}, std::nullopt);
return result;
}
std::vector<std::string> getAddressReceiveRequests() override {
Expand Down
31 changes: 14 additions & 17 deletions src/wallet/rpc/addresses.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -721,23 +721,20 @@ RPCHelpMan getaddressesbylabel()
UniValue ret(UniValue::VOBJ);
std::set<std::string> addresses;
pwallet->ForEachAddrBookEntry([&](const CTxDestination& _dest, const std::string& _label, bool _is_change, const std::optional<AddressPurpose>& _purpose) {
if (_is_change) return;
if (_label == label) {
std::string address = EncodeDestination(_dest);
// CWallet::m_address_book is not expected to contain duplicate
// address strings, but build a separate set as a precaution just in
// case it does.
bool unique = addresses.emplace(address).second;
CHECK_NONFATAL(unique);
// UniValue::pushKV checks if the key exists in O(N)
// and since duplicate addresses are unexpected (checked with
// std::set in O(log(N))), UniValue::pushKVEnd is used instead,
// which currently is O(1).
UniValue value(UniValue::VOBJ);
value.pushKV("purpose", _purpose ? PurposeToString(*_purpose) : "unknown");
ret.pushKVEnd(address, std::move(value));
}
});
std::string address = EncodeDestination(_dest);
// CWallet::m_address_book is not expected to contain duplicate
// address strings, but build a separate set as a precaution just in
// case it does.
bool unique = addresses.emplace(address).second;
CHECK_NONFATAL(unique);
// UniValue::pushKV checks if the key exists in O(N)
// and since duplicate addresses are unexpected (checked with
// std::set in O(log(N))), UniValue::pushKVEnd is used instead,
// which currently is O(1).
UniValue value(UniValue::VOBJ);
value.pushKV("purpose", _purpose ? PurposeToString(*_purpose) : "unknown");
ret.pushKVEnd(address, std::move(value));
}, CWallet::AddrBookFilter{.m_op_label = label, .m_ignore_output_types = std::vector{OutputType::SILENT_PAYMENT}, .ignore_change = true});

if (ret.empty()) {
throw JSONRPCError(RPC_WALLET_INVALID_LABEL_NAME, std::string("No addresses with label " + label));
Expand Down
2 changes: 1 addition & 1 deletion src/wallet/rpc/coins.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ static CAmount GetReceived(const CWallet& wallet, const UniValue& params, bool b
std::vector<CTxDestination> addresses;
if (by_label) {
// Get the set of addresses assigned to label
addresses = wallet.ListAddrBookAddresses(CWallet::AddrBookFilter{LabelFromValue(params[0])});
addresses = wallet.ListAddrBookAddresses(CWallet::AddrBookFilter{ .m_op_label = LabelFromValue(params[0]), .m_ignore_output_types = std::vector{OutputType::SILENT_PAYMENT} });
if (addresses.empty()) throw JSONRPCError(RPC_WALLET_ERROR, "Label not found in wallet");
} else {
// Get the address
Expand Down
2 changes: 1 addition & 1 deletion src/wallet/rpc/transactions.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -182,7 +182,7 @@ static UniValue ListReceived(const CWallet& wallet, const UniValue& params, cons
if (entry) func(*filtered_address, entry->GetLabel(), entry->IsChange(), entry->purpose);
} else {
// No filtered addr, walk-through the addressbook entry
wallet.ForEachAddrBookEntry(func);
wallet.ForEachAddrBookEntry(func, CWallet::AddrBookFilter{ .m_ignore_output_types = std::vector{OutputType::SILENT_PAYMENT} });
}

if (by_label) {
Expand Down
36 changes: 25 additions & 11 deletions src/wallet/wallet.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -2688,28 +2688,42 @@ void CWallet::MarkDestinationsDirty(const std::set<CTxDestination>& destinations
}
}

void CWallet::ForEachAddrBookEntry(const ListAddrBookFunc& func) const
void CWallet::ForEachAddrBookEntry(const ListAddrBookFunc& func, const std::optional<AddrBookFilter>& _filter) const
{
AssertLockHeld(cs_wallet);
AddrBookFilter filter = _filter ? *_filter : AddrBookFilter();
for (const std::pair<const CTxDestination, CAddressBookData>& item : m_address_book) {
const auto& entry = item.second;
// Filter by change
if (filter.ignore_change && entry.IsChange()) continue;
// Filter by label
if (filter.m_op_label && *filter.m_op_label != entry.GetLabel()) continue;
// Filter by output type
if (filter.m_ignore_output_types) {
auto output_type = std::visit(DestinationToOutputTypeVisitor{}, item.first);
if (!output_type) continue;
bool is_ignored = false;
for (auto ignored_type : *filter.m_ignore_output_types) {
if (*output_type == ignored_type) {
is_ignored = true;
continue;
}
}
if (is_ignored) continue;
}

// All good
func(item.first, entry.GetLabel(), entry.IsChange(), entry.purpose);
}
}

std::vector<CTxDestination> CWallet::ListAddrBookAddresses(const std::optional<AddrBookFilter>& _filter) const
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, bool is_change, const std::optional<AddressPurpose>& purpose) {
// 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
ForEachAddrBookEntry([&result](const CTxDestination& dest, const std::string& label, bool is_change, const std::optional<AddressPurpose>& purpose) {
result.emplace_back(dest);
});
}, filter);
return result;
}

Expand All @@ -2723,7 +2737,7 @@ std::set<std::string> CWallet::ListAddrBookLabels(const std::optional<AddressPur
if (!purpose || purpose == _purpose) {
label_set.insert(_label);
}
});
}, std::nullopt);
return label_set;
}

Expand Down
4 changes: 3 additions & 1 deletion src/wallet/wallet.h
Original file line number Diff line number Diff line change
Expand Up @@ -746,6 +746,8 @@ class CWallet final : public WalletStorage, public interfaces::Chain::Notificati
struct AddrBookFilter {
// Fetch addresses with the provided label
std::optional<std::string> m_op_label{std::nullopt};
// Ignore these OutputTypes
std::optional<std::vector<OutputType>> m_ignore_output_types{std::nullopt};
// Don't include change addresses by default
bool ignore_change{true};
};
Expand All @@ -765,7 +767,7 @@ class CWallet final : public WalletStorage, public interfaces::Chain::Notificati
* Stops when the provided 'ListAddrBookFunc' returns false.
*/
using ListAddrBookFunc = std::function<void(const CTxDestination& dest, const std::string& label, bool is_change, const std::optional<AddressPurpose> purpose)>;
void ForEachAddrBookEntry(const ListAddrBookFunc& func) const EXCLUSIVE_LOCKS_REQUIRED(cs_wallet);
void ForEachAddrBookEntry(const ListAddrBookFunc& func, const std::optional<AddrBookFilter>& filter) const EXCLUSIVE_LOCKS_REQUIRED(cs_wallet);

/**
* Marks all outputs in each one of the destinations dirty, so their cache is
Expand Down
41 changes: 41 additions & 0 deletions src/wallet/walletutil.h
Original file line number Diff line number Diff line change
Expand Up @@ -5,11 +5,13 @@
#ifndef BITCOIN_WALLET_WALLETUTIL_H
#define BITCOIN_WALLET_WALLETUTIL_H

#include <addresstype.h>
#include <common/bip352.h>
#include <primitives/transaction.h>
#include <pubkey.h>
#include <script/descriptor.h>
#include <silentpaymentkey.h>
#include <outputtype.h>
#include <util/fs.h>

#include <optional>
Expand Down Expand Up @@ -128,6 +130,45 @@ WalletDescriptor GenerateWalletDescriptor(const CExtKey& master_key, const Outpu
std::optional<std::pair<std::vector<XOnlyPubKey>, BIP352::PubTweakData>> GetSilentPaymentsData(const CTransaction& tx, const std::map<COutPoint, Coin>& spent_coins);

std::optional<SpPubKey> GetSpPubKeyFrom(std::shared_ptr<Descriptor> desc);

struct DestinationToOutputTypeVisitor {
std::optional<OutputType> operator()(const PubKeyDestination& dest) const {
return OutputType::LEGACY;
}

std::optional<OutputType> operator()(const PKHash& dest) const {
return OutputType::LEGACY;
}

std::optional<OutputType> operator()(const ScriptHash& dest) const {
return OutputType::P2SH_SEGWIT;
}

std::optional<OutputType> operator()(const WitnessV0KeyHash& dest) const {
return OutputType::BECH32;
}

std::optional<OutputType> operator()(const WitnessV0ScriptHash& dest) const {
return OutputType::BECH32;
}

std::optional<OutputType> operator()(const WitnessV1Taproot& dest) const {
return OutputType::BECH32M;
}

std::optional<OutputType> operator()(const V0SilentPaymentDestination& dest) const {
return OutputType::SILENT_PAYMENT;
}

std::optional<OutputType> operator()(const WitnessUnknown& dest) const {
return OutputType::UNKNOWN;
}

std::optional<OutputType> operator()(const CNoDestination& dest) const {
return std::nullopt;
}
};

} // namespace wallet

#endif // BITCOIN_WALLET_WALLETUTIL_H

0 comments on commit c445b64

Please sign in to comment.