Merge #9672: Opt-into-RBF for RPC & bitcoin-tx
9a5a1d7
RPC/rawtransaction: createrawtransaction: Check opt_into_rbf when provided with either value (Luke Dashjr)23b0fe3
bitcoin-tx: rbfoptin: Avoid touching nSequence if the value is already opting in (Luke Dashjr)b005bf2
Introduce MAX_BIP125_RBF_SEQUENCE constant (Luke Dashjr)575cde4
[bitcoin-tx] add rbfoptin command (Jonas Schnelli)5d26244
[Tests] extend the replace-by-fee test to cover RPC rawtx features (Jonas Schnelli)36bcab2
RPC/Wallet: Add RBF support for fundrawtransaction (Luke Dashjr)891c5ee
Wallet: Refactor FundTransaction to accept parameters via CCoinControl (Luke Dashjr)578ec80
RPC: rawtransaction: Add RBF support for createrawtransaction (Luke Dashjr) Tree-SHA512: 446e37c617c188cc3b3fd1e2841c98eda6f4869e71cb3249c4a9e54002607d0f1e6bef92187f7894d4e0746ab449cfee89be9f6a1a8831e25c70cf912eac1570
This commit is contained in:
commit
46311e792f
9 changed files with 96 additions and 25 deletions
|
@ -198,6 +198,7 @@ libbitcoin_server_a_SOURCES = \
|
||||||
noui.cpp \
|
noui.cpp \
|
||||||
policy/fees.cpp \
|
policy/fees.cpp \
|
||||||
policy/policy.cpp \
|
policy/policy.cpp \
|
||||||
|
policy/rbf.cpp \
|
||||||
pow.cpp \
|
pow.cpp \
|
||||||
rest.cpp \
|
rest.cpp \
|
||||||
rpc/blockchain.cpp \
|
rpc/blockchain.cpp \
|
||||||
|
@ -240,7 +241,6 @@ libbitcoin_wallet_a_SOURCES = \
|
||||||
wallet/rpcwallet.cpp \
|
wallet/rpcwallet.cpp \
|
||||||
wallet/wallet.cpp \
|
wallet/wallet.cpp \
|
||||||
wallet/walletdb.cpp \
|
wallet/walletdb.cpp \
|
||||||
policy/rbf.cpp \
|
|
||||||
$(BITCOIN_CORE_H)
|
$(BITCOIN_CORE_H)
|
||||||
|
|
||||||
# crypto primitives library
|
# crypto primitives library
|
||||||
|
|
|
@ -13,6 +13,7 @@
|
||||||
#include "core_io.h"
|
#include "core_io.h"
|
||||||
#include "keystore.h"
|
#include "keystore.h"
|
||||||
#include "policy/policy.h"
|
#include "policy/policy.h"
|
||||||
|
#include "policy/rbf.h"
|
||||||
#include "primitives/transaction.h"
|
#include "primitives/transaction.h"
|
||||||
#include "script/script.h"
|
#include "script/script.h"
|
||||||
#include "script/sign.h"
|
#include "script/sign.h"
|
||||||
|
@ -77,6 +78,7 @@ static int AppInitRawTx(int argc, char* argv[])
|
||||||
strUsage += HelpMessageOpt("in=TXID:VOUT(:SEQUENCE_NUMBER)", _("Add input to TX"));
|
strUsage += HelpMessageOpt("in=TXID:VOUT(:SEQUENCE_NUMBER)", _("Add input to TX"));
|
||||||
strUsage += HelpMessageOpt("locktime=N", _("Set TX lock time to N"));
|
strUsage += HelpMessageOpt("locktime=N", _("Set TX lock time to N"));
|
||||||
strUsage += HelpMessageOpt("nversion=N", _("Set TX version to N"));
|
strUsage += HelpMessageOpt("nversion=N", _("Set TX version to N"));
|
||||||
|
strUsage += HelpMessageOpt("rbfoptin(=N)", _("Set RBF opt-in sequence number for input N (if not provided, opt-in all available inputs)"));
|
||||||
strUsage += HelpMessageOpt("outaddr=VALUE:ADDRESS", _("Add address-based output to TX"));
|
strUsage += HelpMessageOpt("outaddr=VALUE:ADDRESS", _("Add address-based output to TX"));
|
||||||
strUsage += HelpMessageOpt("outpubkey=VALUE:PUBKEY[:FLAGS]", _("Add pay-to-pubkey output to TX") + ". " +
|
strUsage += HelpMessageOpt("outpubkey=VALUE:PUBKEY[:FLAGS]", _("Add pay-to-pubkey output to TX") + ". " +
|
||||||
_("Optionally add the \"W\" flag to produce a pay-to-witness-pubkey-hash output") + ". " +
|
_("Optionally add the \"W\" flag to produce a pay-to-witness-pubkey-hash output") + ". " +
|
||||||
|
@ -202,6 +204,26 @@ static void MutateTxLocktime(CMutableTransaction& tx, const std::string& cmdVal)
|
||||||
tx.nLockTime = (unsigned int) newLocktime;
|
tx.nLockTime = (unsigned int) newLocktime;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static void MutateTxRBFOptIn(CMutableTransaction& tx, const std::string& strInIdx)
|
||||||
|
{
|
||||||
|
// parse requested index
|
||||||
|
int inIdx = atoi(strInIdx);
|
||||||
|
if (inIdx < 0 || inIdx >= (int)tx.vin.size()) {
|
||||||
|
throw std::runtime_error("Invalid TX input index '" + strInIdx + "'");
|
||||||
|
}
|
||||||
|
|
||||||
|
// set the nSequence to MAX_INT - 2 (= RBF opt in flag)
|
||||||
|
int cnt = 0;
|
||||||
|
for (CTxIn& txin : tx.vin) {
|
||||||
|
if (strInIdx == "" || cnt == inIdx) {
|
||||||
|
if (txin.nSequence > MAX_BIP125_RBF_SEQUENCE) {
|
||||||
|
txin.nSequence = MAX_BIP125_RBF_SEQUENCE;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
++cnt;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
static void MutateTxAddInput(CMutableTransaction& tx, const std::string& strInput)
|
static void MutateTxAddInput(CMutableTransaction& tx, const std::string& strInput)
|
||||||
{
|
{
|
||||||
std::vector<std::string> vStrInputParts;
|
std::vector<std::string> vStrInputParts;
|
||||||
|
@ -649,6 +671,9 @@ static void MutateTx(CMutableTransaction& tx, const std::string& command,
|
||||||
MutateTxVersion(tx, commandVal);
|
MutateTxVersion(tx, commandVal);
|
||||||
else if (command == "locktime")
|
else if (command == "locktime")
|
||||||
MutateTxLocktime(tx, commandVal);
|
MutateTxLocktime(tx, commandVal);
|
||||||
|
else if (command == "rbfoptin") {
|
||||||
|
MutateTxRBFOptIn(tx, commandVal);
|
||||||
|
}
|
||||||
|
|
||||||
else if (command == "delin")
|
else if (command == "delin")
|
||||||
MutateTxDelInput(tx, commandVal);
|
MutateTxDelInput(tx, commandVal);
|
||||||
|
|
|
@ -7,6 +7,8 @@
|
||||||
|
|
||||||
#include "txmempool.h"
|
#include "txmempool.h"
|
||||||
|
|
||||||
|
static const uint32_t MAX_BIP125_RBF_SEQUENCE = 0xfffffffd;
|
||||||
|
|
||||||
enum RBFTransactionState {
|
enum RBFTransactionState {
|
||||||
RBF_TRANSACTIONSTATE_UNKNOWN,
|
RBF_TRANSACTIONSTATE_UNKNOWN,
|
||||||
RBF_TRANSACTIONSTATE_REPLACEABLE_BIP125,
|
RBF_TRANSACTIONSTATE_REPLACEABLE_BIP125,
|
||||||
|
|
|
@ -86,6 +86,7 @@ static const CRPCConvertParam vRPCConvertParams[] =
|
||||||
{ "createrawtransaction", 0, "inputs" },
|
{ "createrawtransaction", 0, "inputs" },
|
||||||
{ "createrawtransaction", 1, "outputs" },
|
{ "createrawtransaction", 1, "outputs" },
|
||||||
{ "createrawtransaction", 2, "locktime" },
|
{ "createrawtransaction", 2, "locktime" },
|
||||||
|
{ "createrawtransaction", 3, "optintorbf" },
|
||||||
{ "signrawtransaction", 1, "prevtxs" },
|
{ "signrawtransaction", 1, "prevtxs" },
|
||||||
{ "signrawtransaction", 2, "privkeys" },
|
{ "signrawtransaction", 2, "privkeys" },
|
||||||
{ "sendrawtransaction", 1, "allowhighfees" },
|
{ "sendrawtransaction", 1, "allowhighfees" },
|
||||||
|
|
|
@ -14,6 +14,7 @@
|
||||||
#include "merkleblock.h"
|
#include "merkleblock.h"
|
||||||
#include "net.h"
|
#include "net.h"
|
||||||
#include "policy/policy.h"
|
#include "policy/policy.h"
|
||||||
|
#include "policy/rbf.h"
|
||||||
#include "primitives/transaction.h"
|
#include "primitives/transaction.h"
|
||||||
#include "rpc/server.h"
|
#include "rpc/server.h"
|
||||||
#include "script/script.h"
|
#include "script/script.h"
|
||||||
|
@ -289,9 +290,9 @@ UniValue verifytxoutproof(const JSONRPCRequest& request)
|
||||||
|
|
||||||
UniValue createrawtransaction(const JSONRPCRequest& request)
|
UniValue createrawtransaction(const JSONRPCRequest& request)
|
||||||
{
|
{
|
||||||
if (request.fHelp || request.params.size() < 2 || request.params.size() > 3)
|
if (request.fHelp || request.params.size() < 2 || request.params.size() > 4)
|
||||||
throw std::runtime_error(
|
throw std::runtime_error(
|
||||||
"createrawtransaction [{\"txid\":\"id\",\"vout\":n},...] {\"address\":amount,\"data\":\"hex\",...} ( locktime )\n"
|
"createrawtransaction [{\"txid\":\"id\",\"vout\":n},...] {\"address\":amount,\"data\":\"hex\",...} ( locktime ) ( optintorbf )\n"
|
||||||
"\nCreate a transaction spending the given inputs and creating new outputs.\n"
|
"\nCreate a transaction spending the given inputs and creating new outputs.\n"
|
||||||
"Outputs can be addresses or data.\n"
|
"Outputs can be addresses or data.\n"
|
||||||
"Returns hex-encoded raw transaction.\n"
|
"Returns hex-encoded raw transaction.\n"
|
||||||
|
@ -315,6 +316,7 @@ UniValue createrawtransaction(const JSONRPCRequest& request)
|
||||||
" ,...\n"
|
" ,...\n"
|
||||||
" }\n"
|
" }\n"
|
||||||
"3. locktime (numeric, optional, default=0) Raw locktime. Non-0 value also locktime-activates inputs\n"
|
"3. locktime (numeric, optional, default=0) Raw locktime. Non-0 value also locktime-activates inputs\n"
|
||||||
|
"4. optintorbf (boolean, optional, default=false) Allow this transaction to be replaced by a transaction with higher fees. If provided, it is an error if explicit sequence numbers are incompatible.\n"
|
||||||
"\nResult:\n"
|
"\nResult:\n"
|
||||||
"\"transaction\" (string) hex string of the transaction\n"
|
"\"transaction\" (string) hex string of the transaction\n"
|
||||||
|
|
||||||
|
@ -341,6 +343,8 @@ UniValue createrawtransaction(const JSONRPCRequest& request)
|
||||||
rawTx.nLockTime = nLockTime;
|
rawTx.nLockTime = nLockTime;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool rbfOptIn = request.params.size() > 3 ? request.params[3].isTrue() : false;
|
||||||
|
|
||||||
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();
|
||||||
|
@ -354,16 +358,24 @@ UniValue createrawtransaction(const JSONRPCRequest& request)
|
||||||
if (nOutput < 0)
|
if (nOutput < 0)
|
||||||
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 = (rawTx.nLockTime ? std::numeric_limits<uint32_t>::max() - 1 : std::numeric_limits<uint32_t>::max());
|
uint32_t nSequence;
|
||||||
|
if (rbfOptIn) {
|
||||||
|
nSequence = MAX_BIP125_RBF_SEQUENCE;
|
||||||
|
} else if (rawTx.nLockTime) {
|
||||||
|
nSequence = std::numeric_limits<uint32_t>::max() - 1;
|
||||||
|
} else {
|
||||||
|
nSequence = std::numeric_limits<uint32_t>::max();
|
||||||
|
}
|
||||||
|
|
||||||
// set the sequence number if passed in the parameters object
|
// set the sequence number if passed in the parameters object
|
||||||
const UniValue& sequenceObj = find_value(o, "sequence");
|
const UniValue& sequenceObj = find_value(o, "sequence");
|
||||||
if (sequenceObj.isNum()) {
|
if (sequenceObj.isNum()) {
|
||||||
int64_t seqNr64 = sequenceObj.get_int64();
|
int64_t seqNr64 = sequenceObj.get_int64();
|
||||||
if (seqNr64 < 0 || seqNr64 > std::numeric_limits<uint32_t>::max())
|
if (seqNr64 < 0 || seqNr64 > std::numeric_limits<uint32_t>::max()) {
|
||||||
throw JSONRPCError(RPC_INVALID_PARAMETER, "Invalid parameter, sequence number is out of range");
|
throw JSONRPCError(RPC_INVALID_PARAMETER, "Invalid parameter, sequence number is out of range");
|
||||||
else
|
} else {
|
||||||
nSequence = (uint32_t)seqNr64;
|
nSequence = (uint32_t)seqNr64;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
CTxIn in(COutPoint(txid, nOutput), CScript(), nSequence);
|
CTxIn in(COutPoint(txid, nOutput), CScript(), nSequence);
|
||||||
|
@ -397,6 +409,10 @@ UniValue createrawtransaction(const JSONRPCRequest& request)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (request.params.size() > 3 && rbfOptIn != SignalsOptInRBF(rawTx)) {
|
||||||
|
throw JSONRPCError(RPC_INVALID_PARAMETER, "Invalid parameter combination: Sequence number(s) contradict optintorbf option");
|
||||||
|
}
|
||||||
|
|
||||||
return EncodeHexTx(rawTx);
|
return EncodeHexTx(rawTx);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -21,6 +21,7 @@
|
||||||
#include "timedata.h"
|
#include "timedata.h"
|
||||||
#include "util.h"
|
#include "util.h"
|
||||||
#include "utilmoneystr.h"
|
#include "utilmoneystr.h"
|
||||||
|
#include "wallet/coincontrol.h"
|
||||||
#include "wallet/feebumper.h"
|
#include "wallet/feebumper.h"
|
||||||
#include "wallet/wallet.h"
|
#include "wallet/wallet.h"
|
||||||
#include "wallet/walletdb.h"
|
#include "wallet/walletdb.h"
|
||||||
|
@ -2628,7 +2629,7 @@ UniValue fundrawtransaction(const JSONRPCRequest& request)
|
||||||
return NullUniValue;
|
return NullUniValue;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (request.fHelp || request.params.size() < 1 || request.params.size() > 2)
|
if (request.fHelp || request.params.size() < 1 || request.params.size() > 3)
|
||||||
throw std::runtime_error(
|
throw std::runtime_error(
|
||||||
"fundrawtransaction \"hexstring\" ( options )\n"
|
"fundrawtransaction \"hexstring\" ( options )\n"
|
||||||
"\nAdd inputs to a transaction until it has enough in value to meet its out value.\n"
|
"\nAdd inputs to a transaction until it has enough in value to meet its out value.\n"
|
||||||
|
@ -2657,6 +2658,7 @@ UniValue fundrawtransaction(const JSONRPCRequest& request)
|
||||||
" Those recipients will receive less bitcoins than you enter in their corresponding amount field.\n"
|
" Those recipients will receive less bitcoins than you enter in their corresponding amount field.\n"
|
||||||
" If no outputs are specified here, the sender pays the fee.\n"
|
" If no outputs are specified here, the sender pays the fee.\n"
|
||||||
" [vout_index,...]\n"
|
" [vout_index,...]\n"
|
||||||
|
" \"optIntoRbf\" (boolean, optional) Allow this transaction to be replaced by a transaction with higher fees\n"
|
||||||
" }\n"
|
" }\n"
|
||||||
" for backward compatibility: passing in a true instead of an object will result in {\"includeWatching\":true}\n"
|
" for backward compatibility: passing in a true instead of an object will result in {\"includeWatching\":true}\n"
|
||||||
"\nResult:\n"
|
"\nResult:\n"
|
||||||
|
@ -2678,20 +2680,21 @@ UniValue fundrawtransaction(const JSONRPCRequest& request)
|
||||||
|
|
||||||
RPCTypeCheck(request.params, boost::assign::list_of(UniValue::VSTR));
|
RPCTypeCheck(request.params, boost::assign::list_of(UniValue::VSTR));
|
||||||
|
|
||||||
CTxDestination changeAddress = CNoDestination();
|
CCoinControl coinControl;
|
||||||
|
coinControl.destChange = CNoDestination();
|
||||||
int changePosition = -1;
|
int changePosition = -1;
|
||||||
bool includeWatching = false;
|
coinControl.fAllowWatchOnly = false; // include watching
|
||||||
bool lockUnspents = false;
|
bool lockUnspents = false;
|
||||||
bool reserveChangeKey = true;
|
bool reserveChangeKey = true;
|
||||||
CFeeRate feeRate = CFeeRate(0);
|
coinControl.nFeeRate = CFeeRate(0);
|
||||||
bool overrideEstimatedFeerate = false;
|
coinControl.fOverrideFeeRate = false;
|
||||||
UniValue subtractFeeFromOutputs;
|
UniValue subtractFeeFromOutputs;
|
||||||
std::set<int> setSubtractFeeFromOutputs;
|
std::set<int> setSubtractFeeFromOutputs;
|
||||||
|
|
||||||
if (request.params.size() > 1) {
|
if (request.params.size() > 1) {
|
||||||
if (request.params[1].type() == UniValue::VBOOL) {
|
if (request.params[1].type() == UniValue::VBOOL) {
|
||||||
// backward compatibility bool only fallback
|
// backward compatibility bool only fallback
|
||||||
includeWatching = request.params[1].get_bool();
|
coinControl.fAllowWatchOnly = request.params[1].get_bool();
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
RPCTypeCheck(request.params, boost::assign::list_of(UniValue::VSTR)(UniValue::VOBJ));
|
RPCTypeCheck(request.params, boost::assign::list_of(UniValue::VSTR)(UniValue::VOBJ));
|
||||||
|
@ -2707,6 +2710,7 @@ UniValue fundrawtransaction(const JSONRPCRequest& request)
|
||||||
{"reserveChangeKey", UniValueType(UniValue::VBOOL)},
|
{"reserveChangeKey", UniValueType(UniValue::VBOOL)},
|
||||||
{"feeRate", UniValueType()}, // will be checked below
|
{"feeRate", UniValueType()}, // will be checked below
|
||||||
{"subtractFeeFromOutputs", UniValueType(UniValue::VARR)},
|
{"subtractFeeFromOutputs", UniValueType(UniValue::VARR)},
|
||||||
|
{"optIntoRbf", UniValueType(UniValue::VBOOL)},
|
||||||
},
|
},
|
||||||
true, true);
|
true, true);
|
||||||
|
|
||||||
|
@ -2716,14 +2720,14 @@ UniValue fundrawtransaction(const JSONRPCRequest& request)
|
||||||
if (!address.IsValid())
|
if (!address.IsValid())
|
||||||
throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "changeAddress must be a valid bitcoin address");
|
throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "changeAddress must be a valid bitcoin address");
|
||||||
|
|
||||||
changeAddress = address.Get();
|
coinControl.destChange = address.Get();
|
||||||
}
|
}
|
||||||
|
|
||||||
if (options.exists("changePosition"))
|
if (options.exists("changePosition"))
|
||||||
changePosition = options["changePosition"].get_int();
|
changePosition = options["changePosition"].get_int();
|
||||||
|
|
||||||
if (options.exists("includeWatching"))
|
if (options.exists("includeWatching"))
|
||||||
includeWatching = options["includeWatching"].get_bool();
|
coinControl.fAllowWatchOnly = options["includeWatching"].get_bool();
|
||||||
|
|
||||||
if (options.exists("lockUnspents"))
|
if (options.exists("lockUnspents"))
|
||||||
lockUnspents = options["lockUnspents"].get_bool();
|
lockUnspents = options["lockUnspents"].get_bool();
|
||||||
|
@ -2733,12 +2737,16 @@ UniValue fundrawtransaction(const JSONRPCRequest& request)
|
||||||
|
|
||||||
if (options.exists("feeRate"))
|
if (options.exists("feeRate"))
|
||||||
{
|
{
|
||||||
feeRate = CFeeRate(AmountFromValue(options["feeRate"]));
|
coinControl.nFeeRate = CFeeRate(AmountFromValue(options["feeRate"]));
|
||||||
overrideEstimatedFeerate = true;
|
coinControl.fOverrideFeeRate = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (options.exists("subtractFeeFromOutputs"))
|
if (options.exists("subtractFeeFromOutputs"))
|
||||||
subtractFeeFromOutputs = options["subtractFeeFromOutputs"].get_array();
|
subtractFeeFromOutputs = options["subtractFeeFromOutputs"].get_array();
|
||||||
|
|
||||||
|
if (options.exists("optIntoRbf")) {
|
||||||
|
coinControl.signalRbf = options["optIntoRbf"].get_bool();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -2767,7 +2775,7 @@ UniValue fundrawtransaction(const JSONRPCRequest& request)
|
||||||
CAmount nFeeOut;
|
CAmount nFeeOut;
|
||||||
std::string strFailReason;
|
std::string strFailReason;
|
||||||
|
|
||||||
if (!pwallet->FundTransaction(tx, nFeeOut, overrideEstimatedFeerate, feeRate, changePosition, strFailReason, includeWatching, lockUnspents, setSubtractFeeFromOutputs, reserveChangeKey, changeAddress)) {
|
if (!pwallet->FundTransaction(tx, nFeeOut, changePosition, strFailReason, lockUnspents, setSubtractFeeFromOutputs, coinControl, reserveChangeKey)) {
|
||||||
throw JSONRPCError(RPC_WALLET_ERROR, strFailReason);
|
throw JSONRPCError(RPC_WALLET_ERROR, strFailReason);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -2414,7 +2414,7 @@ bool CWallet::SignTransaction(CMutableTransaction &tx)
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool CWallet::FundTransaction(CMutableTransaction& tx, CAmount& nFeeRet, bool overrideEstimatedFeeRate, const CFeeRate& specificFeeRate, int& nChangePosInOut, std::string& strFailReason, bool includeWatching, bool lockUnspents, const std::set<int>& setSubtractFeeFromOutputs, bool keepReserveKey, const CTxDestination& destChange)
|
bool CWallet::FundTransaction(CMutableTransaction& tx, CAmount& nFeeRet, int& nChangePosInOut, std::string& strFailReason, bool lockUnspents, const std::set<int>& setSubtractFeeFromOutputs, CCoinControl coinControl, bool keepReserveKey)
|
||||||
{
|
{
|
||||||
std::vector<CRecipient> vecSend;
|
std::vector<CRecipient> vecSend;
|
||||||
|
|
||||||
|
@ -2426,12 +2426,7 @@ bool CWallet::FundTransaction(CMutableTransaction& tx, CAmount& nFeeRet, bool ov
|
||||||
vecSend.push_back(recipient);
|
vecSend.push_back(recipient);
|
||||||
}
|
}
|
||||||
|
|
||||||
CCoinControl coinControl;
|
|
||||||
coinControl.destChange = destChange;
|
|
||||||
coinControl.fAllowOtherInputs = true;
|
coinControl.fAllowOtherInputs = true;
|
||||||
coinControl.fAllowWatchOnly = includeWatching;
|
|
||||||
coinControl.fOverrideFeeRate = overrideEstimatedFeeRate;
|
|
||||||
coinControl.nFeeRate = specificFeeRate;
|
|
||||||
|
|
||||||
BOOST_FOREACH(const CTxIn& txin, tx.vin)
|
BOOST_FOREACH(const CTxIn& txin, tx.vin)
|
||||||
coinControl.Select(txin.prevout);
|
coinControl.Select(txin.prevout);
|
||||||
|
@ -2690,9 +2685,10 @@ bool CWallet::CreateTransaction(const std::vector<CRecipient>& vecSend, CWalletT
|
||||||
// and in the spirit of "smallest possible change from prior
|
// and in the spirit of "smallest possible change from prior
|
||||||
// behavior."
|
// behavior."
|
||||||
bool rbf = coinControl ? coinControl->signalRbf : fWalletRbf;
|
bool rbf = coinControl ? coinControl->signalRbf : fWalletRbf;
|
||||||
|
const uint32_t nSequence = rbf ? MAX_BIP125_RBF_SEQUENCE : (std::numeric_limits<unsigned int>::max() - 1);
|
||||||
for (const auto& coin : setCoins)
|
for (const auto& coin : setCoins)
|
||||||
txNew.vin.push_back(CTxIn(coin.outpoint,CScript(),
|
txNew.vin.push_back(CTxIn(coin.outpoint,CScript(),
|
||||||
std::numeric_limits<unsigned int>::max() - (rbf ? 2 : 1)));
|
nSequence));
|
||||||
|
|
||||||
// Fill in dummy signatures for fee calculation.
|
// Fill in dummy signatures for fee calculation.
|
||||||
if (!DummySignTx(txNew, setCoins)) {
|
if (!DummySignTx(txNew, setCoins)) {
|
||||||
|
|
|
@ -932,7 +932,7 @@ public:
|
||||||
* Insert additional inputs into the transaction by
|
* Insert additional inputs into the transaction by
|
||||||
* calling CreateTransaction();
|
* calling CreateTransaction();
|
||||||
*/
|
*/
|
||||||
bool FundTransaction(CMutableTransaction& tx, CAmount& nFeeRet, bool overrideEstimatedFeeRate, const CFeeRate& specificFeeRate, int& nChangePosInOut, std::string& strFailReason, bool includeWatching, bool lockUnspents, const std::set<int>& setSubtractFeeFromOutputs, bool keepReserveKey = true, const CTxDestination& destChange = CNoDestination());
|
bool FundTransaction(CMutableTransaction& tx, CAmount& nFeeRet, int& nChangePosInOut, std::string& strFailReason, bool lockUnspents, const std::set<int>& setSubtractFeeFromOutputs, CCoinControl, bool keepReserveKey = true);
|
||||||
bool SignTransaction(CMutableTransaction& tx);
|
bool SignTransaction(CMutableTransaction& tx);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
@ -99,6 +99,9 @@ class ReplaceByFeeTest(BitcoinTestFramework):
|
||||||
self.log.info("Running test opt-in...")
|
self.log.info("Running test opt-in...")
|
||||||
self.test_opt_in()
|
self.test_opt_in()
|
||||||
|
|
||||||
|
self.log.info("Running test RPC...")
|
||||||
|
self.test_rpc()
|
||||||
|
|
||||||
self.log.info("Running test prioritised transactions...")
|
self.log.info("Running test prioritised transactions...")
|
||||||
self.test_prioritised_transactions()
|
self.test_prioritised_transactions()
|
||||||
|
|
||||||
|
@ -516,5 +519,25 @@ class ReplaceByFeeTest(BitcoinTestFramework):
|
||||||
|
|
||||||
assert(tx2b_txid in self.nodes[0].getrawmempool())
|
assert(tx2b_txid in self.nodes[0].getrawmempool())
|
||||||
|
|
||||||
|
def test_rpc(self):
|
||||||
|
us0 = self.nodes[0].listunspent()[0]
|
||||||
|
ins = [us0];
|
||||||
|
outs = {self.nodes[0].getnewaddress() : Decimal(1.0000000)}
|
||||||
|
rawtx0 = self.nodes[0].createrawtransaction(ins, outs, 0, True)
|
||||||
|
rawtx1 = self.nodes[0].createrawtransaction(ins, outs, 0, False)
|
||||||
|
json0 = self.nodes[0].decoderawtransaction(rawtx0)
|
||||||
|
json1 = self.nodes[0].decoderawtransaction(rawtx1)
|
||||||
|
assert_equal(json0["vin"][0]["sequence"], 4294967293)
|
||||||
|
assert_equal(json1["vin"][0]["sequence"], 4294967295)
|
||||||
|
|
||||||
|
rawtx2 = self.nodes[0].createrawtransaction([], outs)
|
||||||
|
frawtx2a = self.nodes[0].fundrawtransaction(rawtx2, {"optIntoRbf": True})
|
||||||
|
frawtx2b = self.nodes[0].fundrawtransaction(rawtx2, {"optIntoRbf": False})
|
||||||
|
|
||||||
|
json0 = self.nodes[0].decoderawtransaction(frawtx2a['hex'])
|
||||||
|
json1 = self.nodes[0].decoderawtransaction(frawtx2b['hex'])
|
||||||
|
assert_equal(json0["vin"][0]["sequence"], 4294967293)
|
||||||
|
assert_equal(json1["vin"][0]["sequence"], 4294967294)
|
||||||
|
|
||||||
if __name__ == '__main__':
|
if __name__ == '__main__':
|
||||||
ReplaceByFeeTest().main()
|
ReplaceByFeeTest().main()
|
||||||
|
|
Loading…
Reference in a new issue