Skip to content

Commit 46311e7

Browse files
committed
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
2 parents be3e042 + 9a5a1d7 commit 46311e7

File tree

9 files changed

+96
-25
lines changed

9 files changed

+96
-25
lines changed

src/Makefile.am

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -198,6 +198,7 @@ libbitcoin_server_a_SOURCES = \
198198
noui.cpp \
199199
policy/fees.cpp \
200200
policy/policy.cpp \
201+
policy/rbf.cpp \
201202
pow.cpp \
202203
rest.cpp \
203204
rpc/blockchain.cpp \
@@ -240,7 +241,6 @@ libbitcoin_wallet_a_SOURCES = \
240241
wallet/rpcwallet.cpp \
241242
wallet/wallet.cpp \
242243
wallet/walletdb.cpp \
243-
policy/rbf.cpp \
244244
$(BITCOIN_CORE_H)
245245

246246
# crypto primitives library

src/bitcoin-tx.cpp

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@
1313
#include "core_io.h"
1414
#include "keystore.h"
1515
#include "policy/policy.h"
16+
#include "policy/rbf.h"
1617
#include "primitives/transaction.h"
1718
#include "script/script.h"
1819
#include "script/sign.h"
@@ -77,6 +78,7 @@ static int AppInitRawTx(int argc, char* argv[])
7778
strUsage += HelpMessageOpt("in=TXID:VOUT(:SEQUENCE_NUMBER)", _("Add input to TX"));
7879
strUsage += HelpMessageOpt("locktime=N", _("Set TX lock time to N"));
7980
strUsage += HelpMessageOpt("nversion=N", _("Set TX version to N"));
81+
strUsage += HelpMessageOpt("rbfoptin(=N)", _("Set RBF opt-in sequence number for input N (if not provided, opt-in all available inputs)"));
8082
strUsage += HelpMessageOpt("outaddr=VALUE:ADDRESS", _("Add address-based output to TX"));
8183
strUsage += HelpMessageOpt("outpubkey=VALUE:PUBKEY[:FLAGS]", _("Add pay-to-pubkey output to TX") + ". " +
8284
_("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)
202204
tx.nLockTime = (unsigned int) newLocktime;
203205
}
204206

207+
static void MutateTxRBFOptIn(CMutableTransaction& tx, const std::string& strInIdx)
208+
{
209+
// parse requested index
210+
int inIdx = atoi(strInIdx);
211+
if (inIdx < 0 || inIdx >= (int)tx.vin.size()) {
212+
throw std::runtime_error("Invalid TX input index '" + strInIdx + "'");
213+
}
214+
215+
// set the nSequence to MAX_INT - 2 (= RBF opt in flag)
216+
int cnt = 0;
217+
for (CTxIn& txin : tx.vin) {
218+
if (strInIdx == "" || cnt == inIdx) {
219+
if (txin.nSequence > MAX_BIP125_RBF_SEQUENCE) {
220+
txin.nSequence = MAX_BIP125_RBF_SEQUENCE;
221+
}
222+
}
223+
++cnt;
224+
}
225+
}
226+
205227
static void MutateTxAddInput(CMutableTransaction& tx, const std::string& strInput)
206228
{
207229
std::vector<std::string> vStrInputParts;
@@ -649,6 +671,9 @@ static void MutateTx(CMutableTransaction& tx, const std::string& command,
649671
MutateTxVersion(tx, commandVal);
650672
else if (command == "locktime")
651673
MutateTxLocktime(tx, commandVal);
674+
else if (command == "rbfoptin") {
675+
MutateTxRBFOptIn(tx, commandVal);
676+
}
652677

653678
else if (command == "delin")
654679
MutateTxDelInput(tx, commandVal);

src/policy/rbf.h

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,8 @@
77

88
#include "txmempool.h"
99

10+
static const uint32_t MAX_BIP125_RBF_SEQUENCE = 0xfffffffd;
11+
1012
enum RBFTransactionState {
1113
RBF_TRANSACTIONSTATE_UNKNOWN,
1214
RBF_TRANSACTIONSTATE_REPLACEABLE_BIP125,

src/rpc/client.cpp

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -86,6 +86,7 @@ static const CRPCConvertParam vRPCConvertParams[] =
8686
{ "createrawtransaction", 0, "inputs" },
8787
{ "createrawtransaction", 1, "outputs" },
8888
{ "createrawtransaction", 2, "locktime" },
89+
{ "createrawtransaction", 3, "optintorbf" },
8990
{ "signrawtransaction", 1, "prevtxs" },
9091
{ "signrawtransaction", 2, "privkeys" },
9192
{ "sendrawtransaction", 1, "allowhighfees" },

src/rpc/rawtransaction.cpp

Lines changed: 21 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@
1414
#include "merkleblock.h"
1515
#include "net.h"
1616
#include "policy/policy.h"
17+
#include "policy/rbf.h"
1718
#include "primitives/transaction.h"
1819
#include "rpc/server.h"
1920
#include "script/script.h"
@@ -289,9 +290,9 @@ UniValue verifytxoutproof(const JSONRPCRequest& request)
289290

290291
UniValue createrawtransaction(const JSONRPCRequest& request)
291292
{
292-
if (request.fHelp || request.params.size() < 2 || request.params.size() > 3)
293+
if (request.fHelp || request.params.size() < 2 || request.params.size() > 4)
293294
throw std::runtime_error(
294-
"createrawtransaction [{\"txid\":\"id\",\"vout\":n},...] {\"address\":amount,\"data\":\"hex\",...} ( locktime )\n"
295+
"createrawtransaction [{\"txid\":\"id\",\"vout\":n},...] {\"address\":amount,\"data\":\"hex\",...} ( locktime ) ( optintorbf )\n"
295296
"\nCreate a transaction spending the given inputs and creating new outputs.\n"
296297
"Outputs can be addresses or data.\n"
297298
"Returns hex-encoded raw transaction.\n"
@@ -315,6 +316,7 @@ UniValue createrawtransaction(const JSONRPCRequest& request)
315316
" ,...\n"
316317
" }\n"
317318
"3. locktime (numeric, optional, default=0) Raw locktime. Non-0 value also locktime-activates inputs\n"
319+
"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"
318320
"\nResult:\n"
319321
"\"transaction\" (string) hex string of the transaction\n"
320322

@@ -341,6 +343,8 @@ UniValue createrawtransaction(const JSONRPCRequest& request)
341343
rawTx.nLockTime = nLockTime;
342344
}
343345

346+
bool rbfOptIn = request.params.size() > 3 ? request.params[3].isTrue() : false;
347+
344348
for (unsigned int idx = 0; idx < inputs.size(); idx++) {
345349
const UniValue& input = inputs[idx];
346350
const UniValue& o = input.get_obj();
@@ -354,16 +358,24 @@ UniValue createrawtransaction(const JSONRPCRequest& request)
354358
if (nOutput < 0)
355359
throw JSONRPCError(RPC_INVALID_PARAMETER, "Invalid parameter, vout must be positive");
356360

357-
uint32_t nSequence = (rawTx.nLockTime ? std::numeric_limits<uint32_t>::max() - 1 : std::numeric_limits<uint32_t>::max());
361+
uint32_t nSequence;
362+
if (rbfOptIn) {
363+
nSequence = MAX_BIP125_RBF_SEQUENCE;
364+
} else if (rawTx.nLockTime) {
365+
nSequence = std::numeric_limits<uint32_t>::max() - 1;
366+
} else {
367+
nSequence = std::numeric_limits<uint32_t>::max();
368+
}
358369

359370
// set the sequence number if passed in the parameters object
360371
const UniValue& sequenceObj = find_value(o, "sequence");
361372
if (sequenceObj.isNum()) {
362373
int64_t seqNr64 = sequenceObj.get_int64();
363-
if (seqNr64 < 0 || seqNr64 > std::numeric_limits<uint32_t>::max())
374+
if (seqNr64 < 0 || seqNr64 > std::numeric_limits<uint32_t>::max()) {
364375
throw JSONRPCError(RPC_INVALID_PARAMETER, "Invalid parameter, sequence number is out of range");
365-
else
376+
} else {
366377
nSequence = (uint32_t)seqNr64;
378+
}
367379
}
368380

369381
CTxIn in(COutPoint(txid, nOutput), CScript(), nSequence);
@@ -397,6 +409,10 @@ UniValue createrawtransaction(const JSONRPCRequest& request)
397409
}
398410
}
399411

412+
if (request.params.size() > 3 && rbfOptIn != SignalsOptInRBF(rawTx)) {
413+
throw JSONRPCError(RPC_INVALID_PARAMETER, "Invalid parameter combination: Sequence number(s) contradict optintorbf option");
414+
}
415+
400416
return EncodeHexTx(rawTx);
401417
}
402418

src/wallet/rpcwallet.cpp

Lines changed: 19 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@
2121
#include "timedata.h"
2222
#include "util.h"
2323
#include "utilmoneystr.h"
24+
#include "wallet/coincontrol.h"
2425
#include "wallet/feebumper.h"
2526
#include "wallet/wallet.h"
2627
#include "wallet/walletdb.h"
@@ -2628,7 +2629,7 @@ UniValue fundrawtransaction(const JSONRPCRequest& request)
26282629
return NullUniValue;
26292630
}
26302631

2631-
if (request.fHelp || request.params.size() < 1 || request.params.size() > 2)
2632+
if (request.fHelp || request.params.size() < 1 || request.params.size() > 3)
26322633
throw std::runtime_error(
26332634
"fundrawtransaction \"hexstring\" ( options )\n"
26342635
"\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)
26572658
" Those recipients will receive less bitcoins than you enter in their corresponding amount field.\n"
26582659
" If no outputs are specified here, the sender pays the fee.\n"
26592660
" [vout_index,...]\n"
2661+
" \"optIntoRbf\" (boolean, optional) Allow this transaction to be replaced by a transaction with higher fees\n"
26602662
" }\n"
26612663
" for backward compatibility: passing in a true instead of an object will result in {\"includeWatching\":true}\n"
26622664
"\nResult:\n"
@@ -2678,20 +2680,21 @@ UniValue fundrawtransaction(const JSONRPCRequest& request)
26782680

26792681
RPCTypeCheck(request.params, boost::assign::list_of(UniValue::VSTR));
26802682

2681-
CTxDestination changeAddress = CNoDestination();
2683+
CCoinControl coinControl;
2684+
coinControl.destChange = CNoDestination();
26822685
int changePosition = -1;
2683-
bool includeWatching = false;
2686+
coinControl.fAllowWatchOnly = false; // include watching
26842687
bool lockUnspents = false;
26852688
bool reserveChangeKey = true;
2686-
CFeeRate feeRate = CFeeRate(0);
2687-
bool overrideEstimatedFeerate = false;
2689+
coinControl.nFeeRate = CFeeRate(0);
2690+
coinControl.fOverrideFeeRate = false;
26882691
UniValue subtractFeeFromOutputs;
26892692
std::set<int> setSubtractFeeFromOutputs;
26902693

26912694
if (request.params.size() > 1) {
26922695
if (request.params[1].type() == UniValue::VBOOL) {
26932696
// backward compatibility bool only fallback
2694-
includeWatching = request.params[1].get_bool();
2697+
coinControl.fAllowWatchOnly = request.params[1].get_bool();
26952698
}
26962699
else {
26972700
RPCTypeCheck(request.params, boost::assign::list_of(UniValue::VSTR)(UniValue::VOBJ));
@@ -2707,6 +2710,7 @@ UniValue fundrawtransaction(const JSONRPCRequest& request)
27072710
{"reserveChangeKey", UniValueType(UniValue::VBOOL)},
27082711
{"feeRate", UniValueType()}, // will be checked below
27092712
{"subtractFeeFromOutputs", UniValueType(UniValue::VARR)},
2713+
{"optIntoRbf", UniValueType(UniValue::VBOOL)},
27102714
},
27112715
true, true);
27122716

@@ -2716,14 +2720,14 @@ UniValue fundrawtransaction(const JSONRPCRequest& request)
27162720
if (!address.IsValid())
27172721
throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "changeAddress must be a valid bitcoin address");
27182722

2719-
changeAddress = address.Get();
2723+
coinControl.destChange = address.Get();
27202724
}
27212725

27222726
if (options.exists("changePosition"))
27232727
changePosition = options["changePosition"].get_int();
27242728

27252729
if (options.exists("includeWatching"))
2726-
includeWatching = options["includeWatching"].get_bool();
2730+
coinControl.fAllowWatchOnly = options["includeWatching"].get_bool();
27272731

27282732
if (options.exists("lockUnspents"))
27292733
lockUnspents = options["lockUnspents"].get_bool();
@@ -2733,12 +2737,16 @@ UniValue fundrawtransaction(const JSONRPCRequest& request)
27332737

27342738
if (options.exists("feeRate"))
27352739
{
2736-
feeRate = CFeeRate(AmountFromValue(options["feeRate"]));
2737-
overrideEstimatedFeerate = true;
2740+
coinControl.nFeeRate = CFeeRate(AmountFromValue(options["feeRate"]));
2741+
coinControl.fOverrideFeeRate = true;
27382742
}
27392743

27402744
if (options.exists("subtractFeeFromOutputs"))
27412745
subtractFeeFromOutputs = options["subtractFeeFromOutputs"].get_array();
2746+
2747+
if (options.exists("optIntoRbf")) {
2748+
coinControl.signalRbf = options["optIntoRbf"].get_bool();
2749+
}
27422750
}
27432751
}
27442752

