Skip to content

Commit d2ce666

Browse files
achow101vijaydasmp
authored andcommitted
Merge bitcoin#28414: wallet rpc: return final tx hex from walletprocesspsbt if complete
2e249b9 doc: add release note for PR bitcoin#28414 (Matthew Zipkin) 4614332 test: remove unnecessary finalizepsbt rpc calls (ismaelsadeeq) e3d484b wallet rpc: return final tx hex from walletprocesspsbt if complete (Matthew Zipkin) Pull request description: See bitcoin#28363 (comment) `walletprocesspsbt` currently returns a base64-encoded PSBT and a boolean indicating if the tx is "complete". If it is complete, the base64 PSBT can be finalized with `finalizepsbt` which returns the hex-encoded transaction suitable for `sendrawtransaction`. With this patch, `walletprocesspsbt` return object will ALSO include the broadcast-able hex string if the tx is already final. This saves users the extra step of calling `finalizepsbt` assuming they have already inspected and approve the transaction from earlier steps. ACKs for top commit: ismaelsadeeq: re ACK 2e249b9 BrandonOdiwuor: re ACK 2e249b9 Randy808: Tested ACK 2e249b9 achow101: ACK 2e249b9 ishaanam: ACK 2e249b9 Tree-SHA512: 229c1103265a9b4248f080935a7ad5607c3be3f9a096a9ab6554093b2cd8aa8b4d1fa55b1b97d3925ba208dbc3ccba4e4d37c40e1491db0d27ba3d9fe98f931e
1 parent cfcbc8d commit d2ce666

7 files changed

Lines changed: 38 additions & 22 deletions

File tree

doc/release-notes-28414.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
RPC Wallet
2+
----------
3+
4+
- RPC `walletprocesspsbt` return object now includes field `hex` (if the transaction
5+
is complete) containing the serialized transaction suitable for RPC `sendrawtransaction`. (#28414)

src/wallet/rpc/spend.cpp

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1278,6 +1278,7 @@ RPCHelpMan walletprocesspsbt()
12781278
{
12791279
{RPCResult::Type::STR, "psbt", "The base64-encoded partially signed transaction"},
12801280
{RPCResult::Type::BOOL, "complete", "If the transaction has a complete set of signatures"},
1281+
{RPCResult::Type::STR_HEX, "hex", /*optional=*/true, "The hex-encoded network transaction if complete"},
12811282
}
12821283
},
12831284
RPCExamples{
@@ -1327,6 +1328,14 @@ RPCHelpMan walletprocesspsbt()
13271328
ssTx << psbtx;
13281329
result.pushKV("psbt", EncodeBase64(ssTx.str()));
13291330
result.pushKV("complete", complete);
1331+
if (complete) {
1332+
CMutableTransaction mtx;
1333+
// Returns true if complete, which we already think it is.
1334+
CHECK_NONFATAL(FinalizeAndExtractPSBT(psbtx, mtx));
1335+
CDataStream ssTx_final(SER_NETWORK, PROTOCOL_VERSION);
1336+
ssTx_final << mtx;
1337+
result.pushKV("hex", HexStr(ssTx_final));
1338+
}
13301339

13311340
return result;
13321341
},

test/functional/rpc_psbt.py

Lines changed: 20 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -77,12 +77,21 @@ def run_test(self):
7777

7878
self.nodes[0].walletpassphrase(passphrase="password", timeout=1000000)
7979

80-
# Sign the transaction and send
81-
signed_tx = self.nodes[0].walletprocesspsbt(psbt=psbtx, finalize=False)['psbt']
82-
finalized_tx = self.nodes[0].walletprocesspsbt(psbt=psbtx, finalize=True)['psbt']
83-
assert signed_tx != finalized_tx
84-
final_tx = self.nodes[0].finalizepsbt(signed_tx)['hex']
85-
self.nodes[0].sendrawtransaction(final_tx)
80+
# Sign the transaction but don't finalize
81+
processed_psbt = self.nodes[0].walletprocesspsbt(psbt=psbtx, finalize=False)
82+
assert "hex" not in processed_psbt
83+
signed_psbt = processed_psbt['psbt']
84+
85+
# Finalize and send
86+
finalized_hex = self.nodes[0].finalizepsbt(signed_psbt)['hex']
87+
self.nodes[0].sendrawtransaction(finalized_hex)
88+
89+
# Alternative method: sign AND finalize in one command
90+
processed_finalized_psbt = self.nodes[0].walletprocesspsbt(psbt=psbtx, finalize=True)
91+
finalized_psbt = processed_finalized_psbt['psbt']
92+
finalized_psbt_hex = processed_finalized_psbt['hex']
93+
assert signed_psbt != finalized_psbt
94+
assert finalized_psbt_hex == finalized_hex
8695

