aboutsummaryrefslogtreecommitdiff
path: root/src/rpc/rawtransaction.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'src/rpc/rawtransaction.cpp')
-rw-r--r--src/rpc/rawtransaction.cpp183
1 files changed, 115 insertions, 68 deletions
diff --git a/src/rpc/rawtransaction.cpp b/src/rpc/rawtransaction.cpp
index 7dd8abc193..8d9c207011 100644
--- a/src/rpc/rawtransaction.cpp
+++ b/src/rpc/rawtransaction.cpp
@@ -11,6 +11,7 @@
#include <core_io.h>
#include <index/txindex.h>
#include <init.h>
+#include <interfaces/chain.h>
#include <key_io.h>
#include <keystore.h>
#include <merkleblock.h>
@@ -28,6 +29,7 @@
#include <script/standard.h>
#include <uint256.h>
#include <util/bip32.h>
+#include <util/moneystr.h>
#include <util/strencodings.h>
#include <validation.h>
#include <validationinterface.h>
@@ -38,6 +40,11 @@
#include <univalue.h>
+/** High fee for sendrawtransaction and testmempoolaccept.
+ * By default, transaction with a fee higher than this will be rejected by the
+ * RPCs. This can be overriden with the maxfeerate argument.
+ */
+constexpr static CAmount DEFAULT_MAX_RAW_TX_FEE{COIN / 10};
static void TxToJSON(const CTransaction& tx, const uint256 hashBlock, UniValue& entry)
{
@@ -608,33 +615,54 @@ static UniValue decoderawtransaction(const JSONRPCRequest& request)
return result;
}
+static std::string GetAllOutputTypes()
+{
+ std::string ret;
+ for (int i = TX_NONSTANDARD; i <= TX_WITNESS_UNKNOWN; ++i) {
+ if (i != TX_NONSTANDARD) ret += ", ";
+ ret += GetTxnOutputType(static_cast<txnouttype>(i));
+ }
+ return ret;
+}
+
static UniValue decodescript(const JSONRPCRequest& request)
{
- if (request.fHelp || request.params.size() != 1)
- throw std::runtime_error(
- RPCHelpMan{"decodescript",
+ const RPCHelpMan help{"decodescript",
"\nDecode a hex-encoded script.\n",
{
{"hexstring", RPCArg::Type::STR_HEX, RPCArg::Optional::NO, "the hex-encoded script"},
},
RPCResult{
"{\n"
- " \"asm\":\"asm\", (string) Script public key\n"
- " \"hex\":\"hex\", (string) hex-encoded public key\n"
- " \"type\":\"type\", (string) The output type\n"
- " \"reqSigs\": n, (numeric) The required signatures\n"
- " \"addresses\": [ (json array of string)\n"
- " \"address\" (string) bitcoin address\n"
+ " \"asm\":\"asm\", (string) Script public key\n"
+ " \"type\":\"type\", (string) The output type (e.g. "+GetAllOutputTypes()+")\n"
+ " \"reqSigs\": n, (numeric) The required signatures\n"
+ " \"addresses\": [ (json array of string)\n"
+ " \"address\" (string) bitcoin address\n"
" ,...\n"
" ],\n"
- " \"p2sh\",\"address\" (string) address of P2SH script wrapping this redeem script (not returned if the script is already a P2SH).\n"
+ " \"p2sh\":\"str\" (string) address of P2SH script wrapping this redeem script (not returned if the script is already a P2SH).\n"
+ " \"segwit\": { (json object) Result of a witness script public key wrapping this redeem script (not returned if the script is a P2SH or witness).\n"
+ " \"asm\":\"str\", (string) String representation of the script public key\n"
+ " \"hex\":\"hexstr\", (string) Hex string of the script public key\n"
+ " \"type\":\"str\", (string) The type of the script public key (e.g. witness_v0_keyhash or witness_v0_scripthash)\n"
+ " \"reqSigs\": n, (numeric) The required signatures (always 1)\n"
+ " \"addresses\": [ (json array of string) (always length 1)\n"
+ " \"address\" (string) segwit address\n"
+ " ,...\n"
+ " ],\n"
+ " \"p2sh-segwit\":\"str\" (string) address of the P2SH script wrapping this witness redeem script.\n"
"}\n"
},
RPCExamples{
HelpExampleCli("decodescript", "\"hexstring\"")
+ HelpExampleRpc("decodescript", "\"hexstring\"")
},
- }.ToString());
+ };
+
+ if (request.fHelp || !help.IsValidNumArgs(request.params.size())) {
+ throw std::runtime_error(help.ToString());
+ }
RPCTypeCheck(request.params, {UniValue::VSTR});
@@ -646,7 +674,7 @@ static UniValue decodescript(const JSONRPCRequest& request)
} else {
// Empty scripts are valid
}
- ScriptPubKeyToUniv(script, r, false);
+ ScriptPubKeyToUniv(script, r, /* fIncludeHex */ false);
UniValue type;
type = find_value(r, "type");
@@ -680,7 +708,7 @@ static UniValue decodescript(const JSONRPCRequest& request)
// Newer segwit program versions should be considered when then become available.
segwitScr = GetScriptForDestination(WitnessV0ScriptHash(script));
}
- ScriptPubKeyToUniv(segwitScr, sr, true);
+ ScriptPubKeyToUniv(segwitScr, sr, /* fIncludeHex */ true);
sr.pushKV("p2sh-segwit", EncodeDestination(CScriptID(segwitScr)));
r.pushKV("segwit", sr);
}
@@ -790,23 +818,20 @@ static UniValue combinerawtransaction(const JSONRPCRequest& request)
return EncodeHexTx(CTransaction(mergedTx));
}
+// TODO(https://github.com/bitcoin/bitcoin/pull/10973#discussion_r267084237):
+// This function is called from both wallet and node rpcs
+// (signrawtransactionwithwallet and signrawtransactionwithkey). It should be
+// moved to a util file so wallet code doesn't need to link against node code.
+// Also the dependency on interfaces::Chain should be removed, so
+// signrawtransactionwithkey doesn't need access to a Chain instance.
UniValue SignTransaction(interfaces::Chain& chain, CMutableTransaction& mtx, const UniValue& prevTxsUnival, CBasicKeyStore *keystore, bool is_temp_keystore, const UniValue& hashType)
{
// Fetch previous transactions (inputs):
- CCoinsView viewDummy;
- CCoinsViewCache view(&viewDummy);
- {
- LOCK2(cs_main, mempool.cs);
- CCoinsViewCache &viewChain = *pcoinsTip;
- CCoinsViewMemPool viewMempool(&viewChain, mempool);
- view.SetBackend(viewMempool); // temporarily switch cache backend to db+mempool view
-
- for (const CTxIn& txin : mtx.vin) {
- view.AccessCoin(txin.prevout); // Load entries from viewChain into view; can fail.
- }
-
- view.SetBackend(viewDummy); // switch back to avoid locking mempool for too long
+ std::map<COutPoint, Coin> coins;
+ for (const CTxIn& txin : mtx.vin) {
+ coins[txin.prevout]; // Create empty map entry keyed by prevout.
}
+ chain.findCoins(coins);
// Add previous txouts given in the RPC call:
if (!prevTxsUnival.isNull()) {
@@ -838,10 +863,10 @@ UniValue SignTransaction(interfaces::Chain& chain, CMutableTransaction& mtx, con
CScript scriptPubKey(pkData.begin(), pkData.end());
{
- const Coin& coin = view.AccessCoin(out);
- if (!coin.IsSpent() && coin.out.scriptPubKey != scriptPubKey) {
+ auto coin = coins.find(out);
+ if (coin != coins.end() && !coin->second.IsSpent() && coin->second.out.scriptPubKey != scriptPubKey) {
std::string err("Previous output scriptPubKey mismatch:\n");
- err = err + ScriptToAsmStr(coin.out.scriptPubKey) + "\nvs:\n"+
+ err = err + ScriptToAsmStr(coin->second.out.scriptPubKey) + "\nvs:\n"+
ScriptToAsmStr(scriptPubKey);
throw JSONRPCError(RPC_DESERIALIZATION_ERROR, err);
}
@@ -852,7 +877,7 @@ UniValue SignTransaction(interfaces::Chain& chain, CMutableTransaction& mtx, con
newcoin.out.nValue = AmountFromValue(find_value(prevOut, "amount"));
}
newcoin.nHeight = 1;
- view.AddCoin(out, std::move(newcoin), true);
+ coins[out] = std::move(newcoin);
}
// if redeemScript and private keys were given, add redeemScript to the keystore so it can be signed
@@ -896,15 +921,15 @@ UniValue SignTransaction(interfaces::Chain& chain, CMutableTransaction& mtx, con
// Sign what we can:
for (unsigned int i = 0; i < mtx.vin.size(); i++) {
CTxIn& txin = mtx.vin[i];
- const Coin& coin = view.AccessCoin(txin.prevout);
- if (coin.IsSpent()) {
+ auto coin = coins.find(txin.prevout);
+ if (coin == coins.end() || coin->second.IsSpent()) {
TxInErrorToJSON(txin, vErrors, "Input not found or already spent");
continue;
}
- const CScript& prevPubKey = coin.out.scriptPubKey;
- const CAmount& amount = coin.out.nValue;
+ const CScript& prevPubKey = coin->second.out.scriptPubKey;
+ const CAmount& amount = coin->second.out.nValue;
- SignatureData sigdata = DataFromTransaction(mtx, i, coin.out);
+ SignatureData sigdata = DataFromTransaction(mtx, i, coin->second.out);
// Only sign SIGHASH_SINGLE if there's a corresponding output:
if (!fHashSingle || (i < mtx.vout.size())) {
ProduceSignature(*keystore, MutableTransactionSignatureCreator(&mtx, i, amount, nHashType), prevPubKey, sigdata);
@@ -914,7 +939,7 @@ UniValue SignTransaction(interfaces::Chain& chain, CMutableTransaction& mtx, con
// amount must be specified for valid segwit signature
if (amount == MAX_MONEY && !txin.scriptWitness.IsNull()) {
- throw JSONRPCError(RPC_TYPE_ERROR, strprintf("Missing amount for %s", coin.out.ToString()));
+ throw JSONRPCError(RPC_TYPE_ERROR, strprintf("Missing amount for %s", coin->second.out.ToString()));
}
ScriptError serror = SCRIPT_ERR_OK;
@@ -965,7 +990,7 @@ static UniValue signrawtransactionwithkey(const JSONRPCRequest& request)
{"scriptPubKey", RPCArg::Type::STR_HEX, RPCArg::Optional::NO, "script key"},
{"redeemScript", RPCArg::Type::STR_HEX, RPCArg::Optional::OMITTED, "(required for P2SH) redeem script"},
{"witnessScript", RPCArg::Type::STR_HEX, RPCArg::Optional::OMITTED, "(required for P2WSH or P2SH-P2WSH) witness script"},
- {"amount", RPCArg::Type::AMOUNT, RPCArg::Optional::NO, "The amount spent"},
+ {"amount", RPCArg::Type::AMOUNT, RPCArg::Optional::OMITTED, "(required for Segwit inputs) the amount spent"},
},
},
},
@@ -1022,24 +1047,14 @@ static UniValue signrawtransactionwithkey(const JSONRPCRequest& request)
return SignTransaction(*g_rpc_interfaces->chain, mtx, request.params[2], &keystore, true, request.params[3]);
}
-UniValue signrawtransaction(const JSONRPCRequest& request)
-{
- // This method should be removed entirely in V0.19, along with the entries in the
- // CRPCCommand table and rpc/client.cpp.
- throw JSONRPCError(RPC_METHOD_DEPRECATED, "signrawtransaction was removed in v0.18.\n"
- "Clients should transition to using signrawtransactionwithkey and signrawtransactionwithwallet");
-}
-
static UniValue sendrawtransaction(const JSONRPCRequest& request)
{
- if (request.fHelp || request.params.size() < 1 || request.params.size() > 2)
- throw std::runtime_error(
- RPCHelpMan{"sendrawtransaction",
+ const RPCHelpMan help{"sendrawtransaction",
"\nSubmits raw transaction (serialized, hex-encoded) to local node and network.\n"
"\nAlso see createrawtransaction and signrawtransactionwithkey calls.\n",
{
{"hexstring", RPCArg::Type::STR_HEX, RPCArg::Optional::NO, "The hex string of the raw transaction"},
- {"allowhighfees", RPCArg::Type::BOOL, /* default */ "false", "Allow high fees"},
+ {"maxfeerate", RPCArg::Type::AMOUNT, /* default */ FormatMoney(DEFAULT_MAX_RAW_TX_FEE), "Reject transactions whose fee rate is higher than the specified value, expressed in " + CURRENCY_UNIT + "/kB\n"},
},
RPCResult{
"\"hex\" (string) The transaction hash in hex\n"
@@ -1054,9 +1069,16 @@ static UniValue sendrawtransaction(const JSONRPCRequest& request)
"\nAs a JSON-RPC call\n"
+ HelpExampleRpc("sendrawtransaction", "\"signedhex\"")
},
- }.ToString());
+ };
- RPCTypeCheck(request.params, {UniValue::VSTR, UniValue::VBOOL});
+ if (request.fHelp || !help.IsValidNumArgs(request.params.size())) {
+ throw std::runtime_error(help.ToString());
+ }
+
+ RPCTypeCheck(request.params, {
+ UniValue::VSTR,
+ UniValueType(), // NUM or BOOL, checked later
+ });
// parse hex string from parameter
CMutableTransaction mtx;
@@ -1064,12 +1086,24 @@ static UniValue sendrawtransaction(const JSONRPCRequest& request)
throw JSONRPCError(RPC_DESERIALIZATION_ERROR, "TX decode failed");
CTransactionRef tx(MakeTransactionRef(std::move(mtx)));
- bool allowhighfees = false;
- if (!request.params[1].isNull()) allowhighfees = request.params[1].get_bool();
- const CAmount highfee{allowhighfees ? 0 : ::maxTxFee};
+ CAmount max_raw_tx_fee = DEFAULT_MAX_RAW_TX_FEE;
+ // TODO: temporary migration code for old clients. Remove in v0.20
+ if (request.params[1].isBool()) {
+ throw JSONRPCError(RPC_INVALID_PARAMETER, "Second argument must be numeric (maxfeerate) and no longer supports a boolean. To allow a transaction with high fees, set maxfeerate to 0.");
+ } else if (request.params[1].isNum()) {
+ size_t weight = GetTransactionWeight(*tx);
+ CFeeRate fr(AmountFromValue(request.params[1]));
+ // the +3/4 part rounds the value up, and is the same formula used when
+ // calculating the fee for a transaction
+ // (see GetVirtualTransactionSize)
+ max_raw_tx_fee = fr.GetFee((weight+3)/4);
+ } else if (!request.params[1].isNull()) {
+ throw JSONRPCError(RPC_INVALID_PARAMETER, "second argument (maxfeerate) must be numeric");
+ }
+
uint256 txid;
std::string err_string;
- const TransactionError err = BroadcastTransaction(tx, txid, err_string, highfee);
+ const TransactionError err = BroadcastTransaction(tx, txid, err_string, max_raw_tx_fee);
if (TransactionError::OK != err) {
throw JSONRPCTransactionError(err, err_string);
}
@@ -1079,9 +1113,7 @@ static UniValue sendrawtransaction(const JSONRPCRequest& request)
static UniValue testmempoolaccept(const JSONRPCRequest& request)
{
- if (request.fHelp || request.params.size() < 1 || request.params.size() > 2) {
- throw std::runtime_error(
- RPCHelpMan{"testmempoolaccept",
+ const RPCHelpMan help{"testmempoolaccept",
"\nReturns result of mempool acceptance tests indicating if raw transaction (serialized, hex-encoded) would be accepted by mempool.\n"
"\nThis checks if the transaction violates the consensus or policy rules.\n"
"\nSee sendrawtransaction call.\n",
@@ -1092,7 +1124,7 @@ static UniValue testmempoolaccept(const JSONRPCRequest& request)
{"rawtx", RPCArg::Type::STR_HEX, RPCArg::Optional::OMITTED, ""},
},
},
- {"allowhighfees", RPCArg::Type::BOOL, /* default */ "false", "Allow high fees"},
+ {"maxfeerate", RPCArg::Type::AMOUNT, /* default */ FormatMoney(DEFAULT_MAX_RAW_TX_FEE), "Reject transactions whose fee rate is higher than the specified value, expressed in " + CURRENCY_UNIT + "/kB\n"},
},
RPCResult{
"[ (array) The result of the mempool acceptance test for each raw transaction in the input array.\n"
@@ -1114,10 +1146,17 @@ static UniValue testmempoolaccept(const JSONRPCRequest& request)
"\nAs a JSON-RPC call\n"
+ HelpExampleRpc("testmempoolaccept", "[\"signedhex\"]")
},
- }.ToString());
+ };
+
+ if (request.fHelp || !help.IsValidNumArgs(request.params.size())) {
+ throw std::runtime_error(help.ToString());
}
- RPCTypeCheck(request.params, {UniValue::VARR, UniValue::VBOOL});
+ RPCTypeCheck(request.params, {
+ UniValue::VARR,
+ UniValueType(), // NUM or BOOL, checked later
+ });
+
if (request.params[0].get_array().size() != 1) {
throw JSONRPCError(RPC_INVALID_PARAMETER, "Array must contain exactly one raw transaction for now");
}
@@ -1129,9 +1168,19 @@ static UniValue testmempoolaccept(const JSONRPCRequest& request)
CTransactionRef tx(MakeTransactionRef(std::move(mtx)));
const uint256& tx_hash = tx->GetHash();
- CAmount max_raw_tx_fee = ::maxTxFee;
- if (!request.params[1].isNull() && request.params[1].get_bool()) {
- max_raw_tx_fee = 0;
+ CAmount max_raw_tx_fee = DEFAULT_MAX_RAW_TX_FEE;
+ // TODO: temporary migration code for old clients. Remove in v0.20
+ if (request.params[1].isBool()) {
+ throw JSONRPCError(RPC_INVALID_PARAMETER, "Second argument must be numeric (maxfeerate) and no longer supports a boolean. To allow a transaction with high fees, set maxfeerate to 0.");
+ } else if (request.params[1].isNum()) {
+ size_t weight = GetTransactionWeight(*tx);
+ CFeeRate fr(AmountFromValue(request.params[1]));
+ // the +3/4 part rounds the value up, and is the same formula used when
+ // calculating the fee for a transaction
+ // (see GetVirtualTransactionSize)
+ max_raw_tx_fee = fr.GetFee((weight+3)/4);
+ } else if (!request.params[1].isNull()) {
+ throw JSONRPCError(RPC_INVALID_PARAMETER, "second argument (maxfeerate) must be numeric");
}
UniValue result(UniValue::VARR);
@@ -1500,7 +1549,6 @@ UniValue combinepsbt(const JSONRPCRequest& request)
throw JSONRPCTransactionError(error);
}
- UniValue result(UniValue::VOBJ);
CDataStream ssTx(SER_NETWORK, PROTOCOL_VERSION);
ssTx << merged_psbt;
return EncodeBase64((unsigned char*)ssTx.data(), ssTx.size());
@@ -1956,11 +2004,10 @@ static const CRPCCommand commands[] =
{ "rawtransactions", "createrawtransaction", &createrawtransaction, {"inputs","outputs","locktime","replaceable"} },
{ "rawtransactions", "decoderawtransaction", &decoderawtransaction, {"hexstring","iswitness"} },
{ "rawtransactions", "decodescript", &decodescript, {"hexstring"} },
- { "rawtransactions", "sendrawtransaction", &sendrawtransaction, {"hexstring","allowhighfees"} },
+ { "rawtransactions", "sendrawtransaction", &sendrawtransaction, {"hexstring","allowhighfees|maxfeerate"} },
{ "rawtransactions", "combinerawtransaction", &combinerawtransaction, {"txs"} },
- { "hidden", "signrawtransaction", &signrawtransaction, {"hexstring","prevtxs","privkeys","sighashtype"} },
{ "rawtransactions", "signrawtransactionwithkey", &signrawtransactionwithkey, {"hexstring","privkeys","prevtxs","sighashtype"} },
- { "rawtransactions", "testmempoolaccept", &testmempoolaccept, {"rawtxs","allowhighfees"} },
+ { "rawtransactions", "testmempoolaccept", &testmempoolaccept, {"rawtxs","allowhighfees|maxfeerate"} },
{ "rawtransactions", "decodepsbt", &decodepsbt, {"psbt"} },
{ "rawtransactions", "combinepsbt", &combinepsbt, {"txs"} },
{ "rawtransactions", "finalizepsbt", &finalizepsbt, {"psbt", "extract"} },