Skip to content

Commit

Permalink
test: add silent payment to wallet descriptor tests
Browse files Browse the repository at this point in the history
- Always set `internal` to `false` for sp descriptors in `listdescriptors` list
- On `getaddressinfo` for silent payment address, search for scan key among wallet silent payment spkmans
- Return the same silent payment for internal and external use
  • Loading branch information
Eunovo committed Aug 13, 2024
1 parent cd48ed2 commit ffdefab
Show file tree
Hide file tree
Showing 7 changed files with 83 additions and 17 deletions.
5 changes: 4 additions & 1 deletion src/rpc/util.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -283,7 +283,10 @@ class DescribeAddressVisitor

UniValue operator()(const V0SilentPaymentDestination& dest) const
{
return UniValue(UniValue::VOBJ);
UniValue obj(UniValue::VOBJ);
obj.pushKV("isscript", false);
obj.pushKV("iswitness", false);
return obj;
}

UniValue operator()(const PubKeyDestination& dest) const
Expand Down
24 changes: 24 additions & 0 deletions src/wallet/rpc/addresses.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -7,15 +7,18 @@
#include <core_io.h>
#include <key_io.h>
#include <rpc/util.h>
#include <script/descriptor.h>
#include <script/script.h>
#include <script/solver.h>
#include <silentpaymentkey.h>
#include <util/bip32.h>
#include <util/translation.h>
#include <wallet/receive.h>
#include <wallet/rpc/util.h>
#include <wallet/wallet.h>

#include <univalue.h>
#include <variant>