8796
# Manually selected inputs can be locked:
8897
assert_equal(len(self.nodes[0].listlockunspent()), 0)
@@ -139,7 +148,7 @@ def run_test(self):
139148
# Check decodepsbt fee calculation (input values shall only be counted once per UTXO)
140149
assert_equal(decoded['fee'], created_psbt['fee'])
141150
assert_equal(walletprocesspsbt_out['complete'], True)
142-
self.nodes[1].sendrawtransaction(self.nodes[1].finalizepsbt(walletprocesspsbt_out['psbt'])['hex'])
151+
self.nodes[1].sendrawtransaction(walletprocesspsbt_out['hex'])
143152

144153
self.log.info("Test walletcreatefundedpsbt fee rate of 10000 sat/vB and 0.1 BTC/kvB produces a total fee at or slightly below -maxtxfee (~0.05290000)")
145154
res1 = self.nodes[1].walletcreatefundedpsbt(inputs, outputs, 0, {"fee_rate": 10000, "add_inputs": True})
@@ -230,7 +239,7 @@ def run_test(self):
230239
# partially sign with node 2. This should be complete and sendable
231240
walletprocesspsbt_out = self.nodes[2].walletprocesspsbt(psbtx)
232241
assert_equal(walletprocesspsbt_out['complete'], True)
233-
self.nodes[2].sendrawtransaction(self.nodes[2].finalizepsbt(walletprocesspsbt_out['psbt'])['hex'])
242+
self.nodes[2].sendrawtransaction(walletprocesspsbt_out['hex'])
234243

235244
# check that walletprocesspsbt fails to decode a non-psbt
236245
rawtx = self.nodes[1].createrawtransaction([{"txid":txid,"vout":p2pkh_pos}], {self.nodes[1].getnewaddress():9.99})
@@ -514,14 +523,13 @@ def test_psbt_input_keys(psbt_input, keys):
514523
assert not signed['complete']
515524
signed = self.nodes[0].walletprocesspsbt(signed['psbt'])
516525
assert signed['complete']
517-
self.nodes[0].finalizepsbt(signed['psbt'])
518526

519527
psbt = wallet.walletcreatefundedpsbt([ext_utxo], {self.nodes[0].getnewaddress(): 15}, 0, {"add_inputs": True, "solving_data":{"descriptors": [desc]}})
520528
signed = wallet.walletprocesspsbt(psbt['psbt'])
521529
assert not signed['complete']
522530
signed = self.nodes[0].walletprocesspsbt(signed['psbt'])
523531
assert signed['complete']
524-
final = self.nodes[0].finalizepsbt(signed['psbt'], False)
532+
final = signed['hex']
525533

