mirror of
https://github.com/dashpay/dash.git
synced 2024-12-25 12:02:48 +01:00
merge bitcoin#19969: Send RPC bug fix and touch-ups
This commit is contained in:
parent
17e2168a4f
commit
76a49addd0
@ -9,5 +9,6 @@ RPC changes
|
|||||||
prevents adding more inputs if necessary and consequently the RPC fails.
|
prevents adding more inputs if necessary and consequently the RPC fails.
|
||||||
|
|
||||||
- A new `send` RPC with similar syntax to `walletcreatefundedpsbt`, including
|
- A new `send` RPC with similar syntax to `walletcreatefundedpsbt`, including
|
||||||
support for coin selection and a custom fee rate. Using the new `send` method
|
support for coin selection and a custom fee rate. The `send` RPC is experimental
|
||||||
is encouraged: `sendmany` and `sendtoaddress` may be deprecated in a future release.
|
and may change in subsequent releases. Using it is encouraged once it's no
|
||||||
|
longer experimental: `sendmany` and `sendtoaddress` may be deprecated in a future release.
|
||||||
|
@ -21,14 +21,16 @@
|
|||||||
|
|
||||||
CMutableTransaction ConstructTransaction(const UniValue& inputs_in, const UniValue& outputs_in, const UniValue& locktime)
|
CMutableTransaction ConstructTransaction(const UniValue& inputs_in, const UniValue& outputs_in, const UniValue& locktime)
|
||||||
{
|
{
|
||||||
if (outputs_in.isNull())
|
if (outputs_in.isNull()) {
|
||||||
throw JSONRPCError(RPC_INVALID_PARAMETER, "Invalid parameter, output argument must be non-null");
|
throw JSONRPCError(RPC_INVALID_PARAMETER, "Invalid parameter, output argument must be non-null");
|
||||||
|
}
|
||||||
|
|
||||||
UniValue inputs;
|
UniValue inputs;
|
||||||
if (inputs_in.isNull())
|
if (inputs_in.isNull()) {
|
||||||
inputs = UniValue::VARR;
|
inputs = UniValue::VARR;
|
||||||
else
|
} else {
|
||||||
inputs = inputs_in.get_array();
|
inputs = inputs_in.get_array();
|
||||||
|
}
|
||||||
|
|
||||||
const bool outputs_is_obj = outputs_in.isObject();
|
const bool outputs_is_obj = outputs_in.isObject();
|
||||||
UniValue outputs = outputs_is_obj ? outputs_in.get_obj() : outputs_in.get_array();
|
UniValue outputs = outputs_is_obj ? outputs_in.get_obj() : outputs_in.get_array();
|
||||||
|
@ -3267,7 +3267,7 @@ void FundTransaction(CWallet* const pwallet, CMutableTransaction& tx, CAmount& f
|
|||||||
{"lockUnspents", UniValueType(UniValue::VBOOL)},
|
{"lockUnspents", UniValueType(UniValue::VBOOL)},
|
||||||
{"lock_unspents", UniValueType(UniValue::VBOOL)},
|
{"lock_unspents", UniValueType(UniValue::VBOOL)},
|
||||||
{"locktime", UniValueType(UniValue::VNUM)},
|
{"locktime", UniValueType(UniValue::VNUM)},
|
||||||
{"feeRate", UniValueType()}, // will be checked below,
|
{"feeRate", UniValueType()}, // will be checked below
|
||||||
{"psbt", UniValueType(UniValue::VBOOL)},
|
{"psbt", UniValueType(UniValue::VBOOL)},
|
||||||
{"subtractFeeFromOutputs", UniValueType(UniValue::VARR)},
|
{"subtractFeeFromOutputs", UniValueType(UniValue::VARR)},
|
||||||
{"subtract_fee_from_outputs", UniValueType(UniValue::VARR)},
|
{"subtract_fee_from_outputs", UniValueType(UniValue::VARR)},
|
||||||
@ -4006,9 +4006,10 @@ static UniValue listlabels(const JSONRPCRequest& request)
|
|||||||
static UniValue send(const JSONRPCRequest& request)
|
static UniValue send(const JSONRPCRequest& request)
|
||||||
{
|
{
|
||||||
RPCHelpMan{"send",
|
RPCHelpMan{"send",
|
||||||
|
"\nEXPERIMENTAL warning: this call may be changed in future releases.\n"
|
||||||
"\nSend a transaction.\n",
|
"\nSend a transaction.\n",
|
||||||
{
|
{
|
||||||
{"outputs", RPCArg::Type::ARR, RPCArg::Optional::NO, "a json array with outputs (key-value pairs), where none of the keys are duplicated.\n"
|
{"outputs", RPCArg::Type::ARR, RPCArg::Optional::NO, "A JSON array with outputs (key-value pairs), where none of the keys are duplicated.\n"
|
||||||
"That is, each address can only appear once and there can only be one 'data' object.\n"
|
"That is, each address can only appear once and there can only be one 'data' object.\n"
|
||||||
"For convenience, a dictionary, which holds the key-value pairs directly, is also accepted.",
|
"For convenience, a dictionary, which holds the key-value pairs directly, is also accepted.",
|
||||||
{
|
{
|
||||||
@ -4039,7 +4040,7 @@ static UniValue send(const JSONRPCRequest& request)
|
|||||||
{"include_watching", RPCArg::Type::BOOL, /* default */ "true for watch-only wallets, otherwise false", "Also select inputs which are watch only.\n"
|
{"include_watching", RPCArg::Type::BOOL, /* default */ "true for watch-only wallets, otherwise false", "Also select inputs which are watch only.\n"
|
||||||
"Only solvable inputs can be used. Watch-only destinations are solvable if the public key and/or output script was imported,\n"
|
"Only solvable inputs can be used. Watch-only destinations are solvable if the public key and/or output script was imported,\n"
|
||||||
"e.g. with 'importpubkey' or 'importmulti' with the 'pubkeys' or 'desc' field."},
|
"e.g. with 'importpubkey' or 'importmulti' with the 'pubkeys' or 'desc' field."},
|
||||||
{"inputs", RPCArg::Type::ARR, /* default */ "empty array", "Specify inputs instead of adding them automatically. A json array of json objects",
|
{"inputs", RPCArg::Type::ARR, /* default */ "empty array", "Specify inputs instead of adding them automatically. A JSON array of JSON objects",
|
||||||
{
|
{
|
||||||
{"txid", RPCArg::Type::STR_HEX, RPCArg::Optional::NO, "The transaction id"},
|
{"txid", RPCArg::Type::STR_HEX, RPCArg::Optional::NO, "The transaction id"},
|
||||||
{"vout", RPCArg::Type::NUM, RPCArg::Optional::NO, "The output number"},
|
{"vout", RPCArg::Type::NUM, RPCArg::Optional::NO, "The output number"},
|
||||||
@ -4049,7 +4050,7 @@ static UniValue send(const JSONRPCRequest& request)
|
|||||||
{"locktime", RPCArg::Type::NUM, /* default */ "0", "Raw locktime. Non-0 value also locktime-activates inputs"},
|
{"locktime", RPCArg::Type::NUM, /* default */ "0", "Raw locktime. Non-0 value also locktime-activates inputs"},
|
||||||
{"lock_unspents", RPCArg::Type::BOOL, /* default */ "false", "Lock selected unspent outputs"},
|
{"lock_unspents", RPCArg::Type::BOOL, /* default */ "false", "Lock selected unspent outputs"},
|
||||||
{"psbt", RPCArg::Type::BOOL, /* default */ "automatic", "Always return a PSBT, implies add_to_wallet=false."},
|
{"psbt", RPCArg::Type::BOOL, /* default */ "automatic", "Always return a PSBT, implies add_to_wallet=false."},
|
||||||
{"subtract_fee_from_outputs", RPCArg::Type::ARR, /* default */ "empty array", "A json array of integers.\n"
|
{"subtract_fee_from_outputs", RPCArg::Type::ARR, /* default */ "empty array", "A JSON array of integers.\n"
|
||||||
"The fee will be equally deducted from the amount of each specified output.\n"
|
"The fee will be equally deducted from the amount of each specified output.\n"
|
||||||
"Those recipients will receive less funds than you enter in their corresponding amount field.\n"
|
"Those recipients will receive less funds than you enter in their corresponding amount field.\n"
|
||||||
"If no outputs are specified here, the sender pays the fee.",
|
"If no outputs are specified here, the sender pays the fee.",
|
||||||
@ -4071,8 +4072,8 @@ static UniValue send(const JSONRPCRequest& request)
|
|||||||
},
|
},
|
||||||
RPCExamples{""
|
RPCExamples{""
|
||||||
"\nSend with a fee rate of 1 " + CURRENCY_ATOM + "/B\n"
|
"\nSend with a fee rate of 1 " + CURRENCY_ATOM + "/B\n"
|
||||||
+ HelpExampleCli("send", "'{\"" + EXAMPLE_ADDRESS[0] + "\": 0.1}' 1 " + CURRENCY_ATOM + "/B\n" +
|
+ HelpExampleCli("send", "'{\"" + EXAMPLE_ADDRESS[0] + "\": 0.1}' 1 " + CURRENCY_ATOM + "/B\n") +
|
||||||
"\nCreate a transaction that should confirm the next block, with a specific input, and return result without adding to wallet or broadcasting to the network\n")
|
"\nCreate a transaction that should confirm the next block, with a specific input, and return result without adding to wallet or broadcasting to the network\n"
|
||||||
+ HelpExampleCli("send", "'{\"" + EXAMPLE_ADDRESS[0] + "\": 0.1}' 1 economical '{\"add_to_wallet\": false, \"inputs\": [{\"txid\":\"a08e6907dbbd3d809776dbfc5d82e371b764ed838b5655e72f463568df1aadf0\", \"vout\":1}]}'")
|
+ HelpExampleCli("send", "'{\"" + EXAMPLE_ADDRESS[0] + "\": 0.1}' 1 economical '{\"add_to_wallet\": false, \"inputs\": [{\"txid\":\"a08e6907dbbd3d809776dbfc5d82e371b764ed838b5655e72f463568df1aadf0\", \"vout\":1}]}'")
|
||||||
}
|
}
|
||||||
}.Check(request);
|
}.Check(request);
|
||||||
@ -4136,7 +4137,7 @@ static UniValue send(const JSONRPCRequest& request)
|
|||||||
// Make a blank psbt
|
// Make a blank psbt
|
||||||
PartiallySignedTransaction psbtx(rawTx);
|
PartiallySignedTransaction psbtx(rawTx);
|
||||||
|
|
||||||
// Fill transaction with out data and sign
|
// Fill transaction with our data and sign
|
||||||
bool complete = true;
|
bool complete = true;
|
||||||
const TransactionError err = pwallet->FillPSBT(psbtx, complete, SIGHASH_ALL, true, false);
|
const TransactionError err = pwallet->FillPSBT(psbtx, complete, SIGHASH_ALL, true, false);
|
||||||
if (err != TransactionError::OK) {
|
if (err != TransactionError::OK) {
|
||||||
@ -4148,13 +4149,11 @@ static UniValue send(const JSONRPCRequest& request)
|
|||||||
|
|
||||||
UniValue result(UniValue::VOBJ);
|
UniValue result(UniValue::VOBJ);
|
||||||
|
|
||||||
// Serialize the PSBT
|
|
||||||
CDataStream ssTx(SER_NETWORK, PROTOCOL_VERSION);
|
|
||||||
ssTx << psbtx;
|
|
||||||
const std::string result_str = EncodeBase64(ssTx.str());
|
|
||||||
|
|
||||||
if (psbt_opt_in || !complete || !add_to_wallet) {
|
if (psbt_opt_in || !complete || !add_to_wallet) {
|
||||||
result.pushKV("psbt", result_str);
|
// Serialize the PSBT
|
||||||
|
CDataStream ssTx(SER_NETWORK, PROTOCOL_VERSION);
|
||||||
|
ssTx << psbtx;
|
||||||
|
result.pushKV("psbt", EncodeBase64(ssTx.str()));
|
||||||
}
|
}
|
||||||
|
|
||||||
if (complete) {
|
if (complete) {
|
||||||
|
@ -29,9 +29,9 @@ class WalletSendTest(BitcoinTestFramework):
|
|||||||
|
|
||||||
def test_send(self, from_wallet, to_wallet=None, amount=None, data=None,
|
def test_send(self, from_wallet, to_wallet=None, amount=None, data=None,
|
||||||
arg_conf_target=None, arg_estimate_mode=None,
|
arg_conf_target=None, arg_estimate_mode=None,
|
||||||
conf_target=None, estimate_mode=None, add_to_wallet=None,psbt=None,
|
conf_target=None, estimate_mode=None, add_to_wallet=None, psbt=None,
|
||||||
inputs=None,add_inputs=None,change_address=None,change_position=None,
|
inputs=None, add_inputs=None, change_address=None, change_position=None,
|
||||||
include_watching=None,locktime=None,lock_unspents=None,subtract_fee_from_outputs=None,
|
include_watching=None, locktime=None, lock_unspents=None, subtract_fee_from_outputs=None,
|
||||||
expect_error=None):
|
expect_error=None):
|
||||||
assert (amount is None) != (data is None)
|
assert (amount is None) != (data is None)
|
||||||
|
|
||||||
@ -86,13 +86,13 @@ class WalletSendTest(BitcoinTestFramework):
|
|||||||
res = from_wallet.send(outputs=outputs, conf_target=arg_conf_target, estimate_mode=arg_estimate_mode, options=options)
|
res = from_wallet.send(outputs=outputs, conf_target=arg_conf_target, estimate_mode=arg_estimate_mode, options=options)
|
||||||
else:
|
else:
|
||||||
try:
|
try:
|
||||||
assert_raises_rpc_error(expect_error[0],expect_error[1],from_wallet.send,
|
assert_raises_rpc_error(expect_error[0], expect_error[1], from_wallet.send,
|
||||||
outputs=outputs,conf_target=arg_conf_target,estimate_mode=arg_estimate_mode,options=options)
|
outputs=outputs, conf_target=arg_conf_target, estimate_mode=arg_estimate_mode, options=options)
|
||||||
except AssertionError:
|
except AssertionError:
|
||||||
# Provide debug info if the test fails
|
# Provide debug info if the test fails
|
||||||
self.log.error("Unexpected successful result:")
|
self.log.error("Unexpected successful result:")
|
||||||
self.log.error(options)
|
self.log.error(options)
|
||||||
res = from_wallet.send(outputs=outputs,conf_target=arg_conf_target,estimate_mode=arg_estimate_mode,options=options)
|
res = from_wallet.send(outputs=outputs, conf_target=arg_conf_target, estimate_mode=arg_estimate_mode, options=options)
|
||||||
self.log.error(res)
|
self.log.error(res)
|
||||||
if "txid" in res and add_to_wallet:
|
if "txid" in res and add_to_wallet:
|
||||||
self.log.error("Transaction details:")
|
self.log.error("Transaction details:")
|
||||||
@ -124,7 +124,7 @@ class WalletSendTest(BitcoinTestFramework):
|
|||||||
tx = from_wallet.gettransaction(res["txid"])
|
tx = from_wallet.gettransaction(res["txid"])
|
||||||
assert tx
|
assert tx
|
||||||
# Ensure transaction exists in the mempool:
|
# Ensure transaction exists in the mempool:
|
||||||
tx = from_wallet.getrawtransaction(res["txid"],True)
|
tx = from_wallet.getrawtransaction(res["txid"], True)
|
||||||
assert tx
|
assert tx
|
||||||
if amount:
|
if amount:
|
||||||
if subtract_fee_from_outputs:
|
if subtract_fee_from_outputs:
|
||||||
@ -157,7 +157,7 @@ class WalletSendTest(BitcoinTestFramework):
|
|||||||
self.nodes[1].createwallet(wallet_name="w2")
|
self.nodes[1].createwallet(wallet_name="w2")
|
||||||
w2 = self.nodes[1].get_wallet_rpc("w2")
|
w2 = self.nodes[1].get_wallet_rpc("w2")
|
||||||
# w3 is a watch-only wallet, based on w2
|
# w3 is a watch-only wallet, based on w2
|
||||||
self.nodes[1].createwallet(wallet_name="w3",disable_private_keys=True)
|
self.nodes[1].createwallet(wallet_name="w3", disable_private_keys=True)
|
||||||
w3 = self.nodes[1].get_wallet_rpc("w3")
|
w3 = self.nodes[1].get_wallet_rpc("w3")
|
||||||
for _ in range(3):
|
for _ in range(3):
|
||||||
a2_receive = w2.getnewaddress()
|
a2_receive = w2.getnewaddress()
|
||||||
@ -181,7 +181,7 @@ class WalletSendTest(BitcoinTestFramework):
|
|||||||
self.sync_blocks()
|
self.sync_blocks()
|
||||||
|
|
||||||
# w4 has private keys enabled, but only contains watch-only keys (from w2)
|
# w4 has private keys enabled, but only contains watch-only keys (from w2)
|
||||||
self.nodes[1].createwallet(wallet_name="w4",disable_private_keys=False)
|
self.nodes[1].createwallet(wallet_name="w4", disable_private_keys=False)
|
||||||
w4 = self.nodes[1].get_wallet_rpc("w4")
|
w4 = self.nodes[1].get_wallet_rpc("w4")
|
||||||
for _ in range(3):
|
for _ in range(3):
|
||||||
a2_receive = w2.getnewaddress()
|
a2_receive = w2.getnewaddress()
|
||||||
@ -312,7 +312,8 @@ class WalletSendTest(BitcoinTestFramework):
|
|||||||
locked_coins = w0.listlockunspent()
|
locked_coins = w0.listlockunspent()
|
||||||
assert_equal(len(locked_coins), 1)
|
assert_equal(len(locked_coins), 1)
|
||||||
# Locked coins are automatically unlocked when manually selected
|
# Locked coins are automatically unlocked when manually selected
|
||||||
self.test_send(from_wallet=w0, to_wallet=w1, amount=1, inputs=[utxo1],add_to_wallet=False)
|
res = self.test_send(from_wallet=w0, to_wallet=w1, amount=1, inputs=[utxo1], add_to_wallet=False)
|
||||||
|
assert res["complete"]
|
||||||
|
|
||||||
self.log.info("Subtract fee from output")
|
self.log.info("Subtract fee from output")
|
||||||
self.test_send(from_wallet=w0, to_wallet=w1, amount=1, subtract_fee_from_outputs=[0])
|
self.test_send(from_wallet=w0, to_wallet=w1, amount=1, subtract_fee_from_outputs=[0])
|
||||||
|
Loading…
Reference in New Issue
Block a user