Merge #15899: rpc: Document iswitness flag and fix bug in converttopsbt

fa499b5f02 rpc: bugfix: Properly use iswitness in converttopsbt (MarcoFalke)
fa5c5cd141 rpc: Switch touched RPCs to IsValidNumArgs (MarcoFalke)

Pull request description:

  When a serialized transaction has inputs, there is no risk in only trying to deserialize it with witness allowed. (This is how all transactions from p2p are deserialized.) In fact, it would avoid a common issue where a transaction with inputs can be deserialized in two ways:
  * Fixes #12989
  * Fixes #15872
  * Fixes #15701
  * Fixes #13738
  * ...

  When a serialized transaction has no inputs, there is no risk in only trying to deserialze it with witness disallowed. (A transaction without inputs can't have corresponding witness data)

ACKs for commit fa499b:
  meshcollider:
    utACK fa499b5f02
  ryanofsky:
    utACK fa499b5f02. Changes since last review: consolidating commits and making iswitness documentation the same across methods.
  PastaPastaPasta:
    utACK fa499b5f02

Tree-SHA512: a64423a3131f3f0222a40da557c8b590c9ff01b45bcd40796f77a1a64ae74c6680a6be9d01ece95c492dfbcc7e2810409d2c2b336c2894af00bb213972fc85c6
This commit is contained in:
MeshCollider 2019-06-19 00:52:24 +12:00
commit 22b6c4ed75
No known key found for this signature in database
GPG key ID: D300116E1C875A3D
3 changed files with 43 additions and 24 deletions

View file

@ -426,14 +426,17 @@ static UniValue createrawtransaction(const JSONRPCRequest& request)
static UniValue decoderawtransaction(const JSONRPCRequest& request) static UniValue decoderawtransaction(const JSONRPCRequest& request)
{ {
if (request.fHelp || request.params.size() < 1 || request.params.size() > 2) const RPCHelpMan help{"decoderawtransaction",
throw std::runtime_error(
RPCHelpMan{"decoderawtransaction",
"\nReturn a JSON object representing the serialized, hex-encoded transaction.\n", "\nReturn a JSON object representing the serialized, hex-encoded transaction.\n",
{ {
{"hexstring", RPCArg::Type::STR_HEX, RPCArg::Optional::NO, "The transaction hex string"}, {"hexstring", RPCArg::Type::STR_HEX, RPCArg::Optional::NO, "The transaction hex string"},
{"iswitness", RPCArg::Type::BOOL, /* default */ "depends on heuristic tests", "Whether the transaction hex is a serialized witness transaction\n" {"iswitness", RPCArg::Type::BOOL, /* default */ "depends on heuristic tests", "Whether the transaction hex is a serialized witness transaction.\n"
" If iswitness is not present, heuristic tests will be used in decoding"}, "If iswitness is not present, heuristic tests will be used in decoding.\n"
"If true, only witness deserialization will be tried.\n"
"If false, only non-witness deserialization will be tried.\n"
"This boolean should reflect whether the transaction has inputs\n"
"(e.g. fully valid, or on-chain transactions), if known by the caller."
},
}, },
RPCResult{ RPCResult{
"{\n" "{\n"
@ -480,7 +483,11 @@ static UniValue decoderawtransaction(const JSONRPCRequest& request)
HelpExampleCli("decoderawtransaction", "\"hexstring\"") HelpExampleCli("decoderawtransaction", "\"hexstring\"")
+ HelpExampleRpc("decoderawtransaction", "\"hexstring\"") + HelpExampleRpc("decoderawtransaction", "\"hexstring\"")
}, },
}.ToString()); };
if (request.fHelp || !help.IsValidNumArgs(request.params.size())) {
throw std::runtime_error(help.ToString());
}
RPCTypeCheck(request.params, {UniValue::VSTR, UniValue::VBOOL}); RPCTypeCheck(request.params, {UniValue::VSTR, UniValue::VBOOL});
@ -1417,19 +1424,20 @@ UniValue createpsbt(const JSONRPCRequest& request)
UniValue converttopsbt(const JSONRPCRequest& request) UniValue converttopsbt(const JSONRPCRequest& request)
{ {
if (request.fHelp || request.params.size() < 1 || request.params.size() > 3) const RPCHelpMan help{"converttopsbt",
throw std::runtime_error(
RPCHelpMan{"converttopsbt",
"\nConverts a network serialized transaction to a PSBT. This should be used only with createrawtransaction and fundrawtransaction\n" "\nConverts a network serialized transaction to a PSBT. This should be used only with createrawtransaction and fundrawtransaction\n"
"createpsbt and walletcreatefundedpsbt should be used for new applications.\n", "createpsbt and walletcreatefundedpsbt should be used for new applications.\n",
{ {
{"hexstring", RPCArg::Type::STR_HEX, RPCArg::Optional::NO, "The hex string of a raw transaction"}, {"hexstring", RPCArg::Type::STR_HEX, RPCArg::Optional::NO, "The hex string of a raw transaction"},
{"permitsigdata", RPCArg::Type::BOOL, /* default */ "false", "If true, any signatures in the input will be discarded and conversion.\n" {"permitsigdata", RPCArg::Type::BOOL, /* default */ "false", "If true, any signatures in the input will be discarded and conversion\n"
" will continue. If false, RPC will fail if any signatures are present."}, " will continue. If false, RPC will fail if any signatures are present."},
{"iswitness", RPCArg::Type::BOOL, /* default */ "depends on heuristic tests", "Whether the transaction hex is a serialized witness transaction.\n" {"iswitness", RPCArg::Type::BOOL, /* default */ "depends on heuristic tests", "Whether the transaction hex is a serialized witness transaction.\n"
" If iswitness is not present, heuristic tests will be used in decoding. If true, only witness deserializaion\n" "If iswitness is not present, heuristic tests will be used in decoding.\n"
" will be tried. If false, only non-witness deserialization will be tried. Only has an effect if\n" "If true, only witness deserialization will be tried.\n"
" permitsigdata is true."}, "If false, only non-witness deserialization will be tried.\n"
"This boolean should reflect whether the transaction has inputs\n"
"(e.g. fully valid, or on-chain transactions), if known by the caller."
},
}, },
RPCResult{ RPCResult{
" \"psbt\" (string) The resulting raw transaction (base64-encoded string)\n" " \"psbt\" (string) The resulting raw transaction (base64-encoded string)\n"
@ -1440,8 +1448,11 @@ UniValue converttopsbt(const JSONRPCRequest& request)
"\nConvert the transaction to a PSBT\n" "\nConvert the transaction to a PSBT\n"
+ HelpExampleCli("converttopsbt", "\"rawtransaction\"") + HelpExampleCli("converttopsbt", "\"rawtransaction\"")
}, },
}.ToString()); };
if (request.fHelp || !help.IsValidNumArgs(request.params.size())) {
throw std::runtime_error(help.ToString());
}
RPCTypeCheck(request.params, {UniValue::VSTR, UniValue::VBOOL, UniValue::VBOOL}, true); RPCTypeCheck(request.params, {UniValue::VSTR, UniValue::VBOOL, UniValue::VBOOL}, true);
@ -1450,8 +1461,8 @@ UniValue converttopsbt(const JSONRPCRequest& request)
bool permitsigdata = request.params[1].isNull() ? false : request.params[1].get_bool(); bool permitsigdata = request.params[1].isNull() ? false : request.params[1].get_bool();
bool witness_specified = !request.params[2].isNull(); bool witness_specified = !request.params[2].isNull();
bool iswitness = witness_specified ? request.params[2].get_bool() : false; bool iswitness = witness_specified ? request.params[2].get_bool() : false;
bool try_witness = permitsigdata ? (witness_specified ? iswitness : true) : false; const bool try_witness = witness_specified ? iswitness : true;
bool try_no_witness = permitsigdata ? (witness_specified ? !iswitness : true) : true; const bool try_no_witness = witness_specified ? !iswitness : true;
if (!DecodeHexTx(tx, request.params[0].get_str(), try_no_witness, try_witness)) { if (!DecodeHexTx(tx, request.params[0].get_str(), try_no_witness, try_witness)) {
throw JSONRPCError(RPC_DESERIALIZATION_ERROR, "TX decode failed"); throw JSONRPCError(RPC_DESERIALIZATION_ERROR, "TX decode failed");
} }

View file

@ -3106,9 +3106,7 @@ static UniValue fundrawtransaction(const JSONRPCRequest& request)
return NullUniValue; return NullUniValue;
} }
if (request.fHelp || request.params.size() < 1 || request.params.size() > 3) const RPCHelpMan help{"fundrawtransaction",
throw std::runtime_error(
RPCHelpMan{"fundrawtransaction",
"\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"
"This will not modify existing inputs, and will add at most one change output to the outputs.\n" "This will not modify existing inputs, and will add at most one change output to the outputs.\n"
"No existing outputs will be modified unless \"subtractFeeFromOutputs\" is specified.\n" "No existing outputs will be modified unless \"subtractFeeFromOutputs\" is specified.\n"
@ -3147,8 +3145,13 @@ static UniValue fundrawtransaction(const JSONRPCRequest& request)
" \"CONSERVATIVE\""}, " \"CONSERVATIVE\""},
}, },
"options"}, "options"},
{"iswitness", RPCArg::Type::BOOL, /* default */ "depends on heuristic tests", "Whether the transaction hex is a serialized witness transaction \n" {"iswitness", RPCArg::Type::BOOL, /* default */ "depends on heuristic tests", "Whether the transaction hex is a serialized witness transaction.\n"
" If iswitness is not present, heuristic tests will be used in decoding"}, "If iswitness is not present, heuristic tests will be used in decoding.\n"
"If true, only witness deserialization will be tried.\n"
"If false, only non-witness deserialization will be tried.\n"
"This boolean should reflect whether the transaction has inputs\n"
"(e.g. fully valid, or on-chain transactions), if known by the caller."
},
}, },
RPCResult{ RPCResult{
"{\n" "{\n"
@ -3167,7 +3170,11 @@ static UniValue fundrawtransaction(const JSONRPCRequest& request)
"\nSend the transaction\n" "\nSend the transaction\n"
+ HelpExampleCli("sendrawtransaction", "\"signedtransactionhex\"") + HelpExampleCli("sendrawtransaction", "\"signedtransactionhex\"")
}, },
}.ToString()); };
if (request.fHelp || !help.IsValidNumArgs(request.params.size())) {
throw std::runtime_error(help.ToString());
}
RPCTypeCheck(request.params, {UniValue::VSTR, UniValueType(), UniValue::VBOOL}); RPCTypeCheck(request.params, {UniValue::VSTR, UniValueType(), UniValue::VBOOL});

