576580fe8a
[test] walletcreatefundedpsbt: check RBF is disabled when -walletrbf=0 (Sjors Provoost)0942a60c06
[doc] rpc: remove "fallback to" from RBF default help (Sjors Provoost)ee950ec465
[rpc] walletcreatefundedpsbt: use wallet default RBF (Sjors Provoost) Pull request description: Backport of #15911 for 0.18 branch. The original PR changed `rawtransaction_util.cpp`, whereas the backport changes `rawtransaction.cpp` to avoid having to also backport #15638. ACKs for top commit: meshcollider: re-utACK576580fe8a
MarcoFalke: ACK576580fe8a
Tree-SHA512: 51c40fc4db4f8739e93e931aeac572779d5ade0bfeb3bcb0082301cf73b4f48e31d4ccbe40421dc65d0ec091d6393685c0c9b8f76584c59249f13642e80a6da5
This commit is contained in:
commit
00ffe5aca1
|
@ -352,7 +352,7 @@ static UniValue verifytxoutproof(const JSONRPCRequest& request)
|
||||||
return res;
|
return res;
|
||||||
}
|
}
|
||||||
|
|
||||||
CMutableTransaction ConstructTransaction(const UniValue& inputs_in, const UniValue& outputs_in, const UniValue& locktime, const UniValue& rbf)
|
CMutableTransaction ConstructTransaction(const UniValue& inputs_in, const UniValue& outputs_in, const UniValue& locktime, bool rbf)
|
||||||
{
|
{
|
||||||
if (inputs_in.isNull() || outputs_in.isNull())
|
if (inputs_in.isNull() || outputs_in.isNull())
|
||||||
throw JSONRPCError(RPC_INVALID_PARAMETER, "Invalid parameter, arguments 1 and 2 must be non-null");
|
throw JSONRPCError(RPC_INVALID_PARAMETER, "Invalid parameter, arguments 1 and 2 must be non-null");
|
||||||
|
@ -370,8 +370,6 @@ CMutableTransaction ConstructTransaction(const UniValue& inputs_in, const UniVal
|
||||||
rawTx.nLockTime = nLockTime;
|
rawTx.nLockTime = nLockTime;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool rbfOptIn = rbf.isTrue();
|
|
||||||
|
|
||||||
for (unsigned int idx = 0; idx < inputs.size(); idx++) {
|
for (unsigned int idx = 0; idx < inputs.size(); idx++) {
|
||||||
const UniValue& input = inputs[idx];
|
const UniValue& input = inputs[idx];
|
||||||
const UniValue& o = input.get_obj();
|
const UniValue& o = input.get_obj();
|
||||||
|
@ -386,7 +384,7 @@ CMutableTransaction ConstructTransaction(const UniValue& inputs_in, const UniVal
|
||||||
throw JSONRPCError(RPC_INVALID_PARAMETER, "Invalid parameter, vout must be positive");
|
throw JSONRPCError(RPC_INVALID_PARAMETER, "Invalid parameter, vout must be positive");
|
||||||
|
|
||||||
uint32_t nSequence;
|
uint32_t nSequence;
|
||||||
if (rbfOptIn) {
|
if (rbf) {
|
||||||
nSequence = MAX_BIP125_RBF_SEQUENCE; /* CTxIn::SEQUENCE_FINAL - 2 */
|
nSequence = MAX_BIP125_RBF_SEQUENCE; /* CTxIn::SEQUENCE_FINAL - 2 */
|
||||||
} else if (rawTx.nLockTime) {
|
} else if (rawTx.nLockTime) {
|
||||||
nSequence = CTxIn::SEQUENCE_FINAL - 1;
|
nSequence = CTxIn::SEQUENCE_FINAL - 1;
|
||||||
|
@ -458,7 +456,7 @@ CMutableTransaction ConstructTransaction(const UniValue& inputs_in, const UniVal
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!rbf.isNull() && rawTx.vin.size() > 0 && rbfOptIn != SignalsOptInRBF(CTransaction(rawTx))) {
|
if (rbf && rawTx.vin.size() > 0 && !SignalsOptInRBF(CTransaction(rawTx))) {
|
||||||
throw JSONRPCError(RPC_INVALID_PARAMETER, "Invalid parameter combination: Sequence number(s) contradict replaceable option");
|
throw JSONRPCError(RPC_INVALID_PARAMETER, "Invalid parameter combination: Sequence number(s) contradict replaceable option");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -528,7 +526,11 @@ static UniValue createrawtransaction(const JSONRPCRequest& request)
|
||||||
}, true
|
}, true
|
||||||
);
|
);
|
||||||
|
|
||||||
CMutableTransaction rawTx = ConstructTransaction(request.params[0], request.params[1], request.params[2], request.params[3]);
|
bool rbf = false;
|
||||||
|
if (!request.params[3].isNull()) {
|
||||||
|
rbf = request.params[3].isTrue();
|
||||||
|
}
|
||||||
|
CMutableTransaction rawTx = ConstructTransaction(request.params[0], request.params[1], request.params[2], rbf);
|
||||||
|
|
||||||
return EncodeHexTx(CTransaction(rawTx));
|
return EncodeHexTx(CTransaction(rawTx));
|
||||||
}
|
}
|
||||||
|
@ -1632,7 +1634,11 @@ UniValue createpsbt(const JSONRPCRequest& request)
|
||||||
}, true
|
}, true
|
||||||
);
|
);
|
||||||
|
|
||||||
CMutableTransaction rawTx = ConstructTransaction(request.params[0], request.params[1], request.params[2], request.params[3]);
|
bool rbf = false;
|
||||||
|
if (!request.params[3].isNull()) {
|
||||||
|
rbf = request.params[3].isTrue();
|
||||||
|
}
|
||||||
|
CMutableTransaction rawTx = ConstructTransaction(request.params[0], request.params[1], request.params[2], rbf);
|
||||||
|
|
||||||
// Make a blank psbt
|
// Make a blank psbt
|
||||||
PartiallySignedTransaction psbtx;
|
PartiallySignedTransaction psbtx;
|
||||||
|
|
|
@ -17,6 +17,6 @@ class Chain;
|
||||||
UniValue SignTransaction(interfaces::Chain& chain, CMutableTransaction& mtx, const UniValue& prevTxs, CBasicKeyStore *keystore, bool tempKeystore, const UniValue& hashType);
|
UniValue SignTransaction(interfaces::Chain& chain, CMutableTransaction& mtx, const UniValue& prevTxs, CBasicKeyStore *keystore, bool tempKeystore, const UniValue& hashType);
|
||||||
|
|
||||||
/** Create a transaction from univalue parameters */
|
/** Create a transaction from univalue parameters */
|
||||||
CMutableTransaction ConstructTransaction(const UniValue& inputs_in, const UniValue& outputs_in, const UniValue& locktime, const UniValue& rbf);
|
CMutableTransaction ConstructTransaction(const UniValue& inputs_in, const UniValue& outputs_in, const UniValue& locktime, bool rbf);
|
||||||
|
|
||||||
#endif // BITCOIN_RPC_RAWTRANSACTION_H
|
#endif // BITCOIN_RPC_RAWTRANSACTION_H
|
||||||
|
|
|
@ -372,8 +372,8 @@ static UniValue sendtoaddress(const JSONRPCRequest& request)
|
||||||
" transaction, just kept in your wallet."},
|
" transaction, just kept in your wallet."},
|
||||||
{"subtractfeefromamount", RPCArg::Type::BOOL, /* default */ "false", "The fee will be deducted from the amount being sent.\n"
|
{"subtractfeefromamount", RPCArg::Type::BOOL, /* default */ "false", "The fee will be deducted from the amount being sent.\n"
|
||||||
" The recipient will receive less bitcoins than you enter in the amount field."},
|
" The recipient will receive less bitcoins than you enter in the amount field."},
|
||||||
{"replaceable", RPCArg::Type::BOOL, /* default */ "fallback to wallet's default", "Allow this transaction to be replaced by a transaction with higher fees via BIP 125"},
|
{"replaceable", RPCArg::Type::BOOL, /* default */ "wallet default", "Allow this transaction to be replaced by a transaction with higher fees via BIP 125"},
|
||||||
{"conf_target", RPCArg::Type::NUM, /* default */ "fallback to wallet's default", "Confirmation target (in blocks)"},
|
{"conf_target", RPCArg::Type::NUM, /* default */ "wallet default", "Confirmation target (in blocks)"},
|
||||||
{"estimate_mode", RPCArg::Type::STR, /* default */ "UNSET", "The fee estimate mode, must be one of:\n"
|
{"estimate_mode", RPCArg::Type::STR, /* default */ "UNSET", "The fee estimate mode, must be one of:\n"
|
||||||
" \"UNSET\"\n"
|
" \"UNSET\"\n"
|
||||||
" \"ECONOMICAL\"\n"
|
" \"ECONOMICAL\"\n"
|
||||||
|
@ -831,8 +831,8 @@ static UniValue sendmany(const JSONRPCRequest& request)
|
||||||
{"address", RPCArg::Type::STR, RPCArg::Optional::OMITTED, "Subtract fee from this address"},
|
{"address", RPCArg::Type::STR, RPCArg::Optional::OMITTED, "Subtract fee from this address"},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{"replaceable", RPCArg::Type::BOOL, /* default */ "fallback to wallet's default", "Allow this transaction to be replaced by a transaction with higher fees via BIP 125"},
|
{"replaceable", RPCArg::Type::BOOL, /* default */ "wallet default", "Allow this transaction to be replaced by a transaction with higher fees via BIP 125"},
|
||||||
{"conf_target", RPCArg::Type::NUM, /* default */ "fallback to wallet's default", "Confirmation target (in blocks)"},
|
{"conf_target", RPCArg::Type::NUM, /* default */ "wallet default", "Confirmation target (in blocks)"},
|
||||||
{"estimate_mode", RPCArg::Type::STR, /* default */ "UNSET", "The fee estimate mode, must be one of:\n"
|
{"estimate_mode", RPCArg::Type::STR, /* default */ "UNSET", "The fee estimate mode, must be one of:\n"
|
||||||
" \"UNSET\"\n"
|
" \"UNSET\"\n"
|
||||||
" \"ECONOMICAL\"\n"
|
" \"ECONOMICAL\"\n"
|
||||||
|
@ -3070,9 +3070,9 @@ static UniValue fundrawtransaction(const JSONRPCRequest& request)
|
||||||
{"vout_index", RPCArg::Type::NUM, RPCArg::Optional::OMITTED, "The zero-based output index, before a change output is added."},
|
{"vout_index", RPCArg::Type::NUM, RPCArg::Optional::OMITTED, "The zero-based output index, before a change output is added."},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{"replaceable", RPCArg::Type::BOOL, /* default */ "fallback to wallet's default", "Marks this transaction as BIP125 replaceable.\n"
|
{"replaceable", RPCArg::Type::BOOL, /* default */ "wallet default", "Marks this transaction as BIP125 replaceable.\n"
|
||||||
" Allows this transaction to be replaced by a transaction with higher fees"},
|
" Allows this transaction to be replaced by a transaction with higher fees"},
|
||||||
{"conf_target", RPCArg::Type::NUM, /* default */ "fallback to wallet's default", "Confirmation target (in blocks)"},
|
{"conf_target", RPCArg::Type::NUM, /* default */ "wallet default", "Confirmation target (in blocks)"},
|
||||||
{"estimate_mode", RPCArg::Type::STR, /* default */ "UNSET", "The fee estimate mode, must be one of:\n"
|
{"estimate_mode", RPCArg::Type::STR, /* default */ "UNSET", "The fee estimate mode, must be one of:\n"
|
||||||
" \"UNSET\"\n"
|
" \"UNSET\"\n"
|
||||||
" \"ECONOMICAL\"\n"
|
" \"ECONOMICAL\"\n"
|
||||||
|
@ -3236,7 +3236,7 @@ static UniValue bumpfee(const JSONRPCRequest& request)
|
||||||
{"txid", RPCArg::Type::STR_HEX, RPCArg::Optional::NO, "The txid to be bumped"},
|
{"txid", RPCArg::Type::STR_HEX, RPCArg::Optional::NO, "The txid to be bumped"},
|
||||||
{"options", RPCArg::Type::OBJ, RPCArg::Optional::OMITTED_NAMED_ARG, "",
|
{"options", RPCArg::Type::OBJ, RPCArg::Optional::OMITTED_NAMED_ARG, "",
|
||||||
{
|
{
|
||||||
{"confTarget", RPCArg::Type::NUM, /* default */ "fallback to wallet's default", "Confirmation target (in blocks)"},
|
{"confTarget", RPCArg::Type::NUM, /* default */ "wallet default", "Confirmation target (in blocks)"},
|
||||||
{"totalFee", RPCArg::Type::NUM, /* default */ "fallback to 'confTarget'", "Total fee (NOT feerate) to pay, in satoshis.\n"
|
{"totalFee", RPCArg::Type::NUM, /* default */ "fallback to 'confTarget'", "Total fee (NOT feerate) to pay, in satoshis.\n"
|
||||||
" In rare cases, the actual fee paid might be slightly higher than the specified\n"
|
" In rare cases, the actual fee paid might be slightly higher than the specified\n"
|
||||||
" totalFee if the tx change output has to be removed because it is too close to\n"
|
" totalFee if the tx change output has to be removed because it is too close to\n"
|
||||||
|
@ -4079,7 +4079,7 @@ UniValue walletcreatefundedpsbt(const JSONRPCRequest& request)
|
||||||
{"vout_index", RPCArg::Type::NUM, RPCArg::Optional::OMITTED, "The zero-based output index, before a change output is added."},
|
{"vout_index", RPCArg::Type::NUM, RPCArg::Optional::OMITTED, "The zero-based output index, before a change output is added."},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{"replaceable", RPCArg::Type::BOOL, /* default */ "false", "Marks this transaction as BIP125 replaceable.\n"
|
{"replaceable", RPCArg::Type::BOOL, /* default */ "wallet default", "Marks this transaction as BIP125 replaceable.\n"
|
||||||
" Allows this transaction to be replaced by a transaction with higher fees"},
|
" Allows this transaction to be replaced by a transaction with higher fees"},
|
||||||
{"conf_target", RPCArg::Type::NUM, /* default */ "Fallback to wallet's confirmation target", "Confirmation target (in blocks)"},
|
{"conf_target", RPCArg::Type::NUM, /* default */ "Fallback to wallet's confirmation target", "Confirmation target (in blocks)"},
|
||||||
{"estimate_mode", RPCArg::Type::STR, /* default */ "UNSET", "The fee estimate mode, must be one of:\n"
|
{"estimate_mode", RPCArg::Type::STR, /* default */ "UNSET", "The fee estimate mode, must be one of:\n"
|
||||||
|
@ -4114,7 +4114,13 @@ UniValue walletcreatefundedpsbt(const JSONRPCRequest& request)
|
||||||
|
|
||||||
CAmount fee;
|
CAmount fee;
|
||||||
int change_position;
|
int change_position;
|
||||||
CMutableTransaction rawTx = ConstructTransaction(request.params[0], request.params[1], request.params[2], request.params[3]["replaceable"]);
|
bool rbf = pwallet->m_signal_rbf;
|
||||||
|
const UniValue &replaceable_arg = request.params[3]["replaceable"];
|
||||||
|
if (!replaceable_arg.isNull()) {
|
||||||
|
RPCTypeCheckArgument(replaceable_arg, UniValue::VBOOL);
|
||||||
|
rbf = replaceable_arg.isTrue();
|
||||||
|
}
|
||||||
|
CMutableTransaction rawTx = ConstructTransaction(request.params[0], request.params[1], request.params[2], rbf);
|
||||||
FundTransaction(pwallet, rawTx, fee, change_position, request.params[3]);
|
FundTransaction(pwallet, rawTx, fee, change_position, request.params[3]);
|
||||||
|
|
||||||
// Make a blank psbt
|
// Make a blank psbt
|
||||||
|
|
|
@ -7,7 +7,7 @@
|
||||||
|
|
||||||
from decimal import Decimal
|
from decimal import Decimal
|
||||||
from test_framework.test_framework import BitcoinTestFramework
|
from test_framework.test_framework import BitcoinTestFramework
|
||||||
from test_framework.util import assert_equal, assert_raises_rpc_error, connect_nodes_bi, disconnect_nodes, find_output, sync_blocks
|
from test_framework.util import assert_equal, assert_greater_than, assert_raises_rpc_error, connect_nodes_bi, disconnect_nodes, find_output, sync_blocks
|
||||||
|
|
||||||
import json
|
import json
|
||||||
import os
|
import os
|
||||||
|
@ -20,6 +20,11 @@ class PSBTTest(BitcoinTestFramework):
|
||||||
def set_test_params(self):
|
def set_test_params(self):
|
||||||
self.setup_clean_chain = False
|
self.setup_clean_chain = False
|
||||||
self.num_nodes = 3
|
self.num_nodes = 3
|
||||||
|
self.extra_args = [
|
||||||
|
["-walletrbf=1"],
|
||||||
|
["-walletrbf=0"],
|
||||||
|
[]
|
||||||
|
]
|
||||||
|
|
||||||
def skip_test_if_missing_module(self):
|
def skip_test_if_missing_module(self):
|
||||||
self.skip_if_no_wallet()
|
self.skip_if_no_wallet()
|
||||||
|
@ -191,18 +196,18 @@ class PSBTTest(BitcoinTestFramework):
|
||||||
# replaceable arg
|
# replaceable arg
|
||||||
block_height = self.nodes[0].getblockcount()
|
block_height = self.nodes[0].getblockcount()
|
||||||
unspent = self.nodes[0].listunspent()[0]
|
unspent = self.nodes[0].listunspent()[0]
|
||||||
psbtx_info = self.nodes[0].walletcreatefundedpsbt([{"txid":unspent["txid"], "vout":unspent["vout"]}], [{self.nodes[2].getnewaddress():unspent["amount"]+1}], block_height+2, {"replaceable":True}, False)
|
psbtx_info = self.nodes[0].walletcreatefundedpsbt([{"txid":unspent["txid"], "vout":unspent["vout"]}], [{self.nodes[2].getnewaddress():unspent["amount"]+1}], block_height+2, {"replaceable":False}, False)
|
||||||
decoded_psbt = self.nodes[0].decodepsbt(psbtx_info["psbt"])
|
decoded_psbt = self.nodes[0].decodepsbt(psbtx_info["psbt"])
|
||||||
for tx_in, psbt_in in zip(decoded_psbt["tx"]["vin"], decoded_psbt["inputs"]):
|
for tx_in, psbt_in in zip(decoded_psbt["tx"]["vin"], decoded_psbt["inputs"]):
|
||||||
assert_equal(tx_in["sequence"], MAX_BIP125_RBF_SEQUENCE)
|
assert_greater_than(tx_in["sequence"], MAX_BIP125_RBF_SEQUENCE)
|
||||||
assert "bip32_derivs" not in psbt_in
|
assert "bip32_derivs" not in psbt_in
|
||||||
assert_equal(decoded_psbt["tx"]["locktime"], block_height+2)
|
assert_equal(decoded_psbt["tx"]["locktime"], block_height+2)
|
||||||
|
|
||||||
# Same construction with only locktime set
|
# Same construction with only locktime set and RBF explicitly enabled
|
||||||
psbtx_info = self.nodes[0].walletcreatefundedpsbt([{"txid":unspent["txid"], "vout":unspent["vout"]}], [{self.nodes[2].getnewaddress():unspent["amount"]+1}], block_height, {}, True)
|
psbtx_info = self.nodes[0].walletcreatefundedpsbt([{"txid":unspent["txid"], "vout":unspent["vout"]}], [{self.nodes[2].getnewaddress():unspent["amount"]+1}], block_height, {"replaceable": True}, True)
|
||||||
decoded_psbt = self.nodes[0].decodepsbt(psbtx_info["psbt"])
|
decoded_psbt = self.nodes[0].decodepsbt(psbtx_info["psbt"])
|
||||||
for tx_in, psbt_in in zip(decoded_psbt["tx"]["vin"], decoded_psbt["inputs"]):
|
for tx_in, psbt_in in zip(decoded_psbt["tx"]["vin"], decoded_psbt["inputs"]):
|
||||||
assert tx_in["sequence"] > MAX_BIP125_RBF_SEQUENCE
|
assert_equal(tx_in["sequence"], MAX_BIP125_RBF_SEQUENCE)
|
||||||
assert "bip32_derivs" in psbt_in
|
assert "bip32_derivs" in psbt_in
|
||||||
assert_equal(decoded_psbt["tx"]["locktime"], block_height)
|
assert_equal(decoded_psbt["tx"]["locktime"], block_height)
|
||||||
|
|
||||||
|
@ -210,9 +215,16 @@ class PSBTTest(BitcoinTestFramework):
|
||||||
psbtx_info = self.nodes[0].walletcreatefundedpsbt([{"txid":unspent["txid"], "vout":unspent["vout"]}], [{self.nodes[2].getnewaddress():unspent["amount"]+1}])
|
psbtx_info = self.nodes[0].walletcreatefundedpsbt([{"txid":unspent["txid"], "vout":unspent["vout"]}], [{self.nodes[2].getnewaddress():unspent["amount"]+1}])
|
||||||
decoded_psbt = self.nodes[0].decodepsbt(psbtx_info["psbt"])
|
decoded_psbt = self.nodes[0].decodepsbt(psbtx_info["psbt"])
|
||||||
for tx_in in decoded_psbt["tx"]["vin"]:
|
for tx_in in decoded_psbt["tx"]["vin"]:
|
||||||
assert tx_in["sequence"] > MAX_BIP125_RBF_SEQUENCE
|
assert_equal(tx_in["sequence"], MAX_BIP125_RBF_SEQUENCE)
|
||||||
assert_equal(decoded_psbt["tx"]["locktime"], 0)
|
assert_equal(decoded_psbt["tx"]["locktime"], 0)
|
||||||
|
|
||||||
|
# Same construction without optional arguments, for a node with -walletrbf=0
|
||||||
|
unspent1 = self.nodes[1].listunspent()[0]
|
||||||
|
psbtx_info = self.nodes[1].walletcreatefundedpsbt([{"txid":unspent1["txid"], "vout":unspent1["vout"]}], [{self.nodes[2].getnewaddress():unspent1["amount"]+1}], block_height)
|
||||||
|
decoded_psbt = self.nodes[1].decodepsbt(psbtx_info["psbt"])
|
||||||
|
for tx_in in decoded_psbt["tx"]["vin"]:
|
||||||
|
assert_greater_than(tx_in["sequence"], MAX_BIP125_RBF_SEQUENCE)
|
||||||
|
|
||||||
# Make sure change address wallet does not have P2SH innerscript access to results in success
|
# Make sure change address wallet does not have P2SH innerscript access to results in success
|
||||||
# when attempting BnB coin selection
|
# when attempting BnB coin selection
|
||||||
self.nodes[0].walletcreatefundedpsbt([], [{self.nodes[2].getnewaddress():unspent["amount"]+1}], block_height+2, {"changeAddress":self.nodes[1].getnewaddress()}, False)
|
self.nodes[0].walletcreatefundedpsbt([], [{self.nodes[2].getnewaddress():unspent["amount"]+1}], block_height+2, {"changeAddress":self.nodes[1].getnewaddress()}, False)
|
||||||
|
|
Loading…
Reference in a new issue