Skip to content

Commit cbcad79

Browse files
committed
Merge bitcoin/bitcoin#21576: rpc, gui: bumpfee signer support
2c07cfa gui: bumpfee signer support (Sjors Provoost) 7e02a33 rpc: bumpfee signer support (Sjors Provoost) 304ece9 rpc: document bools in FillPSBT() calls (Sjors Provoost) Pull request description: The `bumpfee` RPC call and GUI fee bump interface now work with an external signer. ACKs for top commit: achow101: ACK 2c07cfa furszy: code review ACK 2c07cfa jarolrod: tACK 2c07cfa Tree-SHA512: 0c7b931f76fac67c9e33b9b935f29af6f69ac67a5ffcc586ed2f1676feac427735b1d971723b29ef332bb6fb5762949598ebbf728587e8f0ded95a9bfbb3e7a4
2 parents 497f265 + 2c07cfa commit cbcad79

File tree

4 files changed

+51
-9
lines changed

4 files changed

+51
-9
lines changed

src/qt/walletmodel.cpp

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -522,7 +522,9 @@ bool WalletModel::bumpFee(uint256 hash, uint256& new_hash)
522522
questionString.append(tr("Warning: This may pay the additional fee by reducing change outputs or adding inputs, when necessary. It may add a new change output if one does not already exist. These changes may potentially leak privacy."));
523523
}
524524

525-
auto confirmationDialog = new SendConfirmationDialog(tr("Confirm fee bump"), questionString, "", "", SEND_CONFIRM_DELAY, !m_wallet->privateKeysDisabled(), getOptionsModel()->getEnablePSBTControls(), nullptr);
525+
const bool enable_send{!wallet().privateKeysDisabled() || wallet().hasExternalSigner()};
526+
const bool always_show_unsigned{getOptionsModel()->getEnablePSBTControls()};
527+
auto confirmationDialog = new SendConfirmationDialog(tr("Confirm fee bump"), questionString, "", "", SEND_CONFIRM_DELAY, enable_send, always_show_unsigned, nullptr);
526528
confirmationDialog->setAttribute(Qt::WA_DeleteOnClose);
527529
// TODO: Replace QDialog::exec() with safer QDialog::show().
528530
const auto retval = static_cast<QMessageBox::StandardButton>(confirmationDialog->exec());
@@ -540,6 +542,7 @@ bool WalletModel::bumpFee(uint256 hash, uint256& new_hash)
540542

541543
// Short-circuit if we are returning a bumped transaction PSBT to clipboard
542544
if (retval == QMessageBox::Save) {
545+
// "Create Unsigned" clicked
543546
PartiallySignedTransaction psbtx(mtx);
544547
bool complete = false;
545548
const TransactionError err = wallet().fillPSBT(SIGHASH_ALL, /*sign=*/false, /*bip32derivs=*/true, nullptr, psbtx, complete);
@@ -555,7 +558,7 @@ bool WalletModel::bumpFee(uint256 hash, uint256& new_hash)
555558
return true;
556559
}
557560

558-
assert(!m_wallet->privateKeysDisabled());
561+
assert(!m_wallet->privateKeysDisabled() || wallet().hasExternalSigner());
559562

