Skip to content

Commit 8aa9bad

Browse files
committed
Merge #13968: [wallet] couple of walletcreatefundedpsbt fixes
faaac5c RPCTypeCheck bip32derivs arg in walletcreatefunded (Gregory Sanders) 1f0c428 QA: add basic walletcreatefunded optional arg test (Gregory Sanders) 1f18d7b walletcreatefundedpsbt: remove duplicate replaceable arg (Gregory Sanders) 2252ec5 Allow ConstructTransaction to not throw error with 0-input txn (Gregory Sanders) Pull request description: 1) Previously an empty input argument transaction that is marked for replaceability fails to pass the `SignalsOptInRBF` check right before funding it. Explicitly check for that condition before throwing an error. 2) The rpc call had two separate `replaceable` arguments, each of which being used in mutually exclusive places. I preserved the `options` version to retain compatability with `fundtransaction`. Tree-SHA512: 26eb0c9e2d38ea51d11f741d61100223253271a084adadeb7e78c6d4e9004636f089e4273c5bf64a41bd7e9ff795317acf30531cb36aeb0d8db9304b3c8270c3
2 parents 4732fa1 + faaac5c commit 8aa9bad

File tree

4 files changed

+41
-14
lines changed

4 files changed

+41
-14
lines changed

src/rpc/client.cpp

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -113,9 +113,8 @@ static const CRPCConvertParam vRPCConvertParams[] =
113113
{ "walletcreatefundedpsbt", 0, "inputs" },
114114
{ "walletcreatefundedpsbt", 1, "outputs" },
115115
{ "walletcreatefundedpsbt", 2, "locktime" },
116-
{ "walletcreatefundedpsbt", 3, "replaceable" },
117-
{ "walletcreatefundedpsbt", 4, "options" },
118-
{ "walletcreatefundedpsbt", 5, "bip32derivs" },
116+
{ "walletcreatefundedpsbt", 3, "options" },
117+
{ "walletcreatefundedpsbt", 4, "bip32derivs" },
119118
{ "walletprocesspsbt", 1, "sign" },
120119
{ "walletprocesspsbt", 3, "bip32derivs" },
121120
{ "createpsbt", 0, "inputs" },

src/rpc/rawtransaction.cpp

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -436,7 +436,7 @@ CMutableTransaction ConstructTransaction(const UniValue& inputs_in, const UniVal
436436
}
437437
}
438438

439-
if (!rbf.isNull() && rbfOptIn != SignalsOptInRBF(rawTx)) {
439+
if (!rbf.isNull() && rawTx.vin.size() > 0 && rbfOptIn != SignalsOptInRBF(rawTx)) {
440440
throw JSONRPCError(RPC_INVALID_PARAMETER, "Invalid parameter combination: Sequence number(s) contradict replaceable option");
441441
}
442442

src/wallet/rpcwallet.cpp

Lines changed: 9 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -4643,7 +4643,7 @@ UniValue walletcreatefundedpsbt(const JSONRPCRequest& request)
46434643
return NullUniValue;
46444644
}
46454645

4646-
if (request.fHelp || request.params.size() < 2 || request.params.size() > 6)
4646+
if (request.fHelp || request.params.size() < 2 || request.params.size() > 5)
46474647
throw std::runtime_error(
46484648
"walletcreatefundedpsbt [{\"txid\":\"id\",\"vout\":n},...] [{\"address\":amount},{\"data\":\"hex\"},...] ( locktime ) ( replaceable ) ( options bip32derivs )\n"
46494649
"\nCreates and funds a transaction in the Partially Signed Transaction format. Inputs will be added if supplied inputs are not enough\n"
@@ -4670,9 +4670,8 @@ UniValue walletcreatefundedpsbt(const JSONRPCRequest& request)
46704670
" accepted as second parameter.\n"
46714671
" ]\n"
46724672
"3. locktime (numeric, optional, default=0) Raw locktime. Non-0 value also locktime-activates inputs\n"
4673-
"4. replaceable (boolean, optional, default=false) Marks this transaction as BIP125 replaceable.\n"
46744673
" Allows this transaction to be replaced by a transaction with higher fees. If provided, it is an error if explicit sequence numbers are incompatible.\n"
4675-
"5. options (object, optional)\n"
4674+
"4. options (object, optional)\n"
46764675
" {\n"
46774676
" \"changeAddress\" (string, optional, default pool address) The bitcoin address to receive the change\n"
46784677
" \"changePosition\" (numeric, optional, default random) The index of the change output\n"
@@ -4694,7 +4693,7 @@ UniValue walletcreatefundedpsbt(const JSONRPCRequest& request)
46944693
" \"ECONOMICAL\"\n"
46954694
" \"CONSERVATIVE\"\n"
46964695
" }\n"
4697-
"6. bip32derivs (boolean, optiona, default=false) If true, includes the BIP 32 derivation paths for public keys if we know them\n"
4696+
"5. bip32derivs (boolean, optiona, default=false) If true, includes the BIP 32 derivation paths for public keys if we know them\n"
46984697
"\nResult:\n"
46994698
"{\n"
47004699
" \"psbt\": \"value\", (string) The resulting raw transaction (base64-encoded string)\n"
@@ -4710,15 +4709,15 @@ UniValue walletcreatefundedpsbt(const JSONRPCRequest& request)
47104709
UniValue::VARR,
47114710
UniValueType(), // ARR or OBJ, checked later
47124711
UniValue::VNUM,
4713-
UniValue::VBOOL,
4714-
UniValue::VOBJ
4712+
UniValue::VOBJ,
4713+
UniValue::VBOOL
47154714
}, true
47164715
);
47174716

