Page Menu
Home
Phabricator
Search
Configure Global Search
Log In
Files
F13612285
No One
Temporary
Actions
View File
Edit File
Delete File
View Transforms
Subscribe
Mute Notifications
Award Token
Flag For Later
Size
83 KB
Subscribers
None
View Options
diff --git a/src/node/transaction.h b/src/node/transaction.h
index 098429597..2f88e716a 100644
--- a/src/node/transaction.h
+++ b/src/node/transaction.h
@@ -1,41 +1,51 @@
// Copyright (c) 2017-2019 The Bitcoin Core developers
// Distributed under the MIT software license, see the accompanying
// file COPYING or http://www.opensource.org/licenses/mit-license.php.
#ifndef BITCOIN_NODE_TRANSACTION_H
#define BITCOIN_NODE_TRANSACTION_H
#include <attributes.h>
+#include <feerate.h>
#include <primitives/transaction.h>
#include <util/error.h>
class Config;
struct NodeContext;
struct TxId;
+/**
+ * Maximum fee rate for sendrawtransaction and testmempoolaccept RPC calls.
+ * Also used by the GUI when broadcasting a completed PSBT.
+ * By default, a transaction with a fee rate higher than this will be rejected
+ * by these RPCs and the GUI. This can be overridden with the maxfeerate
+ * argument.
+ */
+static const CFeeRate DEFAULT_MAX_RAW_TX_FEE_RATE{COIN / 10};
+
/**
* Submit a transaction to the mempool and (optionally) relay it to all P2P
* peers.
*
* Mempool submission can be synchronous (will await mempool entry notification
* over the CValidationInterface) or asynchronous (will submit and not wait for
* notification), depending on the value of wait_callback. wait_callback MUST
* NOT be set while cs_main, cs_mempool or cs_wallet are held to avoid
* deadlock.
*
* @param[in] node reference to node context
* @param[in] tx the transaction to broadcast
* @param[out] &err_string reference to std::string to fill with error string
* if available
* @param[in] max_tx_fee reject txs with fees higher than this (if 0, accept
* any fee)
* @param[in] relay flag if both mempool insertion and p2p relay are requested
* @param[in] wait_callback, wait until callbacks have been processed to avoid
* stale result due to a sequentially RPC.
* @return error
*/
NODISCARD TransactionError BroadcastTransaction(
NodeContext &node, const Config &config, CTransactionRef tx,
std::string &err_string, Amount max_tx_fee, bool relay, bool wait_callback);
#endif // BITCOIN_NODE_TRANSACTION_H
diff --git a/src/rpc/rawtransaction.cpp b/src/rpc/rawtransaction.cpp
index 410c2c1f8..1e921ac9a 100644
--- a/src/rpc/rawtransaction.cpp
+++ b/src/rpc/rawtransaction.cpp
@@ -1,2027 +1,2020 @@
// Copyright (c) 2010 Satoshi Nakamoto
// Copyright (c) 2009-2016 The Bitcoin Core developers
// Distributed under the MIT software license, see the accompanying
// file COPYING or http://www.opensource.org/licenses/mit-license.php.
#include <blockdb.h>
#include <chain.h>
#include <chainparams.h>
#include <coins.h>
#include <config.h>
#include <consensus/validation.h>
#include <core_io.h>
#include <index/txindex.h>
#include <key_io.h>
#include <merkleblock.h>
#include <node/coin.h>
#include <node/context.h>
#include <node/psbt.h>
#include <node/transaction.h>
#include <policy/policy.h>
#include <primitives/transaction.h>
#include <psbt.h>
#include <random.h>
#include <rpc/blockchain.h>
#include <rpc/rawtransaction_util.h>
#include <rpc/server.h>
#include <rpc/util.h>
#include <script/script.h>
#include <script/sign.h>
#include <script/signingprovider.h>
#include <script/standard.h>
#include <txmempool.h>
#include <uint256.h>
#include <util/error.h>
#include <util/moneystr.h>
#include <util/strencodings.h>
#include <validation.h>
#include <validationinterface.h>
#include <cstdint>
#include <numeric>
#include <univalue.h>
-/**
- * Maximum fee rate for sendrawtransaction and testmempoolaccept.
- * By default, a transaction with a fee rate higher than this will be rejected
- * by the RPCs. This can be overridden with the maxfeerate argument.
- */
-static const CFeeRate DEFAULT_MAX_RAW_TX_FEE_RATE{COIN / 10};
-
static void TxToJSON(const CTransaction &tx, const BlockHash &hashBlock,
UniValue &entry) {
// Call into TxToUniv() in bitcoin-common to decode the transaction hex.
//
// Blockchain contextual information (confirmations and blocktime) is not
// available to code in bitcoin-common, so we query them here and push the
// data into the returned UniValue.
TxToUniv(tx, uint256(), entry, true, RPCSerializationFlags());
if (!hashBlock.IsNull()) {
LOCK(cs_main);
entry.pushKV("blockhash", hashBlock.GetHex());
CBlockIndex *pindex = LookupBlockIndex(hashBlock);
if (pindex) {
if (::ChainActive().Contains(pindex)) {
entry.pushKV("confirmations",
1 + ::ChainActive().Height() - pindex->nHeight);
entry.pushKV("time", pindex->GetBlockTime());
entry.pushKV("blocktime", pindex->GetBlockTime());
} else {
entry.pushKV("confirmations", 0);
}
}
}
}
static UniValue getrawtransaction(const Config &config,
const JSONRPCRequest &request) {
RPCHelpMan{
"getrawtransaction",
"By default this function only works for mempool transactions. When "
"called with a blockhash\n"
"argument, getrawtransaction will return the transaction if the "
"specified block is available and\n"
"the transaction is found in that block. When called without a "
"blockhash argument, getrawtransaction\n"
"will return the transaction if it is in the mempool, or if -txindex "
"is enabled and the transaction\n"
"is in a block in the blockchain.\n"
"\nReturn the raw transaction data.\n"
"\nIf verbose is 'true', returns an Object with information about "
"'txid'.\n"
"If verbose is 'false' or omitted, returns a string that is "
"serialized, hex-encoded data for 'txid'.\n",
{
{"txid", RPCArg::Type::STR_HEX, RPCArg::Optional::NO,
"The transaction id"},
{"verbose", RPCArg::Type::BOOL, /* default */ "false",
"If false, return a string, otherwise return a json object"},
{"blockhash", RPCArg::Type::STR_HEX,
RPCArg::Optional::OMITTED_NAMED_ARG,
"The block in which to look for the transaction"},
},
{
RPCResult{"if verbose is not set or set to false",
"\"data\" (string) The serialized, hex-encoded data "
"for 'txid'\n"},
RPCResult{
"if verbose is set to true",
"{\n"
" \"in_active_chain\": b, (bool) Whether specified block is "
"in the active chain or not (only present with explicit "
"\"blockhash\" argument)\n"
" \"hex\" : \"data\", (string) The serialized, "
"hex-encoded data for 'txid'\n"
" \"txid\" : \"id\", (string) The transaction id (same "
"as provided)\n"
" \"hash\" : \"id\", (string) The transaction hash "
"(differs from txid for witness transactions)\n"
" \"size\" : n, (numeric) The serialized "
"transaction size\n"
" \"version\" : n, (numeric) The version\n"
" \"locktime\" : ttt, (numeric) The lock time\n"
" \"vin\" : [ (array of json objects)\n"
" {\n"
" \"txid\": \"id\", (string) The transaction id\n"
" \"vout\": n, (numeric) \n"
" \"scriptSig\": { (json object) The script\n"
" \"asm\": \"asm\", (string) asm\n"
" \"hex\": \"hex\" (string) hex\n"
" },\n"
" \"sequence\": n (numeric) The script sequence "
"number\n"
" }\n"
" ,...\n"
" ],\n"
" \"vout\" : [ (array of json objects)\n"
" {\n"
" \"value\" : x.xxx, (numeric) The value in " +
CURRENCY_UNIT +
"\n"
" \"n\" : n, (numeric) index\n"
" \"scriptPubKey\" : { (json object)\n"
" \"asm\" : \"asm\", (string) the asm\n"
" \"hex\" : \"hex\", (string) the hex\n"
" \"reqSigs\" : n, (numeric) The "
"required sigs\n"
" \"type\" : \"pubkeyhash\", (string) The type, "
"eg 'pubkeyhash'\n"
" \"addresses\" : [ (json array of "
"string)\n"
" \"address\" (string) bitcoin address\n"
" ,...\n"
" ]\n"
" }\n"
" }\n"
" ,...\n"
" ],\n"
" \"blockhash\" : \"hash\", (string) the block hash\n"
" \"confirmations\" : n, (numeric) The "
"confirmations\n"
" \"blocktime\" : ttt (numeric) The block time "
"expressed in " +
UNIX_EPOCH_TIME +
"\n"
" \"time\" : ttt, (numeric) Same as "
"\"blocktime\"\n"
"}\n"},
},
RPCExamples{HelpExampleCli("getrawtransaction", "\"mytxid\"") +
HelpExampleCli("getrawtransaction", "\"mytxid\" true") +
HelpExampleRpc("getrawtransaction", "\"mytxid\", true") +
HelpExampleCli("getrawtransaction",
"\"mytxid\" false \"myblockhash\"") +
HelpExampleCli("getrawtransaction",
"\"mytxid\" true \"myblockhash\"")},
}
.Check(request);
bool in_active_chain = true;
TxId txid = TxId(ParseHashV(request.params[0], "parameter 1"));
CBlockIndex *blockindex = nullptr;
const CChainParams ¶ms = config.GetChainParams();
if (txid == params.GenesisBlock().hashMerkleRoot) {
// Special exception for the genesis block coinbase transaction
throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY,
"The genesis block coinbase is not considered an "
"ordinary transaction and cannot be retrieved");
}
// Accept either a bool (true) or a num (>=1) to indicate verbose output.
bool fVerbose = false;
if (!request.params[1].isNull()) {
fVerbose = request.params[1].isNum()
? (request.params[1].get_int() != 0)
: request.params[1].get_bool();
}
if (!request.params[2].isNull()) {
LOCK(cs_main);
BlockHash blockhash(ParseHashV(request.params[2], "parameter 3"));
blockindex = LookupBlockIndex(blockhash);
if (!blockindex) {
throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY,
"Block hash not found");
}
in_active_chain = ::ChainActive().Contains(blockindex);
}
bool f_txindex_ready = false;
if (g_txindex && !blockindex) {
f_txindex_ready = g_txindex->BlockUntilSyncedToCurrentChain();
}
CTransactionRef tx;
BlockHash hash_block;
if (!GetTransaction(txid, tx, params.GetConsensus(), hash_block,
blockindex)) {
std::string errmsg;
if (blockindex) {
if (!blockindex->nStatus.hasData()) {
throw JSONRPCError(RPC_MISC_ERROR, "Block not available");
}
errmsg = "No such transaction found in the provided block";
} else if (!g_txindex) {
errmsg = "No such mempool transaction. Use -txindex to enable "
"blockchain transaction queries";
} else if (!f_txindex_ready) {
errmsg = "No such mempool transaction. Blockchain transactions are "
"still in the process of being indexed";
} else {
errmsg = "No such mempool or blockchain transaction";
}
throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY,
errmsg +
". Use gettransaction for wallet transactions.");
}
if (!fVerbose) {
return EncodeHexTx(*tx, RPCSerializationFlags());
}
UniValue result(UniValue::VOBJ);
if (blockindex) {
result.pushKV("in_active_chain", in_active_chain);
}
TxToJSON(*tx, hash_block, result);
return result;
}
static UniValue gettxoutproof(const Config &config,
const JSONRPCRequest &request) {
RPCHelpMan{
"gettxoutproof",
"Returns a hex-encoded proof that \"txid\" was included in a block.\n"
"\nNOTE: By default this function only works sometimes. "
"This is when there is an\n"
"unspent output in the utxo for this transaction. To make it always "
"work,\n"
"you need to maintain a transaction index, using the -txindex command "
"line option or\n"
"specify the block in which the transaction is included manually (by "
"blockhash).\n",
{
{
"txids",
RPCArg::Type::ARR,
RPCArg::Optional::NO,
"A json array of txids to filter",
{
{"txid", RPCArg::Type::STR_HEX, RPCArg::Optional::OMITTED,
"A transaction hash"},
},
},
{"blockhash", RPCArg::Type::STR_HEX,
RPCArg::Optional::OMITTED_NAMED_ARG,
"If specified, looks for txid in the block with this hash"},
},
RPCResult{"\"data\" (string) A string that is a serialized, "
"hex-encoded data for the proof.\n"},
RPCExamples{""},
}
.Check(request);
std::set<TxId> setTxIds;
TxId oneTxId;
UniValue txids = request.params[0].get_array();
for (unsigned int idx = 0; idx < txids.size(); idx++) {
const UniValue &utxid = txids[idx];
TxId txid(ParseHashV(utxid, "txid"));
if (setTxIds.count(txid)) {
throw JSONRPCError(
RPC_INVALID_PARAMETER,
std::string("Invalid parameter, duplicated txid: ") +
utxid.get_str());
}
setTxIds.insert(txid);
oneTxId = txid;
}
CBlockIndex *pblockindex = nullptr;
BlockHash hashBlock;
if (!request.params[1].isNull()) {
LOCK(cs_main);
hashBlock = BlockHash(ParseHashV(request.params[1], "blockhash"));
pblockindex = LookupBlockIndex(hashBlock);
if (!pblockindex) {
throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Block not found");
}
} else {
LOCK(cs_main);
// Loop through txids and try to find which block they're in. Exit loop
// once a block is found.
for (const auto &txid : setTxIds) {
const Coin &coin =
AccessByTxid(::ChainstateActive().CoinsTip(), txid);
if (!coin.IsSpent()) {
pblockindex = ::ChainActive()[coin.GetHeight()];
break;
}
}
}
// Allow txindex to catch up if we need to query it and before we acquire
// cs_main.
if (g_txindex && !pblockindex) {
g_txindex->BlockUntilSyncedToCurrentChain();
}
const Consensus::Params ¶ms = config.GetChainParams().GetConsensus();
LOCK(cs_main);
if (pblockindex == nullptr) {
CTransactionRef tx;
if (!GetTransaction(oneTxId, tx, params, hashBlock) ||
hashBlock.IsNull()) {
throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY,
"Transaction not yet in block");
}
pblockindex = LookupBlockIndex(hashBlock);
if (!pblockindex) {
throw JSONRPCError(RPC_INTERNAL_ERROR, "Transaction index corrupt");
}
}
CBlock block;
if (!ReadBlockFromDisk(block, pblockindex, params)) {
throw JSONRPCError(RPC_INTERNAL_ERROR, "Can't read block from disk");
}
unsigned int ntxFound = 0;
for (const auto &tx : block.vtx) {
if (setTxIds.count(tx->GetId())) {
ntxFound++;
}
}
if (ntxFound != setTxIds.size()) {
throw JSONRPCError(
RPC_INVALID_ADDRESS_OR_KEY,
"Not all transactions found in specified or retrieved block");
}
CDataStream ssMB(SER_NETWORK, PROTOCOL_VERSION);
CMerkleBlock mb(block, setTxIds);
ssMB << mb;
std::string strHex = HexStr(ssMB.begin(), ssMB.end());
return strHex;
}
static UniValue verifytxoutproof(const Config &config,
const JSONRPCRequest &request) {
RPCHelpMan{
"verifytxoutproof",
"Verifies that a proof points to a transaction in a block, returning "
"the transaction it commits to\n"
"and throwing an RPC error if the block is not in our best chain\n",
{
{"proof", RPCArg::Type::STR_HEX, RPCArg::Optional::NO,
"The hex-encoded proof generated by gettxoutproof"},
},
RPCResult{
"[\"txid\"] (array, strings) The txid(s) which the proof "
"commits to, or empty array if the proof can not be validated.\n"},
RPCExamples{""},
}
.Check(request);
CDataStream ssMB(ParseHexV(request.params[0], "proof"), SER_NETWORK,
PROTOCOL_VERSION);
CMerkleBlock merkleBlock;
ssMB >> merkleBlock;
UniValue res(UniValue::VARR);
std::vector<uint256> vMatch;
std::vector<size_t> vIndex;
if (merkleBlock.txn.ExtractMatches(vMatch, vIndex) !=
merkleBlock.header.hashMerkleRoot) {
return res;
}
LOCK(cs_main);
const CBlockIndex *pindex = LookupBlockIndex(merkleBlock.header.GetHash());
if (!pindex || !::ChainActive().Contains(pindex) || pindex->nTx == 0) {
throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY,
"Block not found in chain");
}
// Check if proof is valid, only add results if so
if (pindex->nTx == merkleBlock.txn.GetNumTransactions()) {
for (const uint256 &hash : vMatch) {
res.push_back(hash.GetHex());
}
}
return res;
}
static UniValue createrawtransaction(const Config &config,
const JSONRPCRequest &request) {
RPCHelpMan{
"createrawtransaction",
"Create a transaction spending the given inputs and creating new "
"outputs.\n"
"Outputs can be addresses or data.\n"
"Returns hex-encoded raw transaction.\n"
"Note that the transaction's inputs are not signed, and\n"
"it is not stored in the wallet or transmitted to the network.\n",
{
{
"inputs",
RPCArg::Type::ARR,
RPCArg::Optional::NO,
"A json array of json objects",
{
{
"",
RPCArg::Type::OBJ,
RPCArg::Optional::OMITTED,
"",
{
{"txid", RPCArg::Type::STR_HEX,
RPCArg::Optional::NO, "The transaction id"},
{"vout", RPCArg::Type::NUM, RPCArg::Optional::NO,
"The output number"},
{"sequence", RPCArg::Type::NUM, /* default */
"depends on the value of the 'locktime' argument",
"The sequence number"},
},
},
},
},
{
"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"
"For compatibility reasons, a dictionary, which holds the "
"key-value pairs directly, is also\n"
" accepted as second parameter.",
{
{
"",
RPCArg::Type::OBJ,
RPCArg::Optional::OMITTED,
"",
{
{"address", RPCArg::Type::AMOUNT,
RPCArg::Optional::NO,
"A key-value pair. The key (string) is the "
"bitcoin address, the value (float or string) is "
"the amount in " +
CURRENCY_UNIT},
},
},
{
"",
RPCArg::Type::OBJ,
RPCArg::Optional::OMITTED,
"",
{
{"data", RPCArg::Type::STR_HEX,
RPCArg::Optional::NO,
"A key-value pair. The key must be \"data\", the "
"value is hex-encoded data"},
},
},
},
},
{"locktime", RPCArg::Type::NUM, /* default */ "0",
"Raw locktime. Non-0 value also locktime-activates inputs"},
},
RPCResult{"\"transaction\" (string) hex string of the "
"transaction\n"},
RPCExamples{
HelpExampleCli("createrawtransaction",
"\"[{\\\"txid\\\":\\\"myid\\\",\\\"vout\\\":0}]"
"\" \"[{\\\"address\\\":0.01}]\"") +
HelpExampleCli("createrawtransaction",
"\"[{\\\"txid\\\":\\\"myid\\\",\\\"vout\\\":0}]"
"\" \"[{\\\"data\\\":\\\"00010203\\\"}]\"") +
HelpExampleRpc("createrawtransaction",
"\"[{\\\"txid\\\":\\\"myid\\\",\\\"vout\\\":0}]"
"\", \"[{\\\"address\\\":0.01}]\"") +
HelpExampleRpc("createrawtransaction",
"\"[{\\\"txid\\\":\\\"myid\\\",\\\"vout\\\":0}]"
"\", \"[{\\\"data\\\":\\\"00010203\\\"}]\"")},
}
.Check(request);
RPCTypeCheck(request.params,
{UniValue::VARR,
UniValueType(), // ARR or OBJ, checked later
UniValue::VNUM},
true);
CMutableTransaction rawTx =
ConstructTransaction(config.GetChainParams(), request.params[0],
request.params[1], request.params[2]);
return EncodeHexTx(CTransaction(rawTx));
}
static UniValue decoderawtransaction(const Config &config,
const JSONRPCRequest &request) {
RPCHelpMan{
"decoderawtransaction",
"Return a JSON object representing the serialized, hex-encoded "
"transaction.\n",
{
{"hexstring", RPCArg::Type::STR_HEX, RPCArg::Optional::NO,
"The transaction hex string"},
},
RPCResult{
"{\n"
" \"txid\" : \"id\", (string) The transaction id\n"
" \"hash\" : \"id\", (string) The transaction hash "
"(differs from txid for witness transactions)\n"
" \"size\" : n, (numeric) The transaction size\n"
" \"version\" : n, (numeric) The version\n"
" \"locktime\" : ttt, (numeric) The lock time\n"
" \"vin\" : [ (array of json objects)\n"
" {\n"
" \"txid\": \"id\", (string) The transaction id\n"
" \"vout\": n, (numeric) The output number\n"
" \"scriptSig\": { (json object) The script\n"
" \"asm\": \"asm\", (string) asm\n"
" \"hex\": \"hex\" (string) hex\n"
" },\n"
" \"sequence\": n (numeric) The script sequence number\n"
" }\n"
" ,...\n"
" ],\n"
" \"vout\" : [ (array of json objects)\n"
" {\n"
" \"value\" : x.xxx, (numeric) The value in " +
CURRENCY_UNIT +
"\n"
" \"n\" : n, (numeric) index\n"
" \"scriptPubKey\" : { (json object)\n"
" \"asm\" : \"asm\", (string) the asm\n"
" \"hex\" : \"hex\", (string) the hex\n"
" \"reqSigs\" : n, (numeric) The required sigs\n"
" \"type\" : \"pubkeyhash\", (string) The type, eg "
"'pubkeyhash'\n"
" \"addresses\" : [ (json array of string)\n"
" \"12tvKAXCxZjSmdNbao16dKXC8tRWfcF5oc\" (string) "
"bitcoin address\n"
" ,...\n"
" ]\n"
" }\n"
" }\n"
" ,...\n"
" ],\n"
"}\n"},
RPCExamples{HelpExampleCli("decoderawtransaction", "\"hexstring\"") +
HelpExampleRpc("decoderawtransaction", "\"hexstring\"")},
}
.Check(request);
RPCTypeCheck(request.params, {UniValue::VSTR});
CMutableTransaction mtx;
if (!DecodeHexTx(mtx, request.params[0].get_str())) {
throw JSONRPCError(RPC_DESERIALIZATION_ERROR, "TX decode failed");
}
UniValue result(UniValue::VOBJ);
TxToUniv(CTransaction(std::move(mtx)), uint256(), result, false);
return result;
}
static UniValue decodescript(const Config &config,
const JSONRPCRequest &request) {
RPCHelpMan{
"decodescript",
"Decode 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"
" ,...\n"
" ],\n"
" \"p2sh\",\"address\" (string) address of P2SH script "
"wrapping this redeem script (not returned if the script is "
"already a P2SH).\n"
"}\n"},
RPCExamples{HelpExampleCli("decodescript", "\"hexstring\"") +
HelpExampleRpc("decodescript", "\"hexstring\"")},
}
.Check(request);
RPCTypeCheck(request.params, {UniValue::VSTR});
UniValue r(UniValue::VOBJ);
CScript script;
if (request.params[0].get_str().size() > 0) {
std::vector<uint8_t> scriptData(
ParseHexV(request.params[0], "argument"));
script = CScript(scriptData.begin(), scriptData.end());
} else {
// Empty scripts are valid.
}
ScriptPubKeyToUniv(script, r, false);
UniValue type;
type = find_value(r, "type");
if (type.isStr() && type.get_str() != "scripthash") {
// P2SH cannot be wrapped in a P2SH. If this script is already a P2SH,
// don't return the address for a P2SH of the P2SH.
r.pushKV("p2sh", EncodeDestination(ScriptHash(script), config));
}
return r;
}
static UniValue combinerawtransaction(const Config &config,
const JSONRPCRequest &request) {
RPCHelpMan{
"combinerawtransaction",
"Combine multiple partially signed transactions into one "
"transaction.\n"
"The combined transaction may be another partially signed transaction "
"or a \n"
"fully signed transaction.",
{
{
"txs",
RPCArg::Type::ARR,
RPCArg::Optional::NO,
"A json array of hex strings of partially signed "
"transactions",
{
{"hexstring", RPCArg::Type::STR_HEX,
RPCArg::Optional::OMITTED, "A transaction hash"},
},
},
},
RPCResult{"\"hex\" (string) The hex-encoded raw transaction "
"with signature(s)\n"},
RPCExamples{HelpExampleCli("combinerawtransaction",
"[\"myhex1\", \"myhex2\", \"myhex3\"]")},
}
.Check(request);
UniValue txs = request.params[0].get_array();
std::vector<CMutableTransaction> txVariants(txs.size());
for (unsigned int idx = 0; idx < txs.size(); idx++) {
if (!DecodeHexTx(txVariants[idx], txs[idx].get_str())) {
throw JSONRPCError(RPC_DESERIALIZATION_ERROR,
strprintf("TX decode failed for tx %d", idx));
}
}
if (txVariants.empty()) {
throw JSONRPCError(RPC_DESERIALIZATION_ERROR, "Missing transactions");
}
// mergedTx will end up with all the signatures; it
// starts as a clone of the rawtx:
CMutableTransaction mergedTx(txVariants[0]);
// Fetch previous transactions (inputs):
CCoinsView viewDummy;
CCoinsViewCache view(&viewDummy);
{
const CTxMemPool &mempool = EnsureMemPool(request.context);
LOCK(cs_main);
LOCK(mempool.cs);
CCoinsViewCache &viewChain = ::ChainstateActive().CoinsTip();
CCoinsViewMemPool viewMempool(&viewChain, mempool);
// temporarily switch cache backend to db+mempool view
view.SetBackend(viewMempool);
for (const CTxIn &txin : mergedTx.vin) {
// Load entries from viewChain into view; can fail.
view.AccessCoin(txin.prevout);
}
// switch back to avoid locking mempool for too long
view.SetBackend(viewDummy);
}
// Use CTransaction for the constant parts of the
// transaction to avoid rehashing.
const CTransaction txConst(mergedTx);
// Sign what we can:
for (size_t i = 0; i < mergedTx.vin.size(); i++) {
CTxIn &txin = mergedTx.vin[i];
const Coin &coin = view.AccessCoin(txin.prevout);
if (coin.IsSpent()) {
throw JSONRPCError(RPC_VERIFY_ERROR,
"Input not found or already spent");
}
SignatureData sigdata;
const CTxOut &txout = coin.GetTxOut();
// ... and merge in other signatures:
for (const CMutableTransaction &txv : txVariants) {
if (txv.vin.size() > i) {
sigdata.MergeSignatureData(DataFromTransaction(txv, i, txout));
}
}
ProduceSignature(
DUMMY_SIGNING_PROVIDER,
MutableTransactionSignatureCreator(&mergedTx, i, txout.nValue),
txout.scriptPubKey, sigdata);
UpdateInput(txin, sigdata);
}
return EncodeHexTx(CTransaction(mergedTx));
}
static UniValue signrawtransactionwithkey(const Config &config,
const JSONRPCRequest &request) {
RPCHelpMan{
"signrawtransactionwithkey",
"Sign inputs for raw transaction (serialized, hex-encoded).\n"
"The second argument is an array of base58-encoded private\n"
"keys that will be the only keys used to sign the transaction.\n"
"The third optional argument (may be null) is an array of previous "
"transaction outputs that\n"
"this transaction depends on but may not yet be in the block chain.\n",
{
{"hexstring", RPCArg::Type::STR_HEX, RPCArg::Optional::NO,
"The transaction hex string"},
{
"privkeys",
RPCArg::Type::ARR,
RPCArg::Optional::NO,
"A json array of base58-encoded private keys for signing",
{
{"privatekey", RPCArg::Type::STR, RPCArg::Optional::OMITTED,
"private key in base58-encoding"},
},
},
{
"prevtxs",
RPCArg::Type::ARR,
RPCArg::Optional::OMITTED_NAMED_ARG,
"A json array of previous dependent transaction outputs",
{
{
"",
RPCArg::Type::OBJ,
RPCArg::Optional::OMITTED,
"",
{
{"txid", RPCArg::Type::STR_HEX,
RPCArg::Optional::NO, "The transaction id"},
{"vout", RPCArg::Type::NUM, RPCArg::Optional::NO,
"The output number"},
{"scriptPubKey", RPCArg::Type::STR_HEX,
RPCArg::Optional::NO, "script key"},
{"redeemScript", RPCArg::Type::STR_HEX,
RPCArg::Optional::OMITTED,
"(required for P2SH) redeem script"},
{"amount", RPCArg::Type::AMOUNT,
RPCArg::Optional::NO, "The amount spent"},
},
},
},
},
{"sighashtype", RPCArg::Type::STR, /* default */ "ALL|FORKID",
"The signature hash type. Must be one of:\n"
" \"ALL|FORKID\"\n"
" \"NONE|FORKID\"\n"
" \"SINGLE|FORKID\"\n"
" \"ALL|FORKID|ANYONECANPAY\"\n"
" \"NONE|FORKID|ANYONECANPAY\"\n"
" \"SINGLE|FORKID|ANYONECANPAY\""},
},
RPCResult{
"{\n"
" \"hex\" : \"value\", (string) The hex-encoded raw "
"transaction with signature(s)\n"
" \"complete\" : true|false, (boolean) If the transaction has a "
"complete set of signatures\n"
" \"errors\" : [ (json array of objects) Script "
"verification errors (if there are any)\n"
" {\n"
" \"txid\" : \"hash\", (string) The hash of the "
"referenced, previous transaction\n"
" \"vout\" : n, (numeric) The index of the output "
"to spent and used as input\n"
" \"scriptSig\" : \"hex\", (string) The hex-encoded signature "
"script\n"
" \"sequence\" : n, (numeric) Script sequence number\n"
" \"error\" : \"text\" (string) Verification or signing "
"error related to the input\n"
" }\n"
" ,...\n"
" ]\n"
"}\n"},
RPCExamples{
HelpExampleCli("signrawtransactionwithkey",
"\"myhex\" \"[\\\"key1\\\",\\\"key2\\\"]\"") +
HelpExampleRpc("signrawtransactionwithkey",
"\"myhex\", \"[\\\"key1\\\",\\\"key2\\\"]\"")},
}
.Check(request);
RPCTypeCheck(
request.params,
{UniValue::VSTR, UniValue::VARR, UniValue::VARR, UniValue::VSTR}, true);
CMutableTransaction mtx;
if (!DecodeHexTx(mtx, request.params[0].get_str())) {
throw JSONRPCError(RPC_DESERIALIZATION_ERROR, "TX decode failed");
}
FillableSigningProvider keystore;
const UniValue &keys = request.params[1].get_array();
for (size_t idx = 0; idx < keys.size(); ++idx) {
UniValue k = keys[idx];
CKey key = DecodeSecret(k.get_str());
if (!key.IsValid()) {
throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY,
"Invalid private key");
}
keystore.AddKey(key);
}
// Fetch previous transactions (inputs):
std::map<COutPoint, Coin> coins;
for (const CTxIn &txin : mtx.vin) {
// Create empty map entry keyed by prevout.
coins[txin.prevout];
}
NodeContext &node = EnsureNodeContext(request.context);
FindCoins(node, coins);
// Parse the prevtxs array
ParsePrevouts(request.params[2], &keystore, coins);
UniValue result(UniValue::VOBJ);
SignTransaction(mtx, &keystore, coins, request.params[3], result);
return result;
}
static UniValue sendrawtransaction(const Config &config,
const JSONRPCRequest &request) {
RPCHelpMan{
"sendrawtransaction",
"Submits 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"},
{"maxfeerate", RPCArg::Type::AMOUNT,
/* default */
FormatMoney(DEFAULT_MAX_RAW_TX_FEE_RATE.GetFeePerK()),
"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"},
RPCExamples{
"\nCreate a transaction\n" +
HelpExampleCli(
"createrawtransaction",
"\"[{\\\"txid\\\" : \\\"mytxid\\\",\\\"vout\\\":0}]\" "
"\"{\\\"myaddress\\\":0.01}\"") +
"Sign the transaction, and get back the hex\n" +
HelpExampleCli("signrawtransactionwithwallet", "\"myhex\"") +
"\nSend the transaction (signed hex)\n" +
HelpExampleCli("sendrawtransaction", "\"signedhex\"") +
"\nAs a JSON-RPC call\n" +
HelpExampleRpc("sendrawtransaction", "\"signedhex\"")},
}
.Check(request);
RPCTypeCheck(request.params, {
UniValue::VSTR,
// NUM or BOOL, checked later
UniValueType(),
});
// parse hex string from parameter
CMutableTransaction mtx;
if (!DecodeHexTx(mtx, request.params[0].get_str())) {
throw JSONRPCError(RPC_DESERIALIZATION_ERROR, "TX decode failed");
}
CTransactionRef tx(MakeTransactionRef(std::move(mtx)));
CFeeRate max_raw_tx_fee_rate = DEFAULT_MAX_RAW_TX_FEE_RATE;
// TODO: temporary migration code for old clients. Remove in v0.22
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].isNull()) {
max_raw_tx_fee_rate = CFeeRate(AmountFromValue(request.params[1]));
}
int64_t virtual_size = GetVirtualTransactionSize(*tx);
Amount max_raw_tx_fee = max_raw_tx_fee_rate.GetFee(virtual_size);
std::string err_string;
AssertLockNotHeld(cs_main);
NodeContext &node = EnsureNodeContext(request.context);
const TransactionError err = BroadcastTransaction(
node, config, tx, err_string, max_raw_tx_fee, /*relay*/ true,
/*wait_callback*/ true);
if (err != TransactionError::OK) {
throw JSONRPCTransactionError(err, err_string);
}
return tx->GetHash().GetHex();
}
static UniValue testmempoolaccept(const Config &config,
const JSONRPCRequest &request) {
RPCHelpMan{
"testmempoolaccept",
"Returns 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",
{
{
"rawtxs",
RPCArg::Type::ARR,
RPCArg::Optional::NO,
"An array of hex strings of raw transactions.\n"
" Length must be one for now.",
{
{"rawtx", RPCArg::Type::STR_HEX, RPCArg::Optional::OMITTED,
""},
},
},
{"maxfeerate", RPCArg::Type::AMOUNT,
/* default */
FormatMoney(DEFAULT_MAX_RAW_TX_FEE_RATE.GetFeePerK()),
"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"
" Length is exactly one for now.\n"
" {\n"
" \"txid\" (string) The transaction hash in hex\n"
" \"allowed\" (boolean) If the mempool allows this tx to be "
"inserted\n"
" \"reject-reason\" (string) Rejection string (only present when "
"'allowed' is false)\n"
" }\n"
"]\n"},
RPCExamples{
"\nCreate a transaction\n" +
HelpExampleCli(
"createrawtransaction",
"\"[{\\\"txid\\\" : \\\"mytxid\\\",\\\"vout\\\":0}]\" "
"\"{\\\"myaddress\\\":0.01}\"") +
"Sign the transaction, and get back the hex\n" +
HelpExampleCli("signrawtransactionwithwallet", "\"myhex\"") +
"\nTest acceptance of the transaction (signed hex)\n" +
HelpExampleCli("testmempoolaccept", "[\"signedhex\"]") +
"\nAs a JSON-RPC call\n" +
HelpExampleRpc("testmempoolaccept", "[\"signedhex\"]")},
}
.Check(request);
RPCTypeCheck(request.params, {
UniValue::VARR,
// NUM or BOOL, checked later
UniValueType(),
});
if (request.params[0].get_array().size() != 1) {
throw JSONRPCError(
RPC_INVALID_PARAMETER,
"Array must contain exactly one raw transaction for now");
}
CMutableTransaction mtx;
if (!DecodeHexTx(mtx, request.params[0].get_array()[0].get_str())) {
throw JSONRPCError(RPC_DESERIALIZATION_ERROR, "TX decode failed");
}
CTransactionRef tx(MakeTransactionRef(std::move(mtx)));
const TxId &txid = tx->GetId();
CFeeRate max_raw_tx_fee_rate = DEFAULT_MAX_RAW_TX_FEE_RATE;
// TODO: temporary migration code for old clients. Remove in v0.22
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].isNull()) {
max_raw_tx_fee_rate = CFeeRate(AmountFromValue(request.params[1]));
}
CTxMemPool &mempool = EnsureMemPool(request.context);
int64_t virtual_size = GetVirtualTransactionSize(*tx);
Amount max_raw_tx_fee = max_raw_tx_fee_rate.GetFee(virtual_size);
UniValue result(UniValue::VARR);
UniValue result_0(UniValue::VOBJ);
result_0.pushKV("txid", txid.GetHex());
TxValidationState state;
bool test_accept_res;
{
LOCK(cs_main);
test_accept_res = AcceptToMemoryPool(
config, mempool, state, std::move(tx), false /* bypass_limits */,
max_raw_tx_fee, true /* test_accept */);
}
result_0.pushKV("allowed", test_accept_res);
if (!test_accept_res) {
if (state.IsInvalid()) {
if (state.GetResult() == TxValidationResult::TX_MISSING_INPUTS) {
result_0.pushKV("reject-reason", "missing-inputs");
} else {
result_0.pushKV("reject-reason",
strprintf("%i: %s", state.GetRejectCode(),
state.GetRejectReason()));
}
} else {
result_0.pushKV("reject-reason", state.GetRejectReason());
}
}
result.push_back(std::move(result_0));
return result;
}
static std::string WriteHDKeypath(std::vector<uint32_t> &keypath) {
std::string keypath_str = "m";
for (uint32_t num : keypath) {
keypath_str += "/";
bool hardened = false;
if (num & 0x80000000) {
hardened = true;
num &= ~0x80000000;
}
keypath_str += std::to_string(num);
if (hardened) {
keypath_str += "'";
}
}
return keypath_str;
}
static UniValue decodepsbt(const Config &config,
const JSONRPCRequest &request) {
RPCHelpMan{
"decodepsbt",
"Return a JSON object representing the serialized, base64-encoded "
"partially signed Bitcoin transaction.\n",
{
{"psbt", RPCArg::Type::STR, RPCArg::Optional::NO,
"The PSBT base64 string"},
},
RPCResult{
"{\n"
" \"tx\" : { (json object) The decoded "
"network-serialized unsigned transaction.\n"
" ... The layout is the "
"same as the output of decoderawtransaction.\n"
" },\n"
" \"unknown\" : { (json object) The unknown global "
"fields\n"
" \"key\" : \"value\" (key-value pair) An unknown "
"key-value pair\n"
" ...\n"
" },\n"
" \"inputs\" : [ (array of json objects)\n"
" {\n"
" \"utxo\" : { (json object, optional) Transaction "
"output for UTXOs\n"
" \"amount\" : x.xxx, (numeric) The value in " +
CURRENCY_UNIT +
"\n"
" \"scriptPubKey\" : { (json object)\n"
" \"asm\" : \"asm\", (string) The asm\n"
" \"hex\" : \"hex\", (string) The hex\n"
" \"type\" : \"pubkeyhash\", (string) The type, eg "
"'pubkeyhash'\n"
" \"address\" : \"address\" (string) Bitcoin address "
"if there is one\n"
" }\n"
" },\n"
" \"partial_signatures\" : { (json object, "
"optional)\n"
" \"pubkey\" : \"signature\", (string) The public "
"key and signature that corresponds to it.\n"
" ,...\n"
" }\n"
" \"sighash\" : \"type\", (string, optional) "
"The sighash type to be used\n"
" \"redeem_script\" : { (json object, optional)\n"
" \"asm\" : \"asm\", (string) The asm\n"
" \"hex\" : \"hex\", (string) The hex\n"
" \"type\" : \"pubkeyhash\", (string) The type, eg "
"'pubkeyhash'\n"
" }\n"
" \"bip32_derivs\" : { (json object, optional)\n"
" \"pubkey\" : { (json object, "
"optional) The public key with the derivation path as the value.\n"
" \"master_fingerprint\" : \"fingerprint\" (string) "
"The fingerprint of the master key\n"
" \"path\" : \"path\", (string) "
"The path\n"
" }\n"
" ,...\n"
" }\n"
" \"final_scriptsig\" : { (json object, optional)\n"
" \"asm\" : \"asm\", (string) The asm\n"
" \"hex\" : \"hex\", (string) The hex\n"
" }\n"
" \"unknown\" : { (json object) The unknown "
"global fields\n"
" \"key\" : \"value\" (key-value pair) An "
"unknown key-value pair\n"
" ...\n"
" },\n"
" }\n"
" ,...\n"
" ]\n"
" \"outputs\" : [ (array of json objects)\n"
" {\n"
" \"redeem_script\" : { (json object, optional)\n"
" \"asm\" : \"asm\", (string) The asm\n"
" \"hex\" : \"hex\", (string) The hex\n"
" \"type\" : \"pubkeyhash\", (string) The type, eg "
"'pubkeyhash'\n"
" }\n"
" \"bip32_derivs\" : [ (array of json objects, "
"optional)\n"
" {\n"
" \"pubkey\" : \"pubkey\", (string) "
"The public key this path corresponds to\n"
" \"master_fingerprint\" : \"fingerprint\" (string) "
"The fingerprint of the master key\n"
" \"path\" : \"path\", (string) "
"The path\n"
" }\n"
" }\n"
" ,...\n"
" ],\n"
" \"unknown\" : { (json object) The unknown "
"global fields\n"
" \"key\" : \"value\" (key-value pair) An "
"unknown key-value pair\n"
" ...\n"
" },\n"
" }\n"
" ,...\n"
" ]\n"
" \"fee\" : fee (numeric, optional) The "
"transaction fee paid if all UTXOs slots in the PSBT have been "
"filled.\n"
"}\n"},
RPCExamples{HelpExampleCli("decodepsbt", "\"psbt\"")},
}
.Check(request);
RPCTypeCheck(request.params, {UniValue::VSTR});
// Unserialize the transactions
PartiallySignedTransaction psbtx;
std::string error;
if (!DecodeBase64PSBT(psbtx, request.params[0].get_str(), error)) {
throw JSONRPCError(RPC_DESERIALIZATION_ERROR,
strprintf("TX decode failed %s", error));
}
UniValue result(UniValue::VOBJ);
// Add the decoded tx
UniValue tx_univ(UniValue::VOBJ);
TxToUniv(CTransaction(*psbtx.tx), uint256(), tx_univ, false);
result.pushKV("tx", tx_univ);
// Unknown data
if (psbtx.unknown.size() > 0) {
UniValue unknowns(UniValue::VOBJ);
for (auto entry : psbtx.unknown) {
unknowns.pushKV(HexStr(entry.first), HexStr(entry.second));
}
result.pushKV("unknown", unknowns);
}
// inputs
Amount total_in = Amount::zero();
bool have_all_utxos = true;
UniValue inputs(UniValue::VARR);
for (size_t i = 0; i < psbtx.inputs.size(); ++i) {
const PSBTInput &input = psbtx.inputs[i];
UniValue in(UniValue::VOBJ);
// UTXOs
if (!input.utxo.IsNull()) {
const CTxOut &txout = input.utxo;
UniValue out(UniValue::VOBJ);
out.pushKV("amount", ValueFromAmount(txout.nValue));
total_in += txout.nValue;
UniValue o(UniValue::VOBJ);
ScriptToUniv(txout.scriptPubKey, o, true);
out.pushKV("scriptPubKey", o);
in.pushKV("utxo", out);
} else {
have_all_utxos = false;
}
// Partial sigs
if (!input.partial_sigs.empty()) {
UniValue partial_sigs(UniValue::VOBJ);
for (const auto &sig : input.partial_sigs) {
partial_sigs.pushKV(HexStr(sig.second.first),
HexStr(sig.second.second));
}
in.pushKV("partial_signatures", partial_sigs);
}
// Sighash
uint8_t sighashbyte = input.sighash_type.getRawSigHashType() & 0xff;
if (sighashbyte > 0) {
in.pushKV("sighash", SighashToStr(sighashbyte));
}
// Redeem script
if (!input.redeem_script.empty()) {
UniValue r(UniValue::VOBJ);
ScriptToUniv(input.redeem_script, r, false);
in.pushKV("redeem_script", r);
}
// keypaths
if (!input.hd_keypaths.empty()) {
UniValue keypaths(UniValue::VARR);
for (auto entry : input.hd_keypaths) {
UniValue keypath(UniValue::VOBJ);
keypath.pushKV("pubkey", HexStr(entry.first));
keypath.pushKV(
"master_fingerprint",
strprintf("%08x", ReadBE32(entry.second.fingerprint)));
keypath.pushKV("path", WriteHDKeypath(entry.second.path));
keypaths.push_back(keypath);
}
in.pushKV("bip32_derivs", keypaths);
}
// Final scriptSig
if (!input.final_script_sig.empty()) {
UniValue scriptsig(UniValue::VOBJ);
scriptsig.pushKV("asm",
ScriptToAsmStr(input.final_script_sig, true));
scriptsig.pushKV("hex", HexStr(input.final_script_sig));
in.pushKV("final_scriptSig", scriptsig);
}
// Unknown data
if (input.unknown.size() > 0) {
UniValue unknowns(UniValue::VOBJ);
for (auto entry : input.unknown) {
unknowns.pushKV(HexStr(entry.first), HexStr(entry.second));
}
in.pushKV("unknown", unknowns);
}
inputs.push_back(in);
}
result.pushKV("inputs", inputs);
// outputs
Amount output_value = Amount::zero();
UniValue outputs(UniValue::VARR);
for (size_t i = 0; i < psbtx.outputs.size(); ++i) {
const PSBTOutput &output = psbtx.outputs[i];
UniValue out(UniValue::VOBJ);
// Redeem script
if (!output.redeem_script.empty()) {
UniValue r(UniValue::VOBJ);
ScriptToUniv(output.redeem_script, r, false);
out.pushKV("redeem_script", r);
}
// keypaths
if (!output.hd_keypaths.empty()) {
UniValue keypaths(UniValue::VARR);
for (auto entry : output.hd_keypaths) {
UniValue keypath(UniValue::VOBJ);
keypath.pushKV("pubkey", HexStr(entry.first));
keypath.pushKV(
"master_fingerprint",
strprintf("%08x", ReadBE32(entry.second.fingerprint)));
keypath.pushKV("path", WriteHDKeypath(entry.second.path));
keypaths.push_back(keypath);
}
out.pushKV("bip32_derivs", keypaths);
}
// Unknown data
if (output.unknown.size() > 0) {
UniValue unknowns(UniValue::VOBJ);
for (auto entry : output.unknown) {
unknowns.pushKV(HexStr(entry.first), HexStr(entry.second));
}
out.pushKV("unknown", unknowns);
}
outputs.push_back(out);
// Fee calculation
output_value += psbtx.tx->vout[i].nValue;
}
result.pushKV("outputs", outputs);
if (have_all_utxos) {
result.pushKV("fee", ValueFromAmount(total_in - output_value));
}
return result;
}
static UniValue combinepsbt(const Config &config,
const JSONRPCRequest &request) {
RPCHelpMan{
"combinepsbt",
"Combine multiple partially signed Bitcoin transactions into one "
"transaction.\n"
"Implements the Combiner role.\n",
{
{
"txs",
RPCArg::Type::ARR,
RPCArg::Optional::NO,
"A json array of base64 strings of partially signed "
"transactions",
{
{"psbt", RPCArg::Type::STR, RPCArg::Optional::OMITTED,
"A base64 string of a PSBT"},
},
},
},
RPCResult{" \"psbt\" (string) The base64-encoded partially "
"signed transaction\n"},
RPCExamples{HelpExampleCli(
"combinepsbt", "[\"mybase64_1\", \"mybase64_2\", \"mybase64_3\"]")},
}
.Check(request);
RPCTypeCheck(request.params, {UniValue::VARR}, true);
// Unserialize the transactions
std::vector<PartiallySignedTransaction> psbtxs;
UniValue txs = request.params[0].get_array();
if (txs.empty()) {
throw JSONRPCError(RPC_INVALID_PARAMETER,
"Parameter 'txs' cannot be empty");
}
for (size_t i = 0; i < txs.size(); ++i) {
PartiallySignedTransaction psbtx;
std::string error;
if (!DecodeBase64PSBT(psbtx, txs[i].get_str(), error)) {
throw JSONRPCError(RPC_DESERIALIZATION_ERROR,
strprintf("TX decode failed %s", error));
}
psbtxs.push_back(psbtx);
}
PartiallySignedTransaction merged_psbt;
const TransactionError error = CombinePSBTs(merged_psbt, psbtxs);
if (error != TransactionError::OK) {
throw JSONRPCTransactionError(error);
}
CDataStream ssTx(SER_NETWORK, PROTOCOL_VERSION);
ssTx << merged_psbt;
return EncodeBase64((uint8_t *)ssTx.data(), ssTx.size());
}
static UniValue finalizepsbt(const Config &config,
const JSONRPCRequest &request) {
RPCHelpMan{
"finalizepsbt",
"Finalize the inputs of a PSBT. If the transaction is fully signed, it "
"will produce a\n"
"network serialized transaction which can be broadcast with "
"sendrawtransaction. Otherwise a PSBT will be\n"
"created which has the final_scriptSigfields filled for inputs that "
"are complete.\n"
"Implements the Finalizer and Extractor roles.\n",
{
{"psbt", RPCArg::Type::STR, RPCArg::Optional::NO,
"A base64 string of a PSBT"},
{"extract", RPCArg::Type::BOOL, /* default */ "true",
"If true and the transaction is complete,\n"
" extract and return the complete "
"transaction in normal network serialization instead of the "
"PSBT."},
},
RPCResult{
"{\n"
" \"psbt\" : \"value\", (string) The base64-encoded "
"partially signed transaction if not extracted\n"
" \"hex\" : \"value\", (string) The hex-encoded network "
"transaction if extracted\n"
" \"complete\" : true|false, (boolean) If the transaction has a "
"complete set of signatures\n"
" ]\n"
"}\n"},
RPCExamples{HelpExampleCli("finalizepsbt", "\"psbt\"")},
}
.Check(request);
RPCTypeCheck(request.params, {UniValue::VSTR, UniValue::VBOOL}, true);
// Unserialize the transactions
PartiallySignedTransaction psbtx;
std::string error;
if (!DecodeBase64PSBT(psbtx, request.params[0].get_str(), error)) {
throw JSONRPCError(RPC_DESERIALIZATION_ERROR,
strprintf("TX decode failed %s", error));
}
bool extract = request.params[1].isNull() || (!request.params[1].isNull() &&
request.params[1].get_bool());
CMutableTransaction mtx;
bool complete = FinalizeAndExtractPSBT(psbtx, mtx);
UniValue result(UniValue::VOBJ);
CDataStream ssTx(SER_NETWORK, PROTOCOL_VERSION);
std::string result_str;
if (complete && extract) {
ssTx << mtx;
result_str = HexStr(ssTx.str());
result.pushKV("hex", result_str);
} else {
ssTx << psbtx;
result_str = EncodeBase64(ssTx.str());
result.pushKV("psbt", result_str);
}
result.pushKV("complete", complete);
return result;
}
static UniValue createpsbt(const Config &config,
const JSONRPCRequest &request) {
RPCHelpMan{
"createpsbt",
"Creates a transaction in the Partially Signed Transaction format.\n"
"Implements the Creator role.\n",
{
{
"inputs",
RPCArg::Type::ARR,
RPCArg::Optional::NO,
"A json array of json objects",
{
{
"",
RPCArg::Type::OBJ,
RPCArg::Optional::OMITTED,
"",
{
{"txid", RPCArg::Type::STR_HEX,
RPCArg::Optional::NO, "The transaction id"},
{"vout", RPCArg::Type::NUM, RPCArg::Optional::NO,
"The output number"},
{"sequence", RPCArg::Type::NUM, /* default */
"depends on the value of the 'locktime' argument",
"The sequence number"},
},
},
},
},
{
"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"
"For compatibility reasons, a dictionary, which holds the "
"key-value pairs directly, is also\n"
" accepted as second parameter.",
{
{
"",
RPCArg::Type::OBJ,
RPCArg::Optional::OMITTED,
"",
{
{"address", RPCArg::Type::AMOUNT,
RPCArg::Optional::NO,
"A key-value pair. The key (string) is the "
"bitcoin address, the value (float or string) is "
"the amount in " +
CURRENCY_UNIT},
},
},
{
"",
RPCArg::Type::OBJ,
RPCArg::Optional::OMITTED,
"",
{
{"data", RPCArg::Type::STR_HEX,
RPCArg::Optional::NO,
"A key-value pair. The key must be \"data\", the "
"value is hex-encoded data"},
},
},
},
},
{"locktime", RPCArg::Type::NUM, /* default */ "0",
"Raw locktime. Non-0 value also locktime-activates inputs"},
},
RPCResult{" \"psbt\" (string) The resulting raw transaction "
"(base64-encoded string)\n"},
RPCExamples{HelpExampleCli(
"createpsbt", "\"[{\\\"txid\\\":\\\"myid\\\",\\\"vout\\\":0}]"
"\" \"[{\\\"data\\\":\\\"00010203\\\"}]\"")},
}
.Check(request);
RPCTypeCheck(request.params,
{
UniValue::VARR,
UniValueType(), // ARR or OBJ, checked later
UniValue::VNUM,
},
true);
CMutableTransaction rawTx =
ConstructTransaction(config.GetChainParams(), request.params[0],
request.params[1], request.params[2]);
// Make a blank psbt
PartiallySignedTransaction psbtx;
psbtx.tx = rawTx;
for (size_t i = 0; i < rawTx.vin.size(); ++i) {
psbtx.inputs.push_back(PSBTInput());
}
for (size_t i = 0; i < rawTx.vout.size(); ++i) {
psbtx.outputs.push_back(PSBTOutput());
}
// Serialize the PSBT
CDataStream ssTx(SER_NETWORK, PROTOCOL_VERSION);
ssTx << psbtx;
return EncodeBase64((uint8_t *)ssTx.data(), ssTx.size());
}
static UniValue converttopsbt(const Config &config,
const JSONRPCRequest &request) {
RPCHelpMan{
"converttopsbt",
"Converts 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",
{
{"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"
" will continue. If false, RPC will "
"fail if any signatures are present."},
},
RPCResult{" \"psbt\" (string) The resulting raw "
"transaction (base64-encoded string)\n"},
RPCExamples{
"\nCreate a transaction\n" +
HelpExampleCli("createrawtransaction",
"\"[{\\\"txid\\\":\\\"myid\\\",\\\"vout\\\":0}]"
"\" \"[{\\\"data\\\":\\\"00010203\\\"}]\"") +
"\nConvert the transaction to a PSBT\n" +
HelpExampleCli("converttopsbt", "\"rawtransaction\"")},
}
.Check(request);
RPCTypeCheck(request.params, {UniValue::VSTR, UniValue::VBOOL}, true);
// parse hex string from parameter
CMutableTransaction tx;
bool permitsigdata =
request.params[1].isNull() ? false : request.params[1].get_bool();
if (!DecodeHexTx(tx, request.params[0].get_str())) {
throw JSONRPCError(RPC_DESERIALIZATION_ERROR, "TX decode failed");
}
// Remove all scriptSigs from inputs
for (CTxIn &input : tx.vin) {
if (!input.scriptSig.empty() && !permitsigdata) {
throw JSONRPCError(RPC_DESERIALIZATION_ERROR,
"Inputs must not have scriptSigs");
}
input.scriptSig.clear();
}
// Make a blank psbt
PartiallySignedTransaction psbtx;
psbtx.tx = tx;
for (size_t i = 0; i < tx.vin.size(); ++i) {
psbtx.inputs.push_back(PSBTInput());
}
for (size_t i = 0; i < tx.vout.size(); ++i) {
psbtx.outputs.push_back(PSBTOutput());
}
// Serialize the PSBT
CDataStream ssTx(SER_NETWORK, PROTOCOL_VERSION);
ssTx << psbtx;
return EncodeBase64((uint8_t *)ssTx.data(), ssTx.size());
}
UniValue utxoupdatepsbt(const Config &config, const JSONRPCRequest &request) {
RPCHelpMan{
"utxoupdatepsbt",
"Updates all inputs and outputs in a PSBT with data from output "
"descriptors, the UTXO set or the mempool.\n",
{
{"psbt", RPCArg::Type::STR, RPCArg::Optional::NO,
"A base64 string of a PSBT"},
{"descriptors",
RPCArg::Type::ARR,
RPCArg::Optional::OMITTED_NAMED_ARG,
"An array of either strings or objects",
{
{"", RPCArg::Type::STR, RPCArg::Optional::OMITTED,
"An output descriptor"},
{"",
RPCArg::Type::OBJ,
RPCArg::Optional::OMITTED,
"An object with an output descriptor and extra information",
{
{"desc", RPCArg::Type::STR, RPCArg::Optional::NO,
"An output descriptor"},
{"range", RPCArg::Type::RANGE, "1000",
"Up to what index HD chains should be explored (either "
"end or [begin,end])"},
}},
}},
},
RPCResult{" \"psbt\" (string) The base64-encoded "
"partially signed transaction with inputs updated\n"},
RPCExamples{HelpExampleCli("utxoupdatepsbt", "\"psbt\"")}}
.Check(request);
RPCTypeCheck(request.params, {UniValue::VSTR, UniValue::VARR}, true);
// Unserialize the transactions
PartiallySignedTransaction psbtx;
std::string error;
if (!DecodeBase64PSBT(psbtx, request.params[0].get_str(), error)) {
throw JSONRPCError(RPC_DESERIALIZATION_ERROR,
strprintf("TX decode failed %s", error));
}
// Parse descriptors, if any.
FlatSigningProvider provider;
if (!request.params[1].isNull()) {
auto descs = request.params[1].get_array();
for (size_t i = 0; i < descs.size(); ++i) {
EvalDescriptorStringOrObject(descs[i], provider);
}
}
// We don't actually need private keys further on; hide them as a
// precaution.
HidingSigningProvider public_provider(&provider, /* nosign */ true,
/* nobip32derivs */ false);
// Fetch previous transactions (inputs):
CCoinsView viewDummy;
CCoinsViewCache view(&viewDummy);
{
const CTxMemPool &mempool = EnsureMemPool(request.context);
LOCK2(cs_main, mempool.cs);
CCoinsViewCache &viewChain = ::ChainstateActive().CoinsTip();
CCoinsViewMemPool viewMempool(&viewChain, mempool);
// temporarily switch cache backend to db+mempool view
view.SetBackend(viewMempool);
for (const CTxIn &txin : psbtx.tx->vin) {
// Load entries from viewChain into view; can fail.
view.AccessCoin(txin.prevout);
}
// switch back to avoid locking mempool for too long
view.SetBackend(viewDummy);
}
// Fill the inputs
for (size_t i = 0; i < psbtx.tx->vin.size(); ++i) {
PSBTInput &input = psbtx.inputs.at(i);
if (!input.utxo.IsNull()) {
continue;
}
// Update script/keypath information using descriptor data.
// Note that SignPSBTInput does a lot more than just constructing ECDSA
// signatures we don't actually care about those here, in fact.
SignPSBTInput(public_provider, psbtx, i,
/* sighash_type */ SigHashType().withForkId());
}
// Update script/keypath information using descriptor data.
for (unsigned int i = 0; i < psbtx.tx->vout.size(); ++i) {
UpdatePSBTOutput(public_provider, psbtx, i);
}
CDataStream ssTx(SER_NETWORK, PROTOCOL_VERSION);
ssTx << psbtx;
return EncodeBase64((uint8_t *)ssTx.data(), ssTx.size());
}
UniValue joinpsbts(const Config &config, const JSONRPCRequest &request) {
RPCHelpMan{
"joinpsbts",
"Joins multiple distinct PSBTs with different inputs and outputs "
"into one PSBT with inputs and outputs from all of the PSBTs\n"
"No input in any of the PSBTs can be in more than one of the PSBTs.\n",
{{"txs",
RPCArg::Type::ARR,
RPCArg::Optional::NO,
"A json array of base64 strings of partially signed transactions",
{{"psbt", RPCArg::Type::STR, RPCArg::Optional::NO,
"A base64 string of a PSBT"}}}},
RPCResult{" \"psbt\" (string) The base64-encoded partially "
"signed transaction\n"},
RPCExamples{HelpExampleCli("joinpsbts", "\"psbt\"")}}
.Check(request);
RPCTypeCheck(request.params, {UniValue::VARR}, true);
// Unserialize the transactions
std::vector<PartiallySignedTransaction> psbtxs;
UniValue txs = request.params[0].get_array();
if (txs.size() <= 1) {
throw JSONRPCError(RPC_INVALID_PARAMETER,
"At least two PSBTs are required to join PSBTs.");
}
int32_t best_version = 1;
uint32_t best_locktime = 0xffffffff;
for (size_t i = 0; i < txs.size(); ++i) {
PartiallySignedTransaction psbtx;
std::string error;
if (!DecodeBase64PSBT(psbtx, txs[i].get_str(), error)) {
throw JSONRPCError(RPC_DESERIALIZATION_ERROR,
strprintf("TX decode failed %s", error));
}
psbtxs.push_back(psbtx);
// Choose the highest version number
if (psbtx.tx->nVersion > best_version) {
best_version = psbtx.tx->nVersion;
}
// Choose the lowest lock time
if (psbtx.tx->nLockTime < best_locktime) {
best_locktime = psbtx.tx->nLockTime;
}
}
// Create a blank psbt where everything will be added
PartiallySignedTransaction merged_psbt;
merged_psbt.tx = CMutableTransaction();
merged_psbt.tx->nVersion = best_version;
merged_psbt.tx->nLockTime = best_locktime;
// Merge
for (auto &psbt : psbtxs) {
for (size_t i = 0; i < psbt.tx->vin.size(); ++i) {
if (!merged_psbt.AddInput(psbt.tx->vin[i], psbt.inputs[i])) {
throw JSONRPCError(
RPC_INVALID_PARAMETER,
strprintf(
"Input %s:%d exists in multiple PSBTs",
psbt.tx->vin[i].prevout.GetTxId().ToString().c_str(),
psbt.tx->vin[i].prevout.GetN()));
}
}
for (size_t i = 0; i < psbt.tx->vout.size(); ++i) {
merged_psbt.AddOutput(psbt.tx->vout[i], psbt.outputs[i]);
}
merged_psbt.unknown.insert(psbt.unknown.begin(), psbt.unknown.end());
}
// Generate list of shuffled indices for shuffling inputs and outputs of the
// merged PSBT
std::vector<int> input_indices(merged_psbt.inputs.size());
std::iota(input_indices.begin(), input_indices.end(), 0);
std::vector<int> output_indices(merged_psbt.outputs.size());
std::iota(output_indices.begin(), output_indices.end(), 0);
// Shuffle input and output indicies lists
Shuffle(input_indices.begin(), input_indices.end(), FastRandomContext());
Shuffle(output_indices.begin(), output_indices.end(), FastRandomContext());
PartiallySignedTransaction shuffled_psbt;
shuffled_psbt.tx = CMutableTransaction();
shuffled_psbt.tx->nVersion = merged_psbt.tx->nVersion;
shuffled_psbt.tx->nLockTime = merged_psbt.tx->nLockTime;
for (int i : input_indices) {
shuffled_psbt.AddInput(merged_psbt.tx->vin[i], merged_psbt.inputs[i]);
}
for (int i : output_indices) {
shuffled_psbt.AddOutput(merged_psbt.tx->vout[i],
merged_psbt.outputs[i]);
}
shuffled_psbt.unknown.insert(merged_psbt.unknown.begin(),
merged_psbt.unknown.end());
CDataStream ssTx(SER_NETWORK, PROTOCOL_VERSION);
ssTx << shuffled_psbt;
return EncodeBase64((uint8_t *)ssTx.data(), ssTx.size());
}
UniValue analyzepsbt(const Config &config, const JSONRPCRequest &request) {
RPCHelpMan{
"analyzepsbt",
"Analyzes and provides information about the current status of a "
"PSBT and its inputs\n",
{{"psbt", RPCArg::Type::STR, RPCArg::Optional::NO,
"A base64 string of a PSBT"}},
RPCResult{
"{\n"
" \"inputs\" : [ (array of json objects)\n"
" {\n"
" \"has_utxo\" : true|false (boolean) Whether a UTXO is "
"provided\n"
" \"is_final\" : true|false (boolean) Whether the input "
"is finalized\n"
" \"missing\" : { (json object, optional) "
"Things that are missing that are required to complete this input\n"
" \"pubkeys\" : [ (array), optional\n"
" \"keyid\" (string) Public key ID, "
"hash160 of the public key, of a public key whose BIP 32 "
"derivation path is missing\n"
" ]\n"
" \"signatures\" : [ (array), optional\n"
" \"keyid\" (string) Public key ID, "
"hash160 of the public key, of a public key whose signature is "
"missing\n"
" ]\n"
" \"redeemscript\" : \"hash\" (string, optional) Hash160 "
"of the redeemScript that is missing\n"
" }\n"
" \"next\" : \"role\" (string, optional) Role of "
"the next person that this input needs to go to\n"
" }\n"
" ,...\n"
" ]\n"
" \"estimated_vsize\" : vsize (numeric, optional) Estimated "
"vsize of the final signed transaction\n"
" \"estimated_feerate\" : feerate (numeric, optional) Estimated "
"feerate of the final signed transaction in " +
CURRENCY_UNIT +
"/kB. Shown only if all UTXO slots in the PSBT have been filled.\n"
" \"fee\" : fee (numeric, optional) The "
"transaction fee paid. Shown only if all UTXO slots in the PSBT "
"have been filled.\n"
" \"next\" : \"role\" (string) Role of the next "
"person that this psbt needs to go to\n"
" \"error\" : \"error\" (string) Error message if "
"there is one"
"}\n"},
RPCExamples{HelpExampleCli("analyzepsbt", "\"psbt\"")}}
.Check(request);
RPCTypeCheck(request.params, {UniValue::VSTR});
// Unserialize the transaction
PartiallySignedTransaction psbtx;
std::string error;
if (!DecodeBase64PSBT(psbtx, request.params[0].get_str(), error)) {
throw JSONRPCError(RPC_DESERIALIZATION_ERROR,
strprintf("TX decode failed %s", error));
}
PSBTAnalysis psbta = AnalyzePSBT(psbtx);
UniValue result(UniValue::VOBJ);
UniValue inputs_result(UniValue::VARR);
for (const auto &input : psbta.inputs) {
UniValue input_univ(UniValue::VOBJ);
UniValue missing(UniValue::VOBJ);
input_univ.pushKV("has_utxo", input.has_utxo);
input_univ.pushKV("is_final", input.is_final);
input_univ.pushKV("next", PSBTRoleName(input.next));
if (!input.missing_pubkeys.empty()) {
UniValue missing_pubkeys_univ(UniValue::VARR);
for (const CKeyID &pubkey : input.missing_pubkeys) {
missing_pubkeys_univ.push_back(HexStr(pubkey));
}
missing.pushKV("pubkeys", missing_pubkeys_univ);
}
if (!input.missing_redeem_script.IsNull()) {
missing.pushKV("redeemscript", HexStr(input.missing_redeem_script));
}
if (!input.missing_sigs.empty()) {
UniValue missing_sigs_univ(UniValue::VARR);
for (const CKeyID &pubkey : input.missing_sigs) {
missing_sigs_univ.push_back(HexStr(pubkey));
}
missing.pushKV("signatures", missing_sigs_univ);
}
if (!missing.getKeys().empty()) {
input_univ.pushKV("missing", missing);
}
inputs_result.push_back(input_univ);
}
if (!inputs_result.empty()) {
result.pushKV("inputs", inputs_result);
}
if (psbta.estimated_vsize != nullopt) {
result.pushKV("estimated_vsize", (int)*psbta.estimated_vsize);
}
if (psbta.estimated_feerate != nullopt) {
result.pushKV("estimated_feerate",
ValueFromAmount(psbta.estimated_feerate->GetFeePerK()));
}
if (psbta.fee != nullopt) {
result.pushKV("fee", ValueFromAmount(*psbta.fee));
}
result.pushKV("next", PSBTRoleName(psbta.next));
if (!psbta.error.empty()) {
result.pushKV("error", psbta.error);
}
return result;
}
// clang-format off
static const CRPCCommand commands[] = {
// category name actor (function) argNames
// ------------------- ------------------------ ---------------------- ----------
{ "rawtransactions", "getrawtransaction", getrawtransaction, {"txid","verbose","blockhash"} },
{ "rawtransactions", "createrawtransaction", createrawtransaction, {"inputs","outputs","locktime"} },
{ "rawtransactions", "decoderawtransaction", decoderawtransaction, {"hexstring"} },
{ "rawtransactions", "decodescript", decodescript, {"hexstring"} },
{ "rawtransactions", "sendrawtransaction", sendrawtransaction, {"hexstring","allowhighfees|maxfeerate"} },
{ "rawtransactions", "combinerawtransaction", combinerawtransaction, {"txs"} },
{ "rawtransactions", "signrawtransactionwithkey", signrawtransactionwithkey, {"hexstring","privkeys","prevtxs","sighashtype"} },
{ "rawtransactions", "testmempoolaccept", testmempoolaccept, {"rawtxs","allowhighfees|maxfeerate"} },
{ "rawtransactions", "decodepsbt", decodepsbt, {"psbt"} },
{ "rawtransactions", "combinepsbt", combinepsbt, {"txs"} },
{ "rawtransactions", "finalizepsbt", finalizepsbt, {"psbt", "extract"} },
{ "rawtransactions", "createpsbt", createpsbt, {"inputs","outputs","locktime"} },
{ "rawtransactions", "converttopsbt", converttopsbt, {"hexstring","permitsigdata"} },
{ "rawtransactions", "utxoupdatepsbt", utxoupdatepsbt, {"psbt", "descriptors"} },
{ "rawtransactions", "joinpsbts", joinpsbts, {"txs"} },
{ "rawtransactions", "analyzepsbt", analyzepsbt, {"psbt"} },
{ "blockchain", "gettxoutproof", gettxoutproof, {"txids", "blockhash"} },
{ "blockchain", "verifytxoutproof", verifytxoutproof, {"proof"} },
};
// clang-format on
void RegisterRawTransactionRPCCommands(CRPCTable &t) {
for (unsigned int vcidx = 0; vcidx < ARRAYLEN(commands); vcidx++) {
t.appendCommand(commands[vcidx].name, &commands[vcidx]);
}
}
File Metadata
Details
Attached
Mime Type
text/x-diff
Expires
Thu, Apr 17, 03:20 (13 h, 13 m)
Storage Engine
blob
Storage Format
Raw Data
Storage Handle
5508681
Default Alt Text
(83 KB)
Attached To
rABC Bitcoin ABC
Event Timeline
Log In to Comment