Skip to content

Commit 97f7aa2

Browse files
committed
Merge branch 'rpc_getaddressinfo_isactive' into opt_wallet_segwit2
2 parents 1248d0d + 9c38c77 commit 97f7aa2

File tree

8 files changed

+296
-24
lines changed

8 files changed

+296
-24
lines changed

src/script/descriptor.cpp

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -823,8 +823,11 @@ class PKDescriptor final : public DescriptorImpl
823823
private:
824824
const bool m_xonly;
825825
protected:
826-
std::vector<CScript> MakeScripts(const std::vector<CPubKey>& keys, Span<const CScript>, FlatSigningProvider&) const override
826+
std::vector<CScript> MakeScripts(const std::vector<CPubKey>& keys, Span<const CScript>, FlatSigningProvider& out) const override
827827
{
828+
CKeyID id = keys[0].GetID();
829+
out.pubkeys.emplace(id, keys[0]);
830+
828831
if (m_xonly) {
829832
CScript script = CScript() << ToByteVector(XOnlyPubKey(keys[0])) << OP_CHECKSIG;
830833
return Vector(std::move(script));

src/wallet/rpc/addresses.cpp

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -531,6 +531,7 @@ RPCHelpMan getaddressinfo()
531531
{RPCResult::Type::STR, "address", "The bitcoin address validated."},
532532
{RPCResult::Type::STR_HEX, "scriptPubKey", "The hex-encoded output script generated by the address."},
533533
{RPCResult::Type::BOOL, "ismine", "If the address is yours."},
534+
{RPCResult::Type::BOOL, "isactive", "If the key is in the active keypool (always equal to \"ismine\" in descriptor wallets)."},
534535
{RPCResult::Type::BOOL, "iswatchonly", "If the address is watchonly."},
535536
{RPCResult::Type::BOOL, "solvable", "If we know how to spend coins sent to this address, ignoring the possible lack of private keys."},
536537
{RPCResult::Type::STR, "desc", /*optional=*/true, "A descriptor for spending coins sent to this address (only when solvable)."},
@@ -601,6 +602,7 @@ RPCHelpMan getaddressinfo()
601602

602603
isminetype mine = pwallet->IsMine(dest);
603604
ret.pushKV("ismine", bool(mine & ISMINE_SPENDABLE));
605+
ret.pushKV("isactive", pwallet->IsDestinationActive(dest));
604606

605607
if (provider) {
606608
auto inferred = InferDescriptor(scriptPubKey, *provider);

src/wallet/scriptpubkeyman.cpp

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -402,6 +402,23 @@ std::vector<WalletDestination> LegacyScriptPubKeyMan::MarkUnusedAddresses(const
402402
return result;
403403
}
404404

405+
bool LegacyDataSPKM::IsKeyActive(const CScript& script) const
406+
{
407+
LOCK(cs_KeyStore);
408+
409+
if (!IsMine(script)) return false; // Not in the keystore at all
410+
411+
for (const auto& key_id : GetAffectedKeys(script, *this)) {
412+
const auto it = mapKeyMetadata.find(key_id);
413+
if (it == mapKeyMetadata.end()) return false; // This key must be really old
414+
415+
if (!it->second.hd_seed_id.IsNull() && it->second.hd_seed_id == m_hd_chain.seed_id) return true;
416+
}
417+
418+
// Imported or dumped for a new keypool
419+
return false;
420+
}
421+
405422
void LegacyScriptPubKeyMan::UpgradeKeyMetadata()
406423
{
407424
LOCK(cs_KeyStore);

src/wallet/scriptpubkeyman.h

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -205,6 +205,9 @@ class ScriptPubKeyMan
205205
*/
206206
virtual std::vector<WalletDestination> MarkUnusedAddresses(const CScript& script) { return {}; }
207207

208+
/* Determines if address is derived from active key manager */
209+
virtual bool IsKeyActive(const CScript& script) const = 0;
210+
208211
/** Sets up the key generation stuff, i.e. generates new HD seeds and sets them as active.
209212
* Returns false if already setup or setup fails, true if setup is successful
210213
* Set force=true to make it re-setup if already setup, used for upgrades
@@ -313,6 +316,7 @@ class LegacyDataSPKM : public ScriptPubKeyMan, public FillableSigningProvider
313316

314317
// ScriptPubKeyMan overrides
315318
bool CheckDecryptionKey(const CKeyingMaterial& master_key) override;
319+
[[nodiscard]] bool IsKeyActive(const CScript& script) const override;
316320
std::unordered_set<CScript, SaltedSipHasher> GetScriptPubKeys() const override;
317321
std::unique_ptr<SigningProvider> GetSolvingProvider(const CScript& script) const override;
318322
uint256 GetID() const override { return uint256::ONE; }
@@ -650,6 +654,8 @@ class DescriptorScriptPubKeyMan : public ScriptPubKeyMan
650654

651655
std::vector<WalletDestination> MarkUnusedAddresses(const CScript& script) override;
652656

657+
[[nodiscard]] bool IsKeyActive(const CScript& script) const override { return IsMine(script); }
658+
653659
bool IsHDEnabled() const override;
654660

655661
//! Setup descriptors based on the given CExtkey

src/wallet/test/scriptpubkeyman_tests.cpp

Lines changed: 154 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -40,5 +40,159 @@ BOOST_AUTO_TEST_CASE(CanProvide)
4040
BOOST_CHECK(keyman.CanProvide(p2sh_script, data));
4141
}
4242

43+
BOOST_AUTO_TEST_CASE(Legacy_IsKeyActive)
44+
{
45+
CWallet wallet(m_node.chain.get(), "", CreateMockableWalletDatabase());
46+
{
47+
LOCK(wallet.cs_wallet);
48+
wallet.SetMinVersion(FEATURE_LATEST);
49+
wallet.m_keypool_size = 10;
50+
}
51+
LegacyScriptPubKeyMan& spkm = *wallet.GetOrCreateLegacyScriptPubKeyMan();
52+
53+
// Start off empty
54+
BOOST_CHECK(spkm.GetScriptPubKeys().empty());
55+
56+
// Generate 20 keypool keys (10 internal, 10 external)
57+
{
58+
LOCK(wallet.cs_wallet);
59+
spkm.SetupGeneration();
60+
}
61+
62+
// 4 scripts per keypool key (P2PK, P2PKH, P2WPKH, P2SH-P2WPKH)
63+
// Plus 4 scripts for the seed key
64+
auto scripts1 = spkm.GetScriptPubKeys();
65+
BOOST_CHECK_EQUAL(scripts1.size(), 84);
66+
67+
// All keys are active
68+
for (const CScript& script : scripts1) {
69+
BOOST_CHECK(spkm.IsKeyActive(script));
70+
}
71+
72+
// Requesting single from spkm should not deactivate key
73+
CTxDestination dest1;
74+
{
75+
LOCK(wallet.cs_wallet);
76+
auto result = spkm.GetNewDestination(OutputType::BECH32);
77+
dest1 = result.value();
78+
}
79+
CScript script = GetScriptForDestination(dest1);
80+
BOOST_CHECK(spkm.IsKeyActive(script));
81+
82+
// Key pool size did not change
83+
auto scripts2 = spkm.GetScriptPubKeys();
84+
BOOST_CHECK_EQUAL(scripts2.size(), 84);
85+
86+
// Use key that is not the next key
87+
// (i.e. address gap in wallet recovery)
88+
{
89+
LOCK(wallet.cs_wallet);
90+
LOCK(spkm.cs_KeyStore);
91+
auto keys = spkm.MarkReserveKeysAsUsed(5);
92+
BOOST_CHECK_EQUAL(keys.size(), 4); // Because we already used one with GetNewDestination
93+
}
94+
95+
// Key pool size did not change
96+
auto scripts3 = spkm.GetScriptPubKeys();
97+
BOOST_CHECK_EQUAL(scripts3.size(), 84);
98+
99+
// All keys are still active
100+
for (const CScript& script : scripts3) {
101+
BOOST_CHECK(spkm.IsKeyActive(script));
102+
}
103+
104+
// When user encrypts wallet for the first time,
105+
// all existing keys are removed from active keypool
106+
{
107+
LOCK(wallet.cs_wallet);
108+
// called by EncryptWallet()
109+
spkm.SetupGeneration(true);
110+
}
111+
112+
// 20 new keys were added
113+
auto scripts4 = spkm.GetScriptPubKeys();
114+
BOOST_CHECK_EQUAL(scripts4.size(), 84 * 2);
115+
116+
// All 10 original keys are now inactive
117+
for (const CScript& script : scripts3) {
118+
BOOST_CHECK(!spkm.IsKeyActive(script));
119+
}
120+
}
121+
122+
BOOST_AUTO_TEST_CASE(Descriptor_IsKeyActive)
123+
{
124+
CWallet wallet(m_node.chain.get(), "", CreateMockableWalletDatabase());
125+
{
126+
LOCK(wallet.cs_wallet);
127+
wallet.LoadMinVersion(FEATURE_LATEST);
128+
wallet.SetWalletFlag(WALLET_FLAG_DESCRIPTORS);
129+
wallet.m_keypool_size = 10;
130+
wallet.SetupDescriptorScriptPubKeyMans();
131+
}
132+
DescriptorScriptPubKeyMan* spkm = dynamic_cast<DescriptorScriptPubKeyMan*>(wallet.GetScriptPubKeyMan(OutputType::BECH32, /*internal=*/false));
133+
134+
// Start off with 10 pre-generated keys, 1 script each
135+
auto scripts1 = spkm->GetScriptPubKeys();
136+
BOOST_CHECK_EQUAL(scripts1.size(), 10);
137+
138+
// All keys are active
139+
for (const CScript& script : scripts1) {
140+
BOOST_CHECK(spkm->IsKeyActive(script));
141+
}
142+
143+
// Requesting single key from spkm should not deactivate key
144+
auto dest1 = spkm->GetNewDestination(OutputType::BECH32);
145+
CScript script = GetScriptForDestination(dest1.value());
146+
BOOST_CHECK(spkm->IsKeyActive(script));
147+
148+
// Key pool size did not change
149+
auto scripts2 = spkm->GetScriptPubKeys();
150+
BOOST_CHECK_EQUAL(scripts2.size(), 10);
151+
152+
// Use key that is not the next key
153+
// (i.e. address gap in wallet recovery)
154+
{
155+
LOCK(spkm->cs_desc_man);
156+
WalletDescriptor descriptor = spkm->GetWalletDescriptor();
157+
FlatSigningProvider provider;
158+
std::vector<CScript> scripts3;
159+
descriptor.descriptor->ExpandFromCache(/*pos=*/5, descriptor.cache, scripts3, provider);
160+
161+
BOOST_CHECK_EQUAL(scripts3.size(), 1);
162+
spkm->MarkUnusedAddresses(scripts3.front());
163+
}
164+
165+
// Key pool size increased to replace used keys
166+
auto scripts4 = spkm->GetScriptPubKeys();
167+
BOOST_CHECK_EQUAL(scripts4.size(), 16);
168+
169+
// All keys are still active
170+
for (const CScript& script : scripts4) {
171+
BOOST_CHECK(spkm->IsKeyActive(script));
172+
}
173+
174+
// When user encrypts wallet for the first time,
175+
// all existing keys are removed from active keypool
176+
{
177+
LOCK(wallet.cs_wallet);
178+
// called by EncryptWallet()
179+
wallet.SetupDescriptorScriptPubKeyMans();
180+
}
181+
182+
// This SPKM is not affected
183+
for (const CScript& script : scripts4) {
184+
BOOST_CHECK(spkm->IsKeyActive(script));
185+
}
186+
187+
// ...but at the wallet level all the keys from that SPKM are deactivated
188+
int num_script_keys_not_found = 0;
189+
for (const CScript& script : scripts4) {
190+
if (!wallet.IsDestinationActive(WitnessV0ScriptHash(script))) {
191+
++num_script_keys_not_found;
192+
}
193+
}
194+
BOOST_CHECK_EQUAL(num_script_keys_not_found, 16);
195+
}
196+
43197
BOOST_AUTO_TEST_SUITE_END()
44198
} // namespace wallet