@@ -2767,7 +2775,7 @@ UniValue fundrawtransaction(const JSONRPCRequest& request)
27672775
CAmount nFeeOut;
27682776
std::string strFailReason;
27692777

2770-
if (!pwallet->FundTransaction(tx, nFeeOut, overrideEstimatedFeerate, feeRate, changePosition, strFailReason, includeWatching, lockUnspents, setSubtractFeeFromOutputs, reserveChangeKey, changeAddress)) {
2778+
if (!pwallet->FundTransaction(tx, nFeeOut, changePosition, strFailReason, lockUnspents, setSubtractFeeFromOutputs, coinControl, reserveChangeKey)) {
27712779
throw JSONRPCError(RPC_WALLET_ERROR, strFailReason);
27722780
}
27732781

src/wallet/wallet.cpp

Lines changed: 3 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -2414,7 +2414,7 @@ bool CWallet::SignTransaction(CMutableTransaction &tx)
24142414
return true;
24152415
}
24162416

2417-
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)
2417+
bool CWallet::FundTransaction(CMutableTransaction& tx, CAmount& nFeeRet, int& nChangePosInOut, std::string& strFailReason, bool lockUnspents, const std::set<int>& setSubtractFeeFromOutputs, CCoinControl coinControl, bool keepReserveKey)
24182418
{
24192419
std::vector<CRecipient> vecSend;
24202420

@@ -2426,12 +2426,7 @@ bool CWallet::FundTransaction(CMutableTransaction& tx, CAmount& nFeeRet, bool ov
24262426
vecSend.push_back(recipient);
24272427
}
24282428

2429-
CCoinControl coinControl;
2430-
coinControl.destChange = destChange;
24312429
coinControl.fAllowOtherInputs = true;
2432-
coinControl.fAllowWatchOnly = includeWatching;
2433-
coinControl.fOverrideFeeRate = overrideEstimatedFeeRate;
2434-
coinControl.nFeeRate = specificFeeRate;
24352430

24362431
BOOST_FOREACH(const CTxIn& txin, tx.vin)
24372432
coinControl.Select(txin.prevout);
@@ -2690,9 +2685,10 @@ bool CWallet::CreateTransaction(const std::vector<CRecipient>& vecSend, CWalletT
26902685
// and in the spirit of "smallest possible change from prior
26912686
// behavior."
26922687
bool rbf = coinControl ? coinControl->signalRbf : fWalletRbf;
2688+
const uint32_t nSequence = rbf ? MAX_BIP125_RBF_SEQUENCE : (std::numeric_limits<unsigned int>::max() - 1);
26932689
for (const auto& coin : setCoins)
26942690
txNew.vin.push_back(CTxIn(coin.outpoint,CScript(),
2695-
std::numeric_limits<unsigned int>::max() - (rbf ? 2 : 1)));
2691+
nSequence));
26962692