47184717
CAmount fee;
47194718
int change_position;
4720-
CMutableTransaction rawTx = ConstructTransaction(request.params[0], request.params[1], request.params[2], request.params[3]);
4721-
FundTransaction(pwallet, rawTx, fee, change_position, request.params[4]);
4719+
CMutableTransaction rawTx = ConstructTransaction(request.params[0], request.params[1], request.params[2], request.params[3]["replaceable"]);
4720+
FundTransaction(pwallet, rawTx, fee, change_position, request.params[3]);
47224721

47234722
// Make a blank psbt
47244723
PartiallySignedTransaction psbtx;
@@ -4735,7 +4734,7 @@ UniValue walletcreatefundedpsbt(const JSONRPCRequest& request)
47354734
const CTransaction txConst(*psbtx.tx);
47364735

47374736
// Fill transaction with out data but don't sign
4738-
bool bip32derivs = request.params[5].isNull() ? false : request.params[5].get_bool();
4737+
bool bip32derivs = request.params[4].isNull() ? false : request.params[5].get_bool();
47394738
FillPSBT(pwallet, psbtx, &txConst, 1, false, bip32derivs);
47404739

47414740
// Serialize the PSBT
@@ -4765,7 +4764,7 @@ static const CRPCCommand commands[] =
47654764
// --------------------- ------------------------ ----------------------- ----------
47664765
{ "rawtransactions", "fundrawtransaction", &fundrawtransaction, {"hexstring","options","iswitness"} },
47674766
{ "wallet", "walletprocesspsbt", &walletprocesspsbt, {"psbt","sign","sighashtype","bip32derivs"} },
4768-
{ "wallet", "walletcreatefundedpsbt", &walletcreatefundedpsbt, {"inputs","outputs","locktime","replaceable","options","bip32derivs"} },
4767+
{ "wallet", "walletcreatefundedpsbt", &walletcreatefundedpsbt, {"inputs","outputs","locktime","options","bip32derivs"} },
47694768
{ "hidden", "resendwallettransactions", &resendwallettransactions, {} },
47704769
{ "wallet", "abandontransaction", &abandontransaction, {"txid"} },
47714770
{ "wallet", "abortrescan", &abortrescan, {} },

test/functional/rpc_psbt.py

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,8 @@
1111
import json
1212
import os
1313

14+
MAX_BIP125_RBF_SEQUENCE = 0xfffffffd
15+
1416
# Create one-input, one-output, no-fee transaction:
1517
class PSBTTest(BitcoinTestFramework):
1618

@@ -135,6 +137,33 @@ def run_test(self):
135137
self.nodes[0].generate(6)
136138
self.sync_all()
137139

140+
# Test additional args in walletcreatepsbt
141+
# Make sure both pre-included and funded inputs
142+
# have the correct sequence numbers based on
143+
# replaceable arg
144+
block_height = self.nodes[0].getblockcount()
145+
unspent = self.nodes[0].listunspent()[0]
146+
psbtx_info = self.nodes[0].walletcreatefundedpsbt([{"txid":unspent["txid"], "vout":unspent["vout"]}], [{self.nodes[2].getnewaddress():unspent["amount"]+1}], block_height+2, {"replaceable":True})
147+
decoded_psbt = self.nodes[0].decodepsbt(psbtx_info["psbt"])
148+
for tx_in in decoded_psbt["tx"]["vin"]:
149+
assert_equal(tx_in["sequence"], MAX_BIP125_RBF_SEQUENCE)
150+
assert_equal(decoded_psbt["tx"]["locktime"], block_height+2)
151+
152+
# Same construction with only locktime set
153+
psbtx_info = self.nodes[0].walletcreatefundedpsbt([{"txid":unspent["txid"], "vout":unspent["vout"]}], [{self.nodes[2].getnewaddress():unspent["amount"]+1}], block_height)
154+
decoded_psbt = self.nodes[0].decodepsbt(psbtx_info["psbt"])
155+
for tx_in in decoded_psbt["tx"]["vin"]:
156+
assert tx_in["sequence"] > MAX_BIP125_RBF_SEQUENCE
157+
assert_equal(decoded_psbt["tx"]["locktime"], block_height)
158+
159+
# Same construction without optional arguments
160+
psbtx_info = self.nodes[0].walletcreatefundedpsbt([{"txid":unspent["txid"], "vout":unspent["vout"]}], [{self.nodes[2].getnewaddress():unspent["amount"]+1}])
161+
decoded_psbt = self.nodes[0].decodepsbt(psbtx_info["psbt"])
162+
for tx_in in decoded_psbt["tx"]["vin"]:
163+
assert tx_in["sequence"] > MAX_BIP125_RBF_SEQUENCE
164+
assert_equal(decoded_psbt["tx"]["locktime"], 0)
165+
166+
138167
# BIP 174 Test Vectors
139168

140169
# Check that unknown values are just passed through

0 commit comments

Comments
 (0)