namespace wallet {
RPCHelpMan getnewaddress()
Expand Down Expand Up @@ -592,6 +595,27 @@ RPCHelpMan getaddressinfo()

UniValue ret(UniValue::VOBJ);

auto sp_dest = std::get_if<V0SilentPaymentDestination>(&dest);
if (sp_dest) {
for (const auto& sp_spk_man : pwallet->GetSilentPaymentsSPKMs()) {
LOCK(sp_spk_man->cs_desc_man);
auto wallet_desc = sp_spk_man->GetWalletDescriptor();
auto sppub = GetSpPubKeyFrom(wallet_desc.descriptor);
CHECK_NONFATAL(sppub.has_value());
auto scan_pubkey = sppub->scanKey.GetPubKey();
if (scan_pubkey != sp_dest->m_scan_pubkey) continue;

SpPubKey dest_sppub(sppub->scanKey, sp_dest->m_spend_pubkey);
std::string sppub_str = EncodeSpPubKey(dest_sppub);
std::string desc_str = "sp("+ sppub_str +")";
FlatSigningProvider keys;
std::string error;
std::unique_ptr<Descriptor> desc = Parse(desc_str, keys, error, false);
ret.pushKV("desc", desc->ToString());
ret.pushKV("parent_desc", wallet_desc.descriptor->ToString());
}
}

std::string currentAddress = EncodeDestination(dest);
ret.pushKV("address", currentAddress);

Expand Down
2 changes: 1 addition & 1 deletion src/wallet/rpc/wallet.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -940,7 +940,7 @@ static RPCHelpMan createwalletdescriptor()
"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\"."},
{"type", RPCArg::Type::STR, RPCArg::Optional::NO, "The address type the descriptor will produce. Options are \"legacy\", \"p2sh-segwit\", \"bech32\", \"bech32m\" and \"silent-payment\"."},
{"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"},
Expand Down
17 changes: 15 additions & 2 deletions src/wallet/wallet.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -3609,10 +3609,19 @@ ScriptPubKeyMan* CWallet::GetScriptPubKeyMan(const OutputType& type, bool intern
{
const std::map<OutputType, ScriptPubKeyMan*>& spk_managers = internal ? m_internal_spk_managers : m_external_spk_managers;
std::map<OutputType, ScriptPubKeyMan*>::const_iterator it = spk_managers.find(type);
if (it == spk_managers.end()) {
if (it != spk_managers.end()) {
return it->second;
}
if (type != OutputType::SILENT_PAYMENT) {
return nullptr;
}
// Silent payment is a special case where the same spk_man works for both internal and external destinations
const std::map<OutputType, ScriptPubKeyMan*>& other_spk_managers = internal ? m_external_spk_managers : m_internal_spk_managers;
std::map<OutputType, ScriptPubKeyMan*>::const_iterator other_it = other_spk_managers.find(type);
if (other_it == other_spk_managers.end()) {
return nullptr;
}
return it->second;
return other_it->second;
}

std::set<ScriptPubKeyMan*> CWallet::GetScriptPubKeyMans(const CScript& script) const
Expand Down Expand Up @@ -3977,6 +3986,10 @@ std::optional<bool> CWallet::IsInternalScriptPubKeyMan(ScriptPubKeyMan* spk_man)
const auto& type = desc_spk_man->GetWalletDescriptor().descriptor->GetOutputType();
assert(type.has_value());

if (*type == OutputType::SILENT_PAYMENT) {
return false;
}

return GetScriptPubKeyMan(*type, /* internal= */ true) == desc_spk_man;
}

Expand Down
11 changes: 9 additions & 2 deletions test/functional/test_framework/test_framework.py
Original file line number Diff line number Diff line change
Expand Up @@ -441,7 +441,7 @@ def init_wallet(self, *, node):
if wallet_name is not False:
n = self.nodes[node]
if wallet_name is not None:
n.createwallet(wallet_name=wallet_name, descriptors=self.options.descriptors, load_on_startup=True)
n.createwallet(wallet_name=wallet_name, descriptors=self.options.descriptors, silent_payment=self.options.silent_payments, load_on_startup=True)
n.importprivkey(privkey=n.get_deterministic_priv_key().key, label='coinbase', rescan=True)

# Only enables wallet support when the module is available
Expand All @@ -454,7 +454,7 @@ def run_test(self):

# Public helper methods. These can be accessed by the subclass test scripts.

def add_wallet_options(self, parser, *, descriptors=True, legacy=True):
def add_wallet_options(self, parser, *, descriptors=True, legacy=True, silent_payment=False):
kwargs = {}
if descriptors + legacy == 1:
# If only one type can be chosen, set it as default
Expand All @@ -469,6 +469,13 @@ def add_wallet_options(self, parser, *, descriptors=True, legacy=True):
group.add_argument("--legacy-wallet", action='store_const', const=False, **kwargs,
help="Run test using legacy wallets", dest='descriptors')

if silent_payment:
group.add_argument("--silent_payments", action='store_const', const=True, **kwargs,
help="Run test using a silent payment wallet", dest='silent_payments')
else:
group.add_argument("--silent_payments", action='store_const', const=False, **kwargs,
help="Run test using a silent payment wallet", dest='silent_payments')

def add_nodes(self, num_nodes: int, extra_args=None, *, rpchost=None, binary=None, binary_cli=None, versions=None):
"""Instantiate TestNode objects.
Expand Down
18 changes: 17 additions & 1 deletion test/functional/wallet_createwalletdescriptor.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@

class WalletCreateDescriptorTest(BitcoinTestFramework):
def add_options(self, parser):
self.add_wallet_options(parser, descriptors=True, legacy=False)
self.add_wallet_options(parser, descriptors=True, legacy=False, silent_payment=True)

def set_test_params(self):
self.setup_clean_chain = True
Expand All @@ -38,9 +38,12 @@ def test_basic(self):
xpub = xpub_info[0]["xpub"]
xprv = xpub_info[0]["xprv"]
expected_descs = []
sp_desc = None
for desc in def_wallet.listdescriptors()["descriptors"]:
if desc["desc"].startswith("wpkh("):
expected_descs.append(desc["desc"])
if desc["desc"].startswith("sp("):
sp_desc = desc["desc"]

assert_raises_rpc_error(-5, "Unable to determine which HD key to use from active descriptors. Please specify with 'hdkey'", wallet.createwalletdescriptor, "bech32")
assert_raises_rpc_error(-5, f"Private key for {xpub} is not known", wallet.createwalletdescriptor, type="bech32", hdkey=xpub)
Expand Down Expand Up @@ -77,6 +80,19 @@ def test_basic(self):
assert_equal(new_descs[0][1], True)
assert_equal(new_descs[0][2], True)

old_descs = curr_descs
wallet.createwalletdescriptor(type="silent-payment", internal=False)
curr_descs = set([(d["desc"], d["active"], d["internal"]) for d in wallet.listdescriptors(private=True)["descriptors"]])
new_descs = list(curr_descs - old_descs)
assert_equal(len(new_descs), 1)
assert_equal(len(wallet.gethdkeys()), 1)
assert_equal([d["desc"] for d in wallet.listdescriptors()["descriptors"] if d["desc"].startswith("sp(")][0], sp_desc)
assert_equal(new_descs[0][1], True)
assert_equal(new_descs[0][2], False)

assert_raises_rpc_error(-4, "Descriptor already exists", wallet.createwalletdescriptor, type="silent-payment", internal=False)
assert_raises_rpc_error(-4, "Descriptor already exists", wallet.createwalletdescriptor, type="silent-payment", internal=True) # SP descs are the same for both internal and external

def test_imported_other_keys(self):
self.log.info("Test createwalletdescriptor with multiple keys in active descriptors")
def_wallet = self.nodes[0].get_wallet_rpc(self.default_wallet_name)
Expand Down
23 changes: 13 additions & 10 deletions test/functional/wallet_descriptor.py
Original file line number Diff line number Diff line change
Expand Up @@ -209,19 +209,21 @@ def run_test(self):
assert_raises_rpc_error(-4, 'This wallet has no available keys', nopriv_rpc.getnewaddress)

self.log.info("Test descriptor exports")
self.nodes[0].createwallet(wallet_name='desc_export', descriptors=True)
self.nodes[0].createwallet(wallet_name='desc_export', descriptors=True, silent_payment=True)
exp_rpc = self.nodes[0].get_wallet_rpc('desc_export')
self.nodes[0].createwallet(wallet_name='desc_import', disable_private_keys=True, descriptors=True)
self.nodes[0].createwallet(wallet_name='desc_import', disable_private_keys=True, descriptors=True, silent_payment=True)
imp_rpc = self.nodes[0].get_wallet_rpc('desc_import')

addr_types = [('legacy', False, 'pkh(', '44h/1h/0h', -13),
('p2sh-segwit', False, 'sh(wpkh(', '49h/1h/0h', -14),
('bech32', False, 'wpkh(', '84h/1h/0h', -13),
('bech32m', False, 'tr(', '86h/1h/0h', -13),
('silent-payment', False, 'sp(', '', 0),
('legacy', True, 'pkh(', '44h/1h/0h', -13),
('p2sh-segwit', True, 'sh(wpkh(', '49h/1h/0h', -14),
('bech32', True, 'wpkh(', '84h/1h/0h', -13),
('bech32m', True, 'tr(', '86h/1h/0h', -13)]
('bech32m', True, 'tr(', '86h/1h/0h', -13),
('silent-payment', True, 'sp(', '', 0)]

for addr_type, internal, desc_prefix, deriv_path, int_idx in addr_types:
int_str = 'internal' if internal else 'external'
Expand All @@ -233,12 +235,13 @@ def run_test(self):
addr = exp_rpc.getnewaddress(address_type=addr_type)
desc = exp_rpc.getaddressinfo(addr)['parent_desc']
assert_equal(desc_prefix, desc[0:len(desc_prefix)])
idx = desc.index('/') + 1
assert_equal(deriv_path, desc[idx:idx + 9])
if internal:
assert_equal('1', desc[int_idx])
else:
assert_equal('0', desc[int_idx])
if deriv_path:
idx = desc.index('/') + 1
assert_equal(deriv_path, desc[idx:idx + 9])
if internal:
assert_equal('1', desc[int_idx])
else:
assert_equal('0', desc[int_idx])

self.log.info("Testing the same descriptor is returned for address type {} {}".format(addr_type, int_str))
for i in range(0, 10):
Expand All @@ -253,7 +256,7 @@ def run_test(self):
imp_rpc.importdescriptors([{
'desc': desc,
'active': True,
'next_index': 11,
'next_index': 0 if desc_prefix == "sp(" else 11,
'timestamp': 'now',
'internal': internal
}])
Expand Down

0 comments on commit ffdefab

Please sign in to comment.