Skip to content

Commit c96cc61

Browse files
committed
wallet: use silent_payments as change fallback OutputType
This will allow wallets with only silent payment descriptors to use silent payment outputs as a change
1 parent 3fab189 commit c96cc61

File tree

5 files changed

+72
-20
lines changed

5 files changed

+72
-20
lines changed

src/wallet/spend.cpp

Lines changed: 38 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@
1010
#include <consensus/amount.h>
1111
#include <consensus/validation.h>
1212
#include <interfaces/chain.h>
13+
#include <key_io.h>
1314
#include <node/types.h>
1415
#include <numeric>
1516
#include <policy/policy.h>
@@ -1139,6 +1140,7 @@ std::optional<std::map<size_t, WitnessV1Taproot>> CreateSilentPaymentOutputs(
11391140
static util::Result<CreatedTransactionResult> CreateTransactionInternal(
11401141
CWallet& wallet,
11411142
const std::vector<CRecipient>& vecSend,
1143+
const OutputType change_type,
11421144
std::optional<unsigned int> change_pos,
11431145
const CCoinControl& coin_control,
11441146
bool sign) EXCLUSIVE_LOCKS_REQUIRED(wallet.cs_wallet)
@@ -1166,7 +1168,6 @@ static util::Result<CreatedTransactionResult> CreateTransactionInternal(
11661168
coin_selection_params.tx_noinputs_size = 10 + GetSizeOfCompactSize(vecSend.size()); // bytes for output count
11671169

11681170
CAmount recipients_sum = 0;
1169-
const OutputType change_type = wallet.TransactionChangeType(coin_control.m_change_type ? *coin_control.m_change_type : wallet.m_default_change_type, vecSend);
11701171
ReserveDestination reservedest(&wallet, change_type);
11711172
unsigned int outputs_to_subtract_fee_from = 0; // The number of outputs which we are subtracting the fee from
11721173
for (const auto& recipient : vecSend) {
@@ -1302,26 +1303,29 @@ static util::Result<CreatedTransactionResult> CreateTransactionInternal(
13021303
result.GetSelectedValue());
13031304

13041305
std::vector<CRecipient> mutableVecSend = vecSend;
1305-
if (coin_control.m_silent_payment) {
1306+
bool fSilentPayment = coin_control.m_silent_payment || std::holds_alternative<V0SilentPaymentDestination>(change_dest);
1307+
if (fSilentPayment) {
13061308
// Get the silent payment destinations, generate the scriptPubKeys,
13071309
// and update vecSend with the generated scriptPubKeys
13081310
std::map<size_t, V0SilentPaymentDestination> sp_dests;
1311+
size_t sp_change_index = 0;
13091312
for (size_t i = 0; i < mutableVecSend.size(); ++i) {
13101313
if (const auto* sp = std::get_if<V0SilentPaymentDestination>(&mutableVecSend.at(i).dest)) {
13111314
// Keep track of the index in vecSend
13121315
sp_dests[i] = *sp;
1316+
sp_change_index += 1;
13131317
}
13141318
}
1315-
if (const auto* change_sp = std::get_if<V0SilentPaymentDestination>(&change_dest)) {
1319+
if (const auto* sp_change = std::get_if<V0SilentPaymentDestination>(&change_dest)) {
13161320
// Generate output for change too
1317-
sp_dests[mutableVecSend.size()] = *change_sp;
1321+
sp_dests[sp_change_index] = *sp_change;
13181322
}
13191323
const auto& silent_payment_tr_spks = CreateSilentPaymentOutputs(wallet, sp_dests, result.GetInputSet(), error);
13201324
if (!silent_payment_tr_spks.has_value()) {
13211325
return util::Error{error};
13221326
}
13231327
for (const auto& [out_idx, tr_dest] : *silent_payment_tr_spks) {
1324-
if (out_idx == mutableVecSend.size()) {
1328+
if (out_idx == sp_change_index) {
13251329
change_dest = tr_dest;
13261330
continue;
13271331
}
@@ -1535,7 +1539,7 @@ static util::Result<CreatedTransactionResult> CreateTransactionInternal(
15351539
// TODO: this feels a bit hacky, but given the current way we handle change and receiving addresses,
15361540
// I think this is good enough. Ultimately, improving our change detection and how we handle the address
15371541
// seems like a better time to revisit this.
1538-
if (coin_control.m_silent_payment && wallet.IsWalletFlagSet(WALLET_FLAG_SILENT_PAYMENTS)) {
1542+
if (fSilentPayment && wallet.IsWalletFlagSet(WALLET_FLAG_SILENT_PAYMENTS)) {
15391543
// If our wallet supports receiving silent payments, check if this transaction is a self transfer
15401544
std::map<COutPoint, Coin> spent_coins;
15411545
for (const auto& utxo : result.GetInputSet()) {
@@ -1565,14 +1569,33 @@ util::Result<CreatedTransactionResult> CreateTransaction(
15651569

15661570
LOCK(wallet.cs_wallet);
15671571

1568-
auto res = CreateTransactionInternal(wallet, vecSend, change_pos, coin_control, sign);
1569-
TRACEPOINT(coin_selection, normal_create_tx_internal,
1572+
wallet::CreatedTransactionResult txr_ungrouped;
1573+
OutputType change_type = wallet.TransactionChangeType(coin_control.m_change_type ? *coin_control.m_change_type : wallet.m_default_change_type, vecSend);
1574+
auto res1 = CreateTransactionInternal(wallet, vecSend, change_type, change_pos, coin_control, sign);
1575+
if (!res1) {
1576+
change_type = wallet.TransactionChangeType(
1577+
coin_control.m_change_type ? *coin_control.m_change_type : wallet.m_default_change_type,
1578+
vecSend, /* exclude_sp= */true);
1579+
auto res2 = CreateTransactionInternal(wallet, vecSend, change_type, change_pos, coin_control, false);
1580+
1581+
TRACEPOINT(coin_selection, normal_create_tx_internal,
1582+
wallet.GetName().c_str(),
1583+
bool(res2),
1584+
res2 ? res2->fee : 0,
1585+
res2 && res2->change_pos.has_value() ? int32_t(*res2->change_pos) : -1);
1586+
if (!res2) return res2;
1587+
1588+
txr_ungrouped = *res2;
1589+
} else {
1590+
TRACEPOINT(coin_selection, normal_create_tx_internal,
15701591
wallet.GetName().c_str(),
1571-
bool(res),
1572-
res ? res->fee : 0,
1573-
res && res->change_pos.has_value() ? int32_t(*res->change_pos) : -1);
1574-
if (!res) return res;
1575-
const auto& txr_ungrouped = *res;
1592+
bool(res1),
1593+
res1->fee,
1594+
res1->change_pos.has_value() ? int32_t(*res1->change_pos) : -1);
1595+
1596+
txr_ungrouped = *res1;
1597+
}
1598+
15761599
// try with avoidpartialspends unless it's enabled already
15771600
if (txr_ungrouped.fee > 0 /* 0 means non-functional fee rate estimation */ && wallet.m_max_aps_fee > -1 && !coin_control.m_avoid_partial_spends) {
15781601
TRACEPOINT(coin_selection, attempting_aps_create_tx, wallet.GetName().c_str());
@@ -1586,7 +1609,7 @@ util::Result<CreatedTransactionResult> CreateTransaction(
15861609
ExtractDestination(txr_ungrouped.tx->vout[*txr_ungrouped.change_pos].scriptPubKey, tmp_cc.destChange);
15871610
}
15881611

1589-
auto txr_grouped = CreateTransactionInternal(wallet, vecSend, change_pos, tmp_cc, sign);
1612+
auto txr_grouped = CreateTransactionInternal(wallet, vecSend, change_type, change_pos, tmp_cc, sign);
15901613
// if fee of this alternative one is within the range of the max fee, we use this one
15911614
const bool use_aps{txr_grouped.has_value() ? (txr_grouped->fee <= txr_ungrouped.fee + wallet.m_max_aps_fee) : false};
15921615
TRACEPOINT(coin_selection, aps_create_tx_internal,
@@ -1601,7 +1624,7 @@ util::Result<CreatedTransactionResult> CreateTransaction(
16011624
if (use_aps) return txr_grouped;
16021625
}
16031626
}
1604-
return res;
1627+
return txr_ungrouped;
16051628
}
16061629

16071630
util::Result<CreatedTransactionResult> FundTransaction(CWallet& wallet, const CMutableTransaction& tx, const std::vector<CRecipient>& vecSend, std::optional<unsigned int> change_pos, bool lockUnspents, CCoinControl coinControl)

src/wallet/spend.h

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -219,6 +219,8 @@ struct CreatedTransactionResult
219219

220220
CreatedTransactionResult(CTransactionRef _tx, CAmount _fee, std::optional<unsigned int> _change_pos, const OutputType change_type, const FeeCalculation& _fee_calc)
221221
: tx(_tx), fee(_fee), fee_calc(_fee_calc), change_pos(_change_pos), change_type(change_type) {}
222+
223+
CreatedTransactionResult() = default;
222224
};
223225

224226
/**

src/wallet/wallet.cpp

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2237,7 +2237,7 @@ SigningResult CWallet::SignMessage(const std::string& message, const PKHash& pkh
22372237
return SigningResult::PRIVATE_KEY_NOT_AVAILABLE;
22382238
}
22392239

2240-
OutputType CWallet::TransactionChangeType(const std::optional<OutputType>& change_type, const std::vector<CRecipient>& vecSend) const
2240+
OutputType CWallet::TransactionChangeType(const std::optional<OutputType>& change_type, const std::vector<CRecipient>& vecSend, bool exclude_sp) const
22412241
{
22422242
// If -changetype is specified, always use that change type.
22432243
if (change_type) {
@@ -2270,7 +2270,7 @@ OutputType CWallet::TransactionChangeType(const std::optional<OutputType>& chang
22702270
}
22712271

22722272
const bool has_sp_spkman(GetScriptPubKeyMan(OutputType::SILENT_PAYMENTS, /*internal=*/true));
2273-
if (has_sp_spkman && (any_sp || any_tr)) {
2273+
if (has_sp_spkman && (any_sp || (any_tr && !exclude_sp))) {
22742274
return OutputType::SILENT_PAYMENTS;
22752275
}
22762276
const bool has_bech32m_spkman(GetScriptPubKeyMan(OutputType::BECH32M, /*internal=*/true));
@@ -2295,6 +2295,11 @@ OutputType CWallet::TransactionChangeType(const std::optional<OutputType>& chang
22952295
return OutputType::LEGACY;
22962296
}
22972297

2298+
bool is_silent_payments_enabled{!IsWalletFlagSet(WALLET_FLAG_DISABLE_PRIVATE_KEYS) && !IsWalletFlagSet(WALLET_FLAG_EXTERNAL_SIGNER)};
2299+
if (is_silent_payments_enabled && has_sp_spkman && !exclude_sp) {
2300+
// If silent payments are enabled, use the silent payments spkman for change
2301+
return OutputType::SILENT_PAYMENTS;
2302+
}
22982303
if (has_bech32m_spkman) {
22992304
return OutputType::BECH32M;
23002305
}

src/wallet/wallet.h

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -661,7 +661,7 @@ class CWallet final : public WalletStorage, public interfaces::Chain::Notificati
661661
bool ShouldResend() const;
662662
void ResubmitWalletTransactions(bool relay, bool force);
663663

664-
OutputType TransactionChangeType(const std::optional<OutputType>& change_type, const std::vector<CRecipient>& vecSend) const;
664+
OutputType TransactionChangeType(const std::optional<OutputType>& change_type, const std::vector<CRecipient>& vecSend, bool exclude_sp = false) const;
665665

666666
/** Fetch the inputs and sign with SIGHASH_ALL. */
667667
bool SignTransaction(CMutableTransaction& tx) const EXCLUSIVE_LOCKS_REQUIRED(cs_wallet);

test/functional/wallet_silentpayments_receiving.py

Lines changed: 24 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -147,7 +147,29 @@ def do_nothing():
147147

148148
self.log.info("Wallet persistence verified successfully")
149149

150-
def test_import_rescan(self):
150+
def test_import_descriptors(self):
151+
self.log.info("Check import silent payments descriptor into blank wallet")
152+
153+
self.nodes[0].createwallet(wallet_name="import_blank", blank=True, silent_payments=True)
154+
wallet = self.nodes[0].get_wallet_rpc("import_blank")
155+
156+
descriptor = "sp([eea23daf/352h/0h/0h/1h/0]cRCGWnoELVHTr9oZWz1TUp7jmdBJPS3Kx8UCaHvfpxL6KiVSHH1A,[eea23daf/352h/0h/0h/0h/0]cUxxbQ67tepsEn3AUKSCWAKrvLwP6MPk55DAptcqxqDiXN6yKkNW)#q42ue5dg"
157+
res = wallet.importdescriptors([{
158+
"desc": descriptor,
159+
"active": True,
160+
"next_index": 0,
161+
"timestamp": "now"
162+
}])
163+
assert_equal(res[0]["success"], True)
164+
165+
self.def_wallet.sendtoaddress(wallet.getnewaddress(address_type="silent-payments"), 10)
166+
self.generate(self.nodes[0], 1)
167+
assert_equal(wallet.getbalance(), 10)
168+
169+
wallet.send({self.def_wallet.getnewaddress(): 5})
170+
self.generate(self.nodes[0], 1)
171+
assert_approx(wallet.getbalance(), 5, 0.0001)
172+
151173
self.log.info("Check import rescan works for silent payments")
152174

153175
self.nodes[0].createwallet(wallet_name="alice", silent_payments=True)
@@ -298,7 +320,7 @@ def run_test(self):
298320
self.test_encrypting_unencrypted()
299321
self.test_basic()
300322
self.test_wallet_persistence()
301-
self.test_import_rescan()
323+
self.test_import_descriptors()
302324
self.test_createwallet_descriptor()
303325
self.test_backup_and_restore()
304326
self.test_getaddressinfo()

0 commit comments

Comments
 (0)