526534
dec = self.nodes[0].decodepsbt(signed["psbt"])
527535
for i, txin in enumerate(dec["tx"]["vin"]):
@@ -555,8 +563,8 @@ def test_psbt_input_keys(psbt_input, keys):
555563
)
556564
signed = wallet.walletprocesspsbt(psbt["psbt"])
557565
signed = self.nodes[0].walletprocesspsbt(signed["psbt"])
558-
final = self.nodes[0].finalizepsbt(signed["psbt"])
559-
assert self.nodes[0].testmempoolaccept([final["hex"]])[0]["allowed"]
566+
final = signed["hex"]
567+
assert self.nodes[0].testmempoolaccept([final])[0]["allowed"]
560568
# Reducing the weight should have a lower fee
561569
psbt2 = wallet.walletcreatefundedpsbt(
562570
inputs=[{"txid": ext_utxo["txid"], "vout": ext_utxo["vout"], "size": low_input_weight}],

test/functional/wallet_fundrawtransaction.py

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -530,8 +530,7 @@ def test_spend_2of2(self):
530530
funded_psbt = wmulti.walletcreatefundedpsbt(inputs=inputs, outputs=outputs, options={'changeAddress': w2.getrawchangeaddress()})['psbt']
531531

532532
signed_psbt = w2.walletprocesspsbt(funded_psbt)
533-
final_psbt = w2.finalizepsbt(signed_psbt['psbt'])
534-
self.nodes[2].sendrawtransaction(final_psbt['hex'])
533+
self.nodes[2].sendrawtransaction(signed_psbt['hex'])
535534
self.generate(self.nodes[2], 1)
536535

537536
# Make sure funds are received at node1.

test/functional/wallet_multisig_descriptor_psbt.py

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -150,8 +150,7 @@ def run_test(self):
150150
signing_wallet = participants["signers"][m]
151151
psbt = signing_wallet.walletprocesspsbt(psbt["psbt"])
152152
assert_equal(psbt["complete"], m == self.M - 1)
153-
finalized = coordinator_wallet.finalizepsbt(psbt["psbt"])
154-
coordinator_wallet.sendrawtransaction(finalized["hex"])
153+
coordinator_wallet.sendrawtransaction(psbt["hex"])
155154

156155
self.log.info("Check that balances are correct after the transaction has been included in a block.")
157156
self.generate(self.nodes[0], 1)

test/functional/wallet_send.py

Lines changed: 1 addition & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -498,13 +498,11 @@ def run_test(self):
498498
signed = ext_wallet.walletprocesspsbt(res["psbt"])
499499
signed = ext_fund.walletprocesspsbt(res["psbt"])
500500
assert signed["complete"]
501-
self.nodes[0].finalizepsbt(signed["psbt"])
502501

503502
res = self.test_send(from_wallet=ext_wallet, to_wallet=self.nodes[0], amount=15, inputs=[ext_utxo], add_inputs=True, psbt=True, include_watching=True, solving_data={"descriptors": [desc]})
504503
signed = ext_wallet.walletprocesspsbt(res["psbt"])
505504
signed = ext_fund.walletprocesspsbt(res["psbt"])
506505
assert signed["complete"]
507-
self.nodes[0].finalizepsbt(signed["psbt"])
508506

509507
dec = self.nodes[0].decodepsbt(signed["psbt"])
510508
for i, txin in enumerate(dec["tx"]["vin"]):
@@ -539,8 +537,7 @@ def run_test(self):
539537
signed = ext_wallet.walletprocesspsbt(res["psbt"])
540538
signed = ext_fund.walletprocesspsbt(res["psbt"])
541539
assert signed["complete"]
542-
tx = self.nodes[0].finalizepsbt(signed["psbt"])
543-
testres = self.nodes[0].testmempoolaccept([tx["hex"]])[0]
540+
testres = self.nodes[0].testmempoolaccept([signed["hex"]])[0]
544541
assert_equal(testres["allowed"], True)
545542
assert_fee_amount(testres["fees"]["base"], testres["vsize"], Decimal(0.0001))
546543

test/functional/wallet_signer.py

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -148,8 +148,7 @@ def test_valid_signer(self):
148148
dest = self.nodes[0].getnewaddress()
149149
mock_psbt = mock_wallet.walletcreatefundedpsbt([], {dest:0.5}, 0, {}, True)['psbt']
150150
mock_psbt_signed = mock_wallet.walletprocesspsbt(psbt=mock_psbt, sign=True, sighashtype="ALL", bip32derivs=True)
151-
mock_psbt_final = mock_wallet.finalizepsbt(mock_psbt_signed["psbt"])
152-
mock_tx = mock_psbt_final["hex"]
151+
mock_tx = mock_psbt_signed["hex"]
153152
assert mock_wallet.testmempoolaccept([mock_tx])[0]["allowed"]
154153

155154
# # Create a new wallet and populate with specific public keys, in order

0 commit comments

Comments
 (0)