aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorAntoine Poinsot <darosior@protonmail.com>2022-06-29 19:04:48 +0200
committerAntoine Poinsot <darosior@protonmail.com>2022-07-19 12:46:01 +0200
commitb724476158a7dfeef9edfda3f519dfd6f93202a8 (patch)
treec5b5f53acb4f58c6ddc9d155c515c877e37a186b
parent55a82eaf91d252a04a0cc8ad7d948d956c6cb24f (diff)
downloadbitcoin-b724476158a7dfeef9edfda3f519dfd6f93202a8.tar.xz
rpc: output wallet descriptors for received entries in listsinceblock
The descriptor wallets allow an application to track coins of multiple descriptors in a single wallet. However, such an application would not previously be able to (easily) tell what received coin "belongs" to what descriptor. This commit tackles this issues by adding a "wallet_desc" entry to the entries for received coins in 'listsinceblock'.
-rw-r--r--src/wallet/rpc/transactions.cpp10
-rw-r--r--src/wallet/rpc/util.cpp9
-rw-r--r--src/wallet/rpc/util.h4
-rwxr-xr-xtest/functional/wallet_listsinceblock.py41
4 files changed, 63 insertions, 1 deletions
diff --git a/src/wallet/rpc/transactions.cpp b/src/wallet/rpc/transactions.cpp
index 0b52dcc001..db44ee7a4c 100644
--- a/src/wallet/rpc/transactions.cpp
+++ b/src/wallet/rpc/transactions.cpp
@@ -367,6 +367,7 @@ static void ListTransactions(const CWallet& wallet, const CWalletTx& wtx, int nM
entry.pushKV("involvesWatchonly", true);
}
MaybePushAddress(entry, r.destination);
+ PushParentDescriptors(wallet, wtx.tx->vout.at(r.vout).scriptPubKey, entry);
if (wtx.IsCoinBase())
{
if (wallet.GetTxDepthInMainChain(wtx) < 1)
@@ -418,7 +419,11 @@ static const std::vector<RPCResult> TransactionDescriptionString()
{RPCResult::Type::NUM_TIME, "timereceived", "The time received expressed in " + UNIX_EPOCH_TIME + "."},
{RPCResult::Type::STR, "comment", /*optional=*/true, "If a comment is associated with the transaction, only present if not empty."},
{RPCResult::Type::STR, "bip125-replaceable", "(\"yes|no|unknown\") Whether this transaction could be replaced due to BIP125 (replace-by-fee);\n"
- "may be unknown for unconfirmed transactions not in the mempool."}};
+ "may be unknown for unconfirmed transactions not in the mempool."},
+ {RPCResult::Type::ARR, "parent_descs", /*optional=*/true, "Only if 'category' is 'received'. List of parent descriptors for the scriptPubKey of this coin.", {
+ {RPCResult::Type::STR, "desc", "The descriptor string."},
+ }},
+ };
}
RPCHelpMan listtransactions()
@@ -709,6 +714,9 @@ RPCHelpMan gettransaction()
"'send' category of transactions."},
{RPCResult::Type::BOOL, "abandoned", /*optional=*/true, "'true' if the transaction has been abandoned (inputs are respendable). Only available for the \n"
"'send' category of transactions."},
+ {RPCResult::Type::ARR, "parent_descs", /*optional=*/true, "Only if 'category' is 'received'. List of parent descriptors for the scriptPubKey of this coin.", {
+ {RPCResult::Type::STR, "desc", "The descriptor string."},
+ }},
}},
}},
{RPCResult::Type::STR_HEX, "hex", "Raw data for transaction"},
diff --git a/src/wallet/rpc/util.cpp b/src/wallet/rpc/util.cpp
index 4fcb393226..16305d4465 100644
--- a/src/wallet/rpc/util.cpp
+++ b/src/wallet/rpc/util.cpp
@@ -123,6 +123,15 @@ std::string LabelFromValue(const UniValue& value)
return label;
}
+void PushParentDescriptors(const CWallet& wallet, const CScript& script_pubkey, UniValue& entry)
+{
+ UniValue parent_descs(UniValue::VARR);
+ for (const auto& desc: wallet.GetWalletDescriptors(script_pubkey)) {
+ parent_descs.push_back(desc.descriptor->ToString());
+ }
+ entry.pushKV("parent_descs", parent_descs);
+}
+
void HandleWalletError(const std::shared_ptr<CWallet> wallet, DatabaseStatus& status, bilingual_str& error)
{
if (!wallet) {
diff --git a/src/wallet/rpc/util.h b/src/wallet/rpc/util.h
index 7b810eb06e..2ca28914e7 100644
--- a/src/wallet/rpc/util.h
+++ b/src/wallet/rpc/util.h
@@ -5,6 +5,8 @@
#ifndef BITCOIN_WALLET_RPC_UTIL_H
#define BITCOIN_WALLET_RPC_UTIL_H
+#include <script/script.h>
+
#include <any>
#include <memory>
#include <string>
@@ -39,6 +41,8 @@ const LegacyScriptPubKeyMan& EnsureConstLegacyScriptPubKeyMan(const CWallet& wal
bool GetAvoidReuseFlag(const CWallet& wallet, const UniValue& param);
bool ParseIncludeWatchonly(const UniValue& include_watchonly, const CWallet& wallet);
std::string LabelFromValue(const UniValue& value);
+//! Fetch parent descriptors of this scriptPubKey.
+void PushParentDescriptors(const CWallet& wallet, const CScript& script_pubkey, UniValue& entry);
void HandleWalletError(const std::shared_ptr<CWallet> wallet, DatabaseStatus& status, bilingual_str& error);
} // namespace wallet
diff --git a/test/functional/wallet_listsinceblock.py b/test/functional/wallet_listsinceblock.py
index 1f3a276d9c..ecce6652aa 100755
--- a/test/functional/wallet_listsinceblock.py
+++ b/test/functional/wallet_listsinceblock.py
@@ -6,6 +6,7 @@
from test_framework.address import key_to_p2wpkh
from test_framework.blocktools import COINBASE_MATURITY
+from test_framework.descriptors import descsum_create
from test_framework.key import ECKey
from test_framework.test_framework import BitcoinTestFramework
from test_framework.messages import MAX_BIP125_RBF_SEQUENCE
@@ -39,6 +40,7 @@ class ListSinceBlockTest(BitcoinTestFramework):
self.test_double_send()
self.double_spends_filtered()
self.test_targetconfirmations()
+ self.test_desc()
def test_no_blockhash(self):
self.log.info("Test no blockhash")
@@ -383,5 +385,44 @@ class ListSinceBlockTest(BitcoinTestFramework):
assert_equal(original_found, False)
assert_equal(double_found, False)
+ def test_desc(self):
+ """Make sure we can track coins by descriptor."""
+ self.log.info("Test descriptor lookup by scriptPubKey.")
+
+ # Create a watchonly wallet tracking two multisig descriptors.
+ multi_a = descsum_create("wsh(multi(1,tpubD6NzVbkrYhZ4YBNjUo96Jxd1u4XKWgnoc7LsA1jz3Yc2NiDbhtfBhaBtemB73n9V5vtJHwU6FVXwggTbeoJWQ1rzdz8ysDuQkpnaHyvnvzR/*,tpubD6NzVbkrYhZ4YHdDGMAYGaWxMSC1B6tPRTHuU5t3BcfcS3nrF523iFm5waFd1pP3ZvJt4Jr8XmCmsTBNx5suhcSgtzpGjGMASR3tau1hJz4/*))")
+ multi_b = descsum_create("wsh(multi(1,tpubD6NzVbkrYhZ4YHdDGMAYGaWxMSC1B6tPRTHuU5t3BcfcS3nrF523iFm5waFd1pP3ZvJt4Jr8XmCmsTBNx5suhcSgtzpGjGMASR3tau1hJz4/*,tpubD6NzVbkrYhZ4Y2RLiuEzNQkntjmsLpPYDm3LTRBYynUQtDtpzeUKAcb9sYthSFL3YR74cdFgF5mW8yKxv2W2CWuZDFR2dUpE5PF9kbrVXNZ/*))")
+ self.nodes[0].createwallet(wallet_name="wo", descriptors=True, disable_private_keys=True)
+ wo_wallet = self.nodes[0].get_wallet_rpc("wo")
+ wo_wallet.importdescriptors([
+ {
+ "desc": multi_a,
+ "active": False,
+ "timestamp": "now",
+ },
+ {
+ "desc": multi_b,
+ "active": False,
+ "timestamp": "now",
+ },
+ ])
+
+ # Send a coin to each descriptor.
+ assert_equal(len(wo_wallet.listsinceblock()["transactions"]), 0)
+ addr_a = self.nodes[0].deriveaddresses(multi_a, 0)[0]
+ addr_b = self.nodes[0].deriveaddresses(multi_b, 0)[0]
+ self.nodes[2].sendtoaddress(addr_a, 1)
+ self.nodes[2].sendtoaddress(addr_b, 2)
+ self.generate(self.nodes[2], 1)
+
+ # We can identify on which descriptor each coin was received.
+ coins = wo_wallet.listsinceblock()["transactions"]
+ assert_equal(len(coins), 2)
+ coin_a = next(c for c in coins if c["amount"] == 1)
+ assert_equal(coin_a["parent_descs"][0], multi_a)
+ coin_b = next(c for c in coins if c["amount"] == 2)
+ assert_equal(coin_b["parent_descs"][0], multi_b)
+
+
if __name__ == '__main__':
ListSinceBlockTest().main()