Skip to content

Commit a5489c9

Browse files
instagibbspromag
authored andcommitted
IsUsedDestination should count any known single-key address
Github-Pull: #17621 Rebased-From: 0950245
1 parent 88729d8 commit a5489c9

File tree

4 files changed

+45
-15
lines changed

4 files changed

+45
-15
lines changed

src/wallet/rpcwallet.cpp

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2924,7 +2924,7 @@ static UniValue listunspent(const JSONRPCRequest& request)
29242924
CTxDestination address;
29252925
const CScript& scriptPubKey = out.tx->tx->vout[out.i].scriptPubKey;
29262926
bool fValidAddress = ExtractDestination(scriptPubKey, address);
2927-
bool reused = avoid_reuse && pwallet->IsUsedDestination(address);
2927+
bool reused = avoid_reuse && pwallet->IsUsedDestination(out.tx->GetHash(), out.i);
29282928

29292929
if (destinations.size() && (!fValidAddress || !destinations.count(address)))
29302930
continue;

src/wallet/wallet.cpp

Lines changed: 21 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1073,17 +1073,31 @@ void CWallet::SetUsedDestinationState(const uint256& hash, unsigned int n, bool
10731073
}
10741074
}
10751075

1076-
bool CWallet::IsUsedDestination(const CTxDestination& dst) const
1077-
{
1078-
LOCK(cs_wallet);
1079-
return ::IsMine(*this, dst) && GetDestData(dst, "used", nullptr);
1080-
}
1081-
10821076
bool CWallet::IsUsedDestination(const uint256& hash, unsigned int n) const
10831077
{
1078+
AssertLockHeld(cs_wallet);
10841079
CTxDestination dst;
10851080
const CWalletTx* srctx = GetWalletTx(hash);
1086-
return srctx && ExtractDestination(srctx->tx->vout[n].scriptPubKey, dst) && IsUsedDestination(dst);
1081+
if (srctx) {
1082+
assert(srctx->tx->vout.size() > n);
1083+
// When descriptor wallets arrive, these additional checks are
1084+
// likely superfluous and can be optimized out
1085+
for (const auto& keyid : GetAffectedKeys(srctx->tx->vout[n].scriptPubKey, *this)) {
1086+
WitnessV0KeyHash wpkh_dest(keyid);
1087+
if (GetDestData(wpkh_dest, "used", nullptr)) {
1088+
return true;
1089+
}
1090+
ScriptHash sh_wpkh_dest(wpkh_dest);
1091+
if (GetDestData(sh_wpkh_dest, "used", nullptr)) {
1092+
return true;
1093+
}
1094+
PKHash pkh_dest(keyid);
1095+
if (GetDestData(pkh_dest, "used", nullptr)) {
1096+
return true;
1097+
}
1098+
}
1099+
}
1100+
return false;
10871101
}
10881102

10891103
bool CWallet::AddToWallet(const CWalletTx& wtxIn, bool fFlushOnClose)

src/wallet/wallet.h

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1004,9 +1004,8 @@ class CWallet final : public FillableSigningProvider, private interfaces::Chain:
10041004

10051005
bool IsSpent(interfaces::Chain::Lock& locked_chain, const uint256& hash, unsigned int n) const EXCLUSIVE_LOCKS_REQUIRED(cs_wallet);
10061006

1007-
// Whether this or any UTXO with the same CTxDestination has been spent.
1008-
bool IsUsedDestination(const CTxDestination& dst) const;
1009-
bool IsUsedDestination(const uint256& hash, unsigned int n) const;
1007+
// Whether this or any known UTXO with the same single key has been spent.
1008+
bool IsUsedDestination(const uint256& hash, unsigned int n) const EXCLUSIVE_LOCKS_REQUIRED(cs_wallet);
10101009
void SetUsedDestinationState(const uint256& hash, unsigned int n, bool used);
10111010

10121011
std::vector<OutputGroup> GroupOutputs(const std::vector<COutput>& outputs, bool single_coin) const;

test/functional/wallet_avoidreuse.py

Lines changed: 21 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -83,7 +83,12 @@ def run_test(self):
8383
reset_balance(self.nodes[1], self.nodes[0].getnewaddress())
8484
self.test_fund_send_fund_senddirty()
8585
reset_balance(self.nodes[1], self.nodes[0].getnewaddress())
86-
self.test_fund_send_fund_send()
86+
self.test_fund_send_fund_send("legacy")
87+
reset_balance(self.nodes[1], self.nodes[0].getnewaddress())
88+
self.test_fund_send_fund_send("p2sh-segwit")
89+
reset_balance(self.nodes[1], self.nodes[0].getnewaddress())
90+
self.test_fund_send_fund_send("bech32")
91+
8792

8893
def test_persistence(self):
8994
'''Test that wallet files persist the avoid_reuse flag.'''
@@ -174,7 +179,7 @@ def test_fund_send_fund_senddirty(self):
174179
assert_approx(self.nodes[1].getbalance(), 5, 0.001)
175180
assert_approx(self.nodes[1].getbalance(avoid_reuse=False), 5, 0.001)
176181

177-
def test_fund_send_fund_send(self):
182+
def test_fund_send_fund_send(self, second_addr_type):
178183
'''
179184
Test the simple case where [1] generates a new address A, then
180185
[0] sends 10 BTC to A.
@@ -184,7 +189,7 @@ def test_fund_send_fund_send(self):
184189
[1] tries to spend 4 BTC (succeeds; change address sufficient)
185190
'''
186191

187-
fundaddr = self.nodes[1].getnewaddress()
192+
fundaddr = self.nodes[1].getnewaddress(label="", address_type="legacy")
188193
retaddr = self.nodes[0].getnewaddress()
189194

190195
self.nodes[0].sendtoaddress(fundaddr, 10)
@@ -205,7 +210,19 @@ def test_fund_send_fund_send(self):
205210
# getbalances should show no used, 5 btc trusted
206211
assert_balances(self.nodes[1], mine={"used": 0, "trusted": 5})
207212

208-
self.nodes[0].sendtoaddress(fundaddr, 10)
213+
# For the second send, we transmute it to a related single-key address
214+
# to make sure it's also detected as re-use
215+
fund_spk = self.nodes[0].getaddressinfo(fundaddr)["scriptPubKey"]
216+
fund_decoded = self.nodes[0].decodescript(fund_spk)
217+
if second_addr_type == "p2sh-segwit":
218+
new_fundaddr = fund_decoded["segwit"]["p2sh-segwit"]
219+
elif second_addr_type == "bech32":
220+
new_fundaddr = fund_decoded["segwit"]["addresses"][0]
221+
else:
222+
new_fundaddr = fundaddr
223+
assert_equal(second_addr_type, "legacy")
224+
225+
self.nodes[0].sendtoaddress(new_fundaddr, 10)
209226
self.nodes[0].generate(1)
210227
self.sync_all()
211228

0 commit comments

Comments
 (0)