src/wallet/wallet.cpp

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2616,6 +2616,13 @@ void CWallet::ForEachAddrBookEntry(const ListAddrBookFunc& func) const
26162616
}
26172617
}
26182618

2619+
bool CWallet::IsDestinationActive(const CTxDestination& dest) const
2620+
{
2621+
const CScript& script{GetScriptForDestination(dest)};
2622+
const std::set<ScriptPubKeyMan*>& spkms{GetActiveScriptPubKeyMans()};
2623+
return std::any_of(spkms.cbegin(), spkms.cend(), [&script](const auto& spkm) { return spkm->IsKeyActive(script); });
2624+
}
2625+
26192626
std::vector<CTxDestination> CWallet::ListAddrBookAddresses(const std::optional<AddrBookFilter>& _filter) const
26202627
{
26212628
AssertLockHeld(cs_wallet);

src/wallet/wallet.h

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -763,6 +763,11 @@ class CWallet final : public WalletStorage, public interfaces::Chain::Notificati
763763
using ListAddrBookFunc = std::function<void(const CTxDestination& dest, const std::string& label, bool is_change, const std::optional<AddressPurpose> purpose)>;
764764
void ForEachAddrBookEntry(const ListAddrBookFunc& func) const EXCLUSIVE_LOCKS_REQUIRED(cs_wallet);
765765

766+
/**
767+
* Determines if a destination is in the active spkm (not imported and not dumped for a new keypool)
768+
*/
769+
[[nodiscard]] bool IsDestinationActive(const CTxDestination& dest) const;
770+
766771
/**
767772
* Marks all outputs in each one of the destinations dirty, so their cache is
768773
* reset and does not return outdated information.

0 commit comments

Comments
 (0)