View file

@ -152,9 +152,10 @@ class PSBTTest(BitcoinTestFramework):
# Make sure that a non-psbt with signatures cannot be converted # Make sure that a non-psbt with signatures cannot be converted
# Error could be either "TX decode failed" (segwit inputs causes parsing to fail) or "Inputs must not have scriptSigs and scriptWitnesses" # Error could be either "TX decode failed" (segwit inputs causes parsing to fail) or "Inputs must not have scriptSigs and scriptWitnesses"
# We must set iswitness=True because the serialized transaction has inputs and is therefore a witness transaction
signedtx = self.nodes[0].signrawtransactionwithwallet(rawtx['hex']) signedtx = self.nodes[0].signrawtransactionwithwallet(rawtx['hex'])
assert_raises_rpc_error(-22, "", self.nodes[0].converttopsbt, signedtx['hex']) assert_raises_rpc_error(-22, "", self.nodes[0].converttopsbt, hexstring=signedtx['hex'], iswitness=True)
assert_raises_rpc_error(-22, "", self.nodes[0].converttopsbt, signedtx['hex'], False) assert_raises_rpc_error(-22, "", self.nodes[0].converttopsbt, hexstring=signedtx['hex'], permitsigdata=False, iswitness=True)
# Unless we allow it to convert and strip signatures # Unless we allow it to convert and strip signatures
self.nodes[0].converttopsbt(signedtx['hex'], True) self.nodes[0].converttopsbt(signedtx['hex'], True)