Add psbtbumpfee RPC

This commit is contained in:
Andrew Chow 2020-04-13 14:48:03 -04:00
parent f32f7e907a
commit 4638224f64
3 changed files with 33 additions and 9 deletions

View File

@ -151,6 +151,7 @@ static const CRPCConvertParam vRPCConvertParams[] =
{ "getmempoolancestors", 1, "verbose" }, { "getmempoolancestors", 1, "verbose" },
{ "getmempooldescendants", 1, "verbose" }, { "getmempooldescendants", 1, "verbose" },
{ "bumpfee", 1, "options" }, { "bumpfee", 1, "options" },
{ "psbtbumpfee", 1, "options" },
{ "logging", 0, "include" }, { "logging", 0, "include" },
{ "logging", 1, "exclude" }, { "logging", 1, "exclude" },
{ "disconnectnode", 1, "nodeid" }, { "disconnectnode", 1, "nodeid" },

View File

@ -3245,8 +3245,11 @@ UniValue signrawtransactionwithwallet(const JSONRPCRequest& request)
static UniValue bumpfee(const JSONRPCRequest& request) static UniValue bumpfee(const JSONRPCRequest& request)
{ {
RPCHelpMan{"bumpfee", bool want_psbt = request.strMethod == "psbtbumpfee";
RPCHelpMan{request.strMethod,
"\nBumps the fee of an opt-in-RBF transaction T, replacing it with a new transaction B.\n" "\nBumps the fee of an opt-in-RBF transaction T, replacing it with a new transaction B.\n"
+ std::string(want_psbt ? "Returns a PSBT instead of creating and signing a new transaction.\n" : "") +
"An opt-in RBF transaction with the given txid must be in the wallet.\n" "An opt-in RBF transaction with the given txid must be in the wallet.\n"
"The command will pay the additional fee by reducing change outputs or adding inputs when necessary. It may add a new change output if one does not already exist.\n" "The command will pay the additional fee by reducing change outputs or adding inputs when necessary. It may add a new change output if one does not already exist.\n"
"All inputs in the original transaction will be included in the replacement transaction.\n" "All inputs in the original transaction will be included in the replacement transaction.\n"
@ -3277,20 +3280,24 @@ static UniValue bumpfee(const JSONRPCRequest& request)
"options"}, "options"},
}, },
RPCResult{ RPCResult{
RPCResult::Type::OBJ, "", "", { RPCResult::Type::OBJ, "", "", Cat(Cat<std::vector<RPCResult>>(
{RPCResult::Type::STR, "psbt", "The base64-encoded unsigned PSBT of the new transaction. Only returned when wallet private keys are disabled."}, {
{RPCResult::Type::STR_HEX, "txid", "The id of the new transaction. Only returned when wallet private keys are enabled."}, {RPCResult::Type::STR, "psbt", "The base64-encoded unsigned PSBT of the new transaction." + std::string(want_psbt ? "" : " Only returned when wallet private keys are disabled. (DEPRECATED)")},
},
want_psbt ? std::vector<RPCResult>{} : std::vector<RPCResult>{{RPCResult::Type::STR_HEX, "txid", "The id of the new transaction. Only returned when wallet private keys are enabled."}}
),
{
{RPCResult::Type::STR_AMOUNT, "origfee", "The fee of the replaced transaction."}, {RPCResult::Type::STR_AMOUNT, "origfee", "The fee of the replaced transaction."},
{RPCResult::Type::STR_AMOUNT, "fee", "The fee of the new transaction."}, {RPCResult::Type::STR_AMOUNT, "fee", "The fee of the new transaction."},
{RPCResult::Type::ARR, "errors", "Errors encountered during processing (may be empty).", {RPCResult::Type::ARR, "errors", "Errors encountered during processing (may be empty).",
{ {
{RPCResult::Type::STR, "", ""}, {RPCResult::Type::STR, "", ""},
}}, }},
} })
}, },
RPCExamples{ RPCExamples{
"\nBump the fee, get the new transaction\'s txid\n" + "\nBump the fee, get the new transaction\'s" + std::string(want_psbt ? "psbt" : "txid") + "\n" +
HelpExampleCli("bumpfee", "<txid>") HelpExampleCli(request.strMethod, "<txid>")
}, },
}.Check(request); }.Check(request);
@ -3298,6 +3305,10 @@ static UniValue bumpfee(const JSONRPCRequest& request)
if (!wallet) return NullUniValue; if (!wallet) return NullUniValue;
CWallet* const pwallet = wallet.get(); CWallet* const pwallet = wallet.get();
if (pwallet->IsWalletFlagSet(WALLET_FLAG_DISABLE_PRIVATE_KEYS) && !want_psbt) {
want_psbt = true;
}
RPCTypeCheck(request.params, {UniValue::VSTR, UniValue::VOBJ}); RPCTypeCheck(request.params, {UniValue::VSTR, UniValue::VOBJ});
uint256 hash(ParseHashV(request.params[0], "txid")); uint256 hash(ParseHashV(request.params[0], "txid"));
@ -3382,7 +3393,7 @@ static UniValue bumpfee(const JSONRPCRequest& request)
// If wallet private keys are enabled, return the new transaction id, // If wallet private keys are enabled, return the new transaction id,
// otherwise return the base64-encoded unsigned PSBT of the new transaction. // otherwise return the base64-encoded unsigned PSBT of the new transaction.
if (!pwallet->IsWalletFlagSet(WALLET_FLAG_DISABLE_PRIVATE_KEYS)) { if (!want_psbt) {
if (!feebumper::SignTransaction(*pwallet, mtx)) { if (!feebumper::SignTransaction(*pwallet, mtx)) {
throw JSONRPCError(RPC_WALLET_ERROR, "Can't sign transaction."); throw JSONRPCError(RPC_WALLET_ERROR, "Can't sign transaction.");
} }
@ -3415,6 +3426,11 @@ static UniValue bumpfee(const JSONRPCRequest& request)
return result; return result;
} }
static UniValue psbtbumpfee(const JSONRPCRequest& request)
{
return bumpfee(request);
}
UniValue rescanblockchain(const JSONRPCRequest& request) UniValue rescanblockchain(const JSONRPCRequest& request)
{ {
RPCHelpMan{"rescanblockchain", RPCHelpMan{"rescanblockchain",
@ -4160,6 +4176,7 @@ static const CRPCCommand commands[] =
{ "wallet", "addmultisigaddress", &addmultisigaddress, {"nrequired","keys","label","address_type"} }, { "wallet", "addmultisigaddress", &addmultisigaddress, {"nrequired","keys","label","address_type"} },
{ "wallet", "backupwallet", &backupwallet, {"destination"} }, { "wallet", "backupwallet", &backupwallet, {"destination"} },
{ "wallet", "bumpfee", &bumpfee, {"txid", "options"} }, { "wallet", "bumpfee", &bumpfee, {"txid", "options"} },
{ "wallet", "psbtbumpfee", &psbtbumpfee, {"txid", "options"} },
{ "wallet", "createwallet", &createwallet, {"wallet_name", "disable_private_keys", "blank", "passphrase", "avoid_reuse", "descriptors"} }, { "wallet", "createwallet", &createwallet, {"wallet_name", "disable_private_keys", "blank", "passphrase", "avoid_reuse", "descriptors"} },
{ "wallet", "dumpprivkey", &dumpprivkey, {"address"} }, { "wallet", "dumpprivkey", &dumpprivkey, {"address"} },
{ "wallet", "dumpwallet", &dumpwallet, {"filename"} }, { "wallet", "dumpwallet", &dumpwallet, {"filename"} },

View File

@ -123,13 +123,19 @@ def test_simple_bumpfee_succeeds(self, mode, rbf_node, peer_node, dest_address):
self.sync_mempools((rbf_node, peer_node)) self.sync_mempools((rbf_node, peer_node))
assert rbfid in rbf_node.getrawmempool() and rbfid in peer_node.getrawmempool() assert rbfid in rbf_node.getrawmempool() and rbfid in peer_node.getrawmempool()
if mode == "fee_rate": if mode == "fee_rate":
bumped_psbt = rbf_node.psbtbumpfee(rbfid, {"fee_rate": NORMAL})
bumped_tx = rbf_node.bumpfee(rbfid, {"fee_rate": NORMAL}) bumped_tx = rbf_node.bumpfee(rbfid, {"fee_rate": NORMAL})
else: else:
bumped_psbt = rbf_node.psbtbumpfee(rbfid)
bumped_tx = rbf_node.bumpfee(rbfid) bumped_tx = rbf_node.bumpfee(rbfid)
assert_equal(bumped_tx["errors"], []) assert_equal(bumped_tx["errors"], [])
assert bumped_tx["fee"] > -rbftx["fee"] assert bumped_tx["fee"] > -rbftx["fee"]
assert_equal(bumped_tx["origfee"], -rbftx["fee"]) assert_equal(bumped_tx["origfee"], -rbftx["fee"])
assert "psbt" not in bumped_tx assert "psbt" not in bumped_tx
assert_equal(bumped_psbt["errors"], [])
assert bumped_psbt["fee"] > -rbftx["fee"]
assert_equal(bumped_psbt["origfee"], -rbftx["fee"])
assert "psbt" in bumped_psbt
# check that bumped_tx propagates, original tx was evicted and has a wallet conflict # check that bumped_tx propagates, original tx was evicted and has a wallet conflict
self.sync_mempools((rbf_node, peer_node)) self.sync_mempools((rbf_node, peer_node))
assert bumped_tx["txid"] in rbf_node.getrawmempool() assert bumped_tx["txid"] in rbf_node.getrawmempool()
@ -391,7 +397,7 @@ def test_watchonly_psbt(self, peer_node, rbf_node, dest_address):
assert_equal(len(watcher.decodepsbt(psbt)["tx"]["vin"]), 1) assert_equal(len(watcher.decodepsbt(psbt)["tx"]["vin"]), 1)
# Bump fee, obnoxiously high to add additional watchonly input # Bump fee, obnoxiously high to add additional watchonly input
bumped_psbt = watcher.bumpfee(original_txid, {"fee_rate": HIGH}) bumped_psbt = watcher.psbtbumpfee(original_txid, {"fee_rate": HIGH})
assert_greater_than(len(watcher.decodepsbt(bumped_psbt['psbt'])["tx"]["vin"]), 1) assert_greater_than(len(watcher.decodepsbt(bumped_psbt['psbt'])["tx"]["vin"]), 1)
assert "txid" not in bumped_psbt assert "txid" not in bumped_psbt
assert_equal(bumped_psbt["origfee"], -watcher.gettransaction(original_txid)["fee"]) assert_equal(bumped_psbt["origfee"], -watcher.gettransaction(original_txid)["fee"])