560563
// sign bumped transaction
561564
if (!m_wallet->signBumpTransaction(mtx)) {

src/wallet/feebumper.cpp

Lines changed: 16 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -293,7 +293,22 @@ Result CreateRateBumpTransaction(CWallet& wallet, const uint256& txid, const CCo
293293

294294
bool SignTransaction(CWallet& wallet, CMutableTransaction& mtx) {
295295
LOCK(wallet.cs_wallet);
296-
return wallet.SignTransaction(mtx);
296+
297+
if (wallet.IsWalletFlagSet(WALLET_FLAG_EXTERNAL_SIGNER)) {
298+
// Make a blank psbt
299+
PartiallySignedTransaction psbtx(mtx);
300+
301+
// First fill transaction with our data without signing,
302+
// so external signers are not asked to sign more than once.
303+
bool complete;
304+
wallet.FillPSBT(psbtx, complete, SIGHASH_ALL, false /* sign */, true /* bip32derivs */);
305+
const TransactionError err = wallet.FillPSBT(psbtx, complete, SIGHASH_ALL, true /* sign */, false /* bip32derivs */);
306+
if (err != TransactionError::OK) return false;
307+
complete = FinalizeAndExtractPSBT(psbtx, mtx);
308+
return complete;
309+
} else {
310+
return wallet.SignTransaction(mtx);
311+
}
297312
}
298313

299314
Result CommitTransaction(CWallet& wallet, const uint256& txid, CMutableTransaction&& mtx, std::vector<bilingual_str>& errors, uint256& bumped_txid)

src/wallet/rpc/spend.cpp

Lines changed: 8 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -82,10 +82,10 @@ static UniValue FinishTransaction(const std::shared_ptr<CWallet> pwallet, const
8282
PartiallySignedTransaction psbtx(rawTx);
8383

8484
// First fill transaction with our data without signing,
85-
// so external signers are not asked sign more than once.
85+
// so external signers are not asked to sign more than once.
8686
bool complete;
87-
pwallet->FillPSBT(psbtx, complete, SIGHASH_DEFAULT, false, true);
88-
const TransactionError err{pwallet->FillPSBT(psbtx, complete, SIGHASH_DEFAULT, true, false)};
87+
pwallet->FillPSBT(psbtx, complete, SIGHASH_DEFAULT, /*sign=*/false, /*bip32derivs=*/true);
88+
const TransactionError err{pwallet->FillPSBT(psbtx, complete, SIGHASH_DEFAULT, /*sign=*/true, /*bip32derivs=*/false)};
8989
if (err != TransactionError::OK) {
9090
throw JSONRPCTransactionError(err);
9191
}
@@ -993,7 +993,7 @@ static RPCHelpMan bumpfee_helper(std::string method_name)
993993
std::shared_ptr<CWallet> const pwallet = GetWalletForJSONRPCRequest(request);
994994
if (!pwallet) return UniValue::VNULL;
995995

996-
if (pwallet->IsWalletFlagSet(WALLET_FLAG_DISABLE_PRIVATE_KEYS) && !want_psbt) {
996+
if (pwallet->IsWalletFlagSet(WALLET_FLAG_DISABLE_PRIVATE_KEYS) && !pwallet->IsWalletFlagSet(WALLET_FLAG_EXTERNAL_SIGNER) && !want_psbt) {
997997
throw JSONRPCError(RPC_WALLET_ERROR, "bumpfee is not available with wallets that have private keys disabled. Use psbtbumpfee instead.");
998998
}
999999

@@ -1071,6 +1071,9 @@ static RPCHelpMan bumpfee_helper(std::string method_name)
10711071
// For psbtbumpfee, return the base64-encoded unsigned PSBT of the new transaction.
10721072
if (!want_psbt) {
10731073
if (!feebumper::SignTransaction(*pwallet, mtx)) {
1074+
if (pwallet->IsWalletFlagSet(WALLET_FLAG_EXTERNAL_SIGNER)) {
1075+
throw JSONRPCError(RPC_WALLET_ERROR, "Transaction incomplete. Try psbtbumpfee instead.");
1076+
}
10741077
throw JSONRPCError(RPC_WALLET_ERROR, "Can't sign transaction.");
10751078
}
10761079

@@ -1667,7 +1670,7 @@ RPCHelpMan walletcreatefundedpsbt()
16671670
// Fill transaction with out data but don't sign
16681671
bool bip32derivs = request.params[4].isNull() ? true : request.params[4].get_bool();
16691672
bool complete = true;
1670-
const TransactionError err{wallet.FillPSBT(psbtx, complete, 1, false, bip32derivs)};
1673+
const TransactionError err{wallet.FillPSBT(psbtx, complete, 1, /*sign=*/false, /*bip32derivs=*/bip32derivs)};
16711674
if (err != TransactionError::OK) {
16721675
throw JSONRPCTransactionError(err);
16731676
}

test/functional/wallet_signer.py

Lines changed: 22 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@
1313
from test_framework.test_framework import BitcoinTestFramework
1414
from test_framework.util import (
1515
assert_equal,
16+
assert_greater_than,
1617
assert_raises_rpc_error,
1718
)
1819

@@ -169,7 +170,7 @@ def test_valid_signer(self):
169170
assert_equal(result[1], {'success': True})
170171
assert_equal(mock_wallet.getwalletinfo()["txcount"], 1)
171172
dest = self.nodes[0].getnewaddress(address_type='bech32')
172-
mock_psbt = mock_wallet.walletcreatefundedpsbt([], {dest:0.5}, 0, {}, True)['psbt']
173+
mock_psbt = mock_wallet.walletcreatefundedpsbt([], {dest:0.5}, 0, {'replaceable': True}, True)['psbt']
173174
mock_psbt_signed = mock_wallet.walletprocesspsbt(psbt=mock_psbt, sign=True, sighashtype="ALL", bip32derivs=True)
174175
mock_psbt_final = mock_wallet.finalizepsbt(mock_psbt_signed["psbt"])
175176
mock_tx = mock_psbt_final["hex"]
@@ -209,6 +210,7 @@ def test_valid_signer(self):
209210

210211
self.log.info('Test send using hww1')
211212

213+
# Don't broadcast transaction yet so the RPC returns the raw hex
212214
res = hww.send(outputs={dest:0.5},options={"add_to_wallet": False})
213215
assert(res["complete"])
214216
assert_equal(res["hex"], mock_tx)
@@ -218,6 +220,25 @@ def test_valid_signer(self):
218220
res = hww.sendall(recipients=[{dest:0.5}, hww.getrawchangeaddress()],options={"add_to_wallet": False})
219221
assert(res["complete"])
220222
assert_equal(res["hex"], mock_tx)
223+
# Broadcast transaction so we can bump the fee
224+
hww.sendrawtransaction(res["hex"])
225+
226+
self.log.info('Prepare fee bumped mock PSBT')
227+
228+
# Now that the transaction is broadcast, bump fee in mock wallet:
229+
orig_tx_id = res["txid"]
230+
mock_psbt_bumped = mock_wallet.psbtbumpfee(orig_tx_id)["psbt"]
231+
mock_psbt_bumped_signed = mock_wallet.walletprocesspsbt(psbt=mock_psbt_bumped, sign=True, sighashtype="ALL", bip32derivs=True)
232+
233+
with open(os.path.join(self.nodes[1].cwd, "mock_psbt"), "w", encoding="utf8") as f:
234+
f.write(mock_psbt_bumped_signed["psbt"])
235+
236+
self.log.info('Test bumpfee using hww1')
237+
238+
# Bump fee
239+
res = hww.bumpfee(orig_tx_id)
240+
assert_greater_than(res["fee"], res["origfee"])
241+
assert_equal(res["errors"], [])
221242

222243
# # Handle error thrown by script
223244
# self.set_mock_result(self.nodes[4], "2")

0 commit comments

Comments
 (0)