26972693
// Fill in dummy signatures for fee calculation.
26982694
if (!DummySignTx(txNew, setCoins)) {

src/wallet/wallet.h

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -932,7 +932,7 @@ class CWallet : public CCryptoKeyStore, public CValidationInterface
932932
* Insert additional inputs into the transaction by
933933
* calling CreateTransaction();
934934
*/
935-
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());
935+
bool FundTransaction(CMutableTransaction& tx, CAmount& nFeeRet, int& nChangePosInOut, std::string& strFailReason, bool lockUnspents, const std::set<int>& setSubtractFeeFromOutputs, CCoinControl, bool keepReserveKey = true);
936936
bool SignTransaction(CMutableTransaction& tx);
937937

938938
/**

test/functional/replace-by-fee.py

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -99,6 +99,9 @@ def run_test(self):
9999
self.log.info("Running test opt-in...")
100100
self.test_opt_in()
101101

102+
self.log.info("Running test RPC...")
103+
self.test_rpc()
104+
102105
self.log.info("Running test prioritised transactions...")
103106
self.test_prioritised_transactions()
104107

@@ -516,5 +519,25 @@ def test_prioritised_transactions(self):
516519

517520
assert(tx2b_txid in self.nodes[0].getrawmempool())
518521

522+
def test_rpc(self):
523+
us0 = self.nodes[0].listunspent()[0]
524+
ins = [us0];
525+
outs = {self.nodes[0].getnewaddress() : Decimal(1.0000000)}
526+
rawtx0 = self.nodes[0].createrawtransaction(ins, outs, 0, True)
527+
rawtx1 = self.nodes[0].createrawtransaction(ins, outs, 0, False)
528+
json0 = self.nodes[0].decoderawtransaction(rawtx0)
529+
json1 = self.nodes[0].decoderawtransaction(rawtx1)
530+
assert_equal(json0["vin"][0]["sequence"], 4294967293)
531+
assert_equal(json1["vin"][0]["sequence"], 4294967295)
532+
533+
rawtx2 = self.nodes[0].createrawtransaction([], outs)
534+
frawtx2a = self.nodes[0].fundrawtransaction(rawtx2, {"optIntoRbf": True})
535+
frawtx2b = self.nodes[0].fundrawtransaction(rawtx2, {"optIntoRbf": False})
536+
537+
json0 = self.nodes[0].decoderawtransaction(frawtx2a['hex'])
538+
json1 = self.nodes[0].decoderawtransaction(frawtx2b['hex'])
539+
assert_equal(json0["vin"][0]["sequence"], 4294967293)
540+
assert_equal(json1["vin"][0]["sequence"], 4294967294)
541+
519542
if __name__ == '__main__':
520543
ReplaceByFeeTest().main()

0 commit comments

Comments
 (0)