mirror of
https://github.com/bitcoin/bitcoin.git
synced 2025-02-09 10:43:19 -05:00
2020 lines
92 KiB
C++
2020 lines
92 KiB
C++
// Copyright (c) 2010 Satoshi Nakamoto
|
|
// Copyright (c) 2009-2022 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 <base58.h>
|
|
#include <chain.h>
|
|
#include <coins.h>
|
|
#include <consensus/amount.h>
|
|
#include <consensus/validation.h>
|
|
#include <core_io.h>
|
|
#include <index/txindex.h>
|
|
#include <key_io.h>
|
|
#include <node/blockstorage.h>
|
|
#include <node/coin.h>
|
|
#include <node/context.h>
|
|
#include <node/psbt.h>
|
|
#include <node/transaction.h>
|
|
#include <policy/packages.h>
|
|
#include <policy/policy.h>
|
|
#include <policy/rbf.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/server_util.h>
|
|
#include <rpc/util.h>
|
|
#include <script/script.h>
|
|
#include <script/sign.h>
|
|
#include <script/signingprovider.h>
|
|
#include <script/solver.h>
|
|
#include <uint256.h>
|
|
#include <undo.h>
|
|
#include <util/bip32.h>
|
|
#include <util/check.h>
|
|
#include <util/strencodings.h>
|
|
#include <util/string.h>
|
|
#include <util/vector.h>
|
|
#include <validation.h>
|
|
#include <validationinterface.h>
|
|
|
|
#include <numeric>
|
|
#include <stdint.h>
|
|
|
|
#include <univalue.h>
|
|
|
|
using node::AnalyzePSBT;
|
|
using node::FindCoins;
|
|
using node::GetTransaction;
|
|
using node::NodeContext;
|
|
using node::PSBTAnalysis;
|
|
|
|
static void TxToJSON(const CTransaction& tx, const uint256 hashBlock, UniValue& entry,
|
|
Chainstate& active_chainstate, const CTxUndo* txundo = nullptr,
|
|
TxVerbosity verbosity = TxVerbosity::SHOW_DETAILS)
|
|
{
|
|
CHECK_NONFATAL(verbosity >= TxVerbosity::SHOW_DETAILS);
|
|
// 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, /*block_hash=*/uint256(), entry, /*include_hex=*/true, RPCSerializationFlags(), txundo, verbosity);
|
|
|
|
if (!hashBlock.IsNull()) {
|
|
LOCK(cs_main);
|
|
|
|
entry.pushKV("blockhash", hashBlock.GetHex());
|
|
const CBlockIndex* pindex = active_chainstate.m_blockman.LookupBlockIndex(hashBlock);
|
|
if (pindex) {
|
|
if (active_chainstate.m_chain.Contains(pindex)) {
|
|
entry.pushKV("confirmations", 1 + active_chainstate.m_chain.Height() - pindex->nHeight);
|
|
entry.pushKV("time", pindex->GetBlockTime());
|
|
entry.pushKV("blocktime", pindex->GetBlockTime());
|
|
}
|
|
else
|
|
entry.pushKV("confirmations", 0);
|
|
}
|
|
}
|
|
}
|
|
|
|
static std::vector<RPCResult> ScriptPubKeyDoc() {
|
|
return
|
|
{
|
|
{RPCResult::Type::STR, "asm", "Disassembly of the public key script"},
|
|
{RPCResult::Type::STR, "desc", "Inferred descriptor for the output"},
|
|
{RPCResult::Type::STR_HEX, "hex", "The raw public key script bytes, hex-encoded"},
|
|
{RPCResult::Type::STR, "address", /*optional=*/true, "The Bitcoin address (only if a well-defined address exists)"},
|
|
{RPCResult::Type::STR, "type", "The type (one of: " + GetAllOutputTypes() + ")"},
|
|
};
|
|
}
|
|
|
|
static std::vector<RPCResult> DecodeTxDoc(const std::string& txid_field_doc)
|
|
{
|
|
return {
|
|
{RPCResult::Type::STR_HEX, "txid", txid_field_doc},
|
|
{RPCResult::Type::STR_HEX, "hash", "The transaction hash (differs from txid for witness transactions)"},
|
|
{RPCResult::Type::NUM, "size", "The serialized transaction size"},
|
|
{RPCResult::Type::NUM, "vsize", "The virtual transaction size (differs from size for witness transactions)"},
|
|
{RPCResult::Type::NUM, "weight", "The transaction's weight (between vsize*4-3 and vsize*4)"},
|
|
{RPCResult::Type::NUM, "version", "The version"},
|
|
{RPCResult::Type::NUM_TIME, "locktime", "The lock time"},
|
|
{RPCResult::Type::ARR, "vin", "",
|
|
{
|
|
{RPCResult::Type::OBJ, "", "",
|
|
{
|
|
{RPCResult::Type::STR_HEX, "coinbase", /*optional=*/true, "The coinbase value (only if coinbase transaction)"},
|
|
{RPCResult::Type::STR_HEX, "txid", /*optional=*/true, "The transaction id (if not coinbase transaction)"},
|
|
{RPCResult::Type::NUM, "vout", /*optional=*/true, "The output number (if not coinbase transaction)"},
|
|
{RPCResult::Type::OBJ, "scriptSig", /*optional=*/true, "The script (if not coinbase transaction)",
|
|
{
|
|
{RPCResult::Type::STR, "asm", "Disassembly of the signature script"},
|
|
{RPCResult::Type::STR_HEX, "hex", "The raw signature script bytes, hex-encoded"},
|
|
}},
|
|
{RPCResult::Type::ARR, "txinwitness", /*optional=*/true, "",
|
|
{
|
|
{RPCResult::Type::STR_HEX, "hex", "hex-encoded witness data (if any)"},
|
|
}},
|
|
{RPCResult::Type::NUM, "sequence", "The script sequence number"},
|
|
}},
|
|
}},
|
|
{RPCResult::Type::ARR, "vout", "",
|
|
{
|
|
{RPCResult::Type::OBJ, "", "",
|
|
{
|
|
{RPCResult::Type::STR_AMOUNT, "value", "The value in " + CURRENCY_UNIT},
|
|
{RPCResult::Type::NUM, "n", "index"},
|
|
{RPCResult::Type::OBJ, "scriptPubKey", "", ScriptPubKeyDoc()},
|
|
}},
|
|
}},
|
|
};
|
|
}
|
|
|
|
static std::vector<RPCArg> CreateTxDoc()
|
|
{
|
|
return {
|
|
{"inputs", RPCArg::Type::ARR, RPCArg::Optional::NO, "The inputs",
|
|
{
|
|
{"", 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, RPCArg::DefaultHint{"depends on the value of the 'replaceable' and 'locktime' arguments"}, "The sequence number"},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
{"outputs", RPCArg::Type::ARR, RPCArg::Optional::NO, "The outputs specified as key-value pairs.\n"
|
|
"Each key may only appear once, i.e. there can only be one 'data' output, and no address may be duplicated.\n"
|
|
"At least one output of either type must be specified.\n"
|
|
"For compatibility reasons, a dictionary, which holds the key-value pairs directly, is also\n"
|
|
" accepted as second parameter.",
|
|
{
|
|
{"", RPCArg::Type::OBJ_USER_KEYS, 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"},
|
|
},
|
|
},
|
|
},
|
|
RPCArgOptions{.skip_type_check = true}},
|
|
{"locktime", RPCArg::Type::NUM, RPCArg::Default{0}, "Raw locktime. Non-0 value also locktime-activates inputs"},
|
|
{"replaceable", RPCArg::Type::BOOL, RPCArg::Default{true}, "Marks this transaction as BIP125-replaceable.\n"
|
|
"Allows this transaction to be replaced by a transaction with higher fees. If provided, it is an error if explicit sequence numbers are incompatible."},
|
|
};
|
|
}
|
|
|
|
// Update PSBT with information from the mempool, the UTXO set, the txindex, and the provided descriptors.
|
|
// Optionally, sign the inputs that we can using information from the descriptors.
|
|
PartiallySignedTransaction ProcessPSBT(const std::string& psbt_string, const std::any& context, const HidingSigningProvider& provider, int sighash_type, bool finalize)
|
|
{
|
|
// Unserialize the transactions
|
|
PartiallySignedTransaction psbtx;
|
|
std::string error;
|
|
if (!DecodeBase64PSBT(psbtx, psbt_string, error)) {
|
|
throw JSONRPCError(RPC_DESERIALIZATION_ERROR, strprintf("TX decode failed %s", error));
|
|
}
|
|
|
|
if (g_txindex) g_txindex->BlockUntilSyncedToCurrentChain();
|
|
const NodeContext& node = EnsureAnyNodeContext(context);
|
|
|
|
// If we can't find the corresponding full transaction for all of our inputs,
|
|
// this will be used to find just the utxos for the segwit inputs for which
|
|
// the full transaction isn't found
|
|
std::map<COutPoint, Coin> coins;
|
|
|
|
// Fetch previous transactions:
|
|
// First, look in the txindex and the mempool
|
|
for (unsigned int i = 0; i < psbtx.tx->vin.size(); ++i) {
|
|
PSBTInput& psbt_input = psbtx.inputs.at(i);
|
|
const CTxIn& tx_in = psbtx.tx->vin.at(i);
|
|
|
|
// The `non_witness_utxo` is the whole previous transaction
|
|
if (psbt_input.non_witness_utxo) continue;
|
|
|
|
CTransactionRef tx;
|
|
|
|
// Look in the txindex
|
|
if (g_txindex) {
|
|
uint256 block_hash;
|
|
g_txindex->FindTx(tx_in.prevout.hash, block_hash, tx);
|
|
}
|
|
// If we still don't have it look in the mempool
|
|
if (!tx) {
|
|
tx = node.mempool->get(tx_in.prevout.hash);
|
|
}
|
|
if (tx) {
|
|
psbt_input.non_witness_utxo = tx;
|
|
} else {
|
|
coins[tx_in.prevout]; // Create empty map entry keyed by prevout
|
|
}
|
|
}
|
|
|
|
// If we still haven't found all of the inputs, look for the missing ones in the utxo set
|
|
if (!coins.empty()) {
|
|
FindCoins(node, coins);
|
|
for (unsigned int i = 0; i < psbtx.tx->vin.size(); ++i) {
|
|
PSBTInput& input = psbtx.inputs.at(i);
|
|
|
|
// If there are still missing utxos, add them if they were found in the utxo set
|
|
if (!input.non_witness_utxo) {
|
|
const CTxIn& tx_in = psbtx.tx->vin.at(i);
|
|
const Coin& coin = coins.at(tx_in.prevout);
|
|
if (!coin.out.IsNull() && IsSegWitOutput(provider, coin.out.scriptPubKey)) {
|
|
input.witness_utxo = coin.out;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
const PrecomputedTransactionData& txdata = PrecomputePSBTData(psbtx);
|
|
|
|
for (unsigned int i = 0; i < psbtx.tx->vin.size(); ++i) {
|
|
if (PSBTInputSigned(psbtx.inputs.at(i))) {
|
|
continue;
|
|
}
|
|
|
|
// Update script/keypath information using descriptor data.
|
|
// Note that SignPSBTInput does a lot more than just constructing ECDSA signatures.
|
|
// We only actually care about those if our signing provider doesn't hide private
|
|
// information, as is the case with `descriptorprocesspsbt`
|
|
SignPSBTInput(provider, psbtx, /*index=*/i, &txdata, sighash_type, /*out_sigdata=*/nullptr, finalize);
|
|
}
|
|
|
|
// Update script/keypath information using descriptor data.
|
|
for (unsigned int i = 0; i < psbtx.tx->vout.size(); ++i) {
|
|
UpdatePSBTOutput(provider, psbtx, i);
|
|
}
|
|
|
|
RemoveUnnecessaryTransactions(psbtx, /*sighash_type=*/1);
|
|
|
|
return psbtx;
|
|
}
|
|
|
|
static RPCHelpMan getrawtransaction()
|
|
{
|
|
return RPCHelpMan{
|
|
"getrawtransaction",
|
|
|
|
"By default, this call only returns a transaction if it is in the mempool. If -txindex is enabled\n"
|
|
"and no blockhash argument is passed, it will return the transaction if it is in the mempool or any block.\n"
|
|
"If a blockhash argument is passed, it will return the transaction if\n"
|
|
"the specified block is available and the transaction is in that block.\n\n"
|
|
"Hint: Use gettransaction for wallet transactions.\n\n"
|
|
|
|
"If verbosity is 0 or omitted, returns the serialized transaction as a hex-encoded string.\n"
|
|
"If verbosity is 1, returns a JSON Object with information about the transaction.\n"
|
|
"If verbosity is 2, returns a JSON Object with information about the transaction, including fee and prevout information.",
|
|
{
|
|
{"txid", RPCArg::Type::STR_HEX, RPCArg::Optional::NO, "The transaction id"},
|
|
{"verbosity|verbose", RPCArg::Type::NUM, RPCArg::Default{0}, "0 for hex-encoded data, 1 for a JSON object, and 2 for JSON object with fee and prevout",
|
|
RPCArgOptions{.skip_type_check = true}},
|
|
{"blockhash", RPCArg::Type::STR_HEX, RPCArg::Optional::OMITTED, "The block in which to look for the transaction"},
|
|
},
|
|
{
|
|
RPCResult{"if verbosity is not set or set to 0",
|
|
RPCResult::Type::STR, "data", "The serialized transaction as a hex-encoded string for 'txid'"
|
|
},
|
|
RPCResult{"if verbosity is set to 1",
|
|
RPCResult::Type::OBJ, "", "",
|
|
Cat<std::vector<RPCResult>>(
|
|
{
|
|
{RPCResult::Type::BOOL, "in_active_chain", /*optional=*/true, "Whether specified block is in the active chain or not (only present with explicit \"blockhash\" argument)"},
|
|
{RPCResult::Type::STR_HEX, "blockhash", /*optional=*/true, "the block hash"},
|
|
{RPCResult::Type::NUM, "confirmations", /*optional=*/true, "The confirmations"},
|
|
{RPCResult::Type::NUM_TIME, "blocktime", /*optional=*/true, "The block time expressed in " + UNIX_EPOCH_TIME},
|
|
{RPCResult::Type::NUM, "time", /*optional=*/true, "Same as \"blocktime\""},
|
|
{RPCResult::Type::STR_HEX, "hex", "The serialized, hex-encoded data for 'txid'"},
|
|
},
|
|
DecodeTxDoc(/*txid_field_doc=*/"The transaction id (same as provided)")),
|
|
},
|
|
RPCResult{"for verbosity = 2",
|
|
RPCResult::Type::OBJ, "", "",
|
|
{
|
|
{RPCResult::Type::ELISION, "", "Same output as verbosity = 1"},
|
|
{RPCResult::Type::NUM, "fee", /*optional=*/true, "transaction fee in " + CURRENCY_UNIT + ", omitted if block undo data is not available"},
|
|
{RPCResult::Type::ARR, "vin", "",
|
|
{
|
|
{RPCResult::Type::OBJ, "", "utxo being spent",
|
|
{
|
|
{RPCResult::Type::ELISION, "", "Same output as verbosity = 1"},
|
|
{RPCResult::Type::OBJ, "prevout", /*optional=*/true, "The previous output, omitted if block undo data is not available",
|
|
{
|
|
{RPCResult::Type::BOOL, "generated", "Coinbase or not"},
|
|
{RPCResult::Type::NUM, "height", "The height of the prevout"},
|
|
{RPCResult::Type::STR_AMOUNT, "value", "The value in " + CURRENCY_UNIT},
|
|
{RPCResult::Type::OBJ, "scriptPubKey", "", ScriptPubKeyDoc()},
|
|
}},
|
|
}},
|
|
}},
|
|
}},
|
|
},
|
|
RPCExamples{
|
|
HelpExampleCli("getrawtransaction", "\"mytxid\"")
|
|
+ HelpExampleCli("getrawtransaction", "\"mytxid\" 1")
|
|
+ HelpExampleRpc("getrawtransaction", "\"mytxid\", 1")
|
|
+ HelpExampleCli("getrawtransaction", "\"mytxid\" 0 \"myblockhash\"")
|
|
+ HelpExampleCli("getrawtransaction", "\"mytxid\" 1 \"myblockhash\"")
|
|
+ HelpExampleCli("getrawtransaction", "\"mytxid\" 2 \"myblockhash\"")
|
|
},
|
|
[&](const RPCHelpMan& self, const JSONRPCRequest& request) -> UniValue
|
|
{
|
|
const NodeContext& node = EnsureAnyNodeContext(request.context);
|
|
ChainstateManager& chainman = EnsureChainman(node);
|
|
|
|
uint256 hash = ParseHashV(request.params[0], "parameter 1");
|
|
const CBlockIndex* blockindex = nullptr;
|
|
|
|
if (hash == chainman.GetParams().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 (>=0) to indicate verbosity.
|
|
int verbosity{0};
|
|
if (!request.params[1].isNull()) {
|
|
if (request.params[1].isBool()) {
|
|
verbosity = request.params[1].get_bool();
|
|
} else {
|
|
verbosity = request.params[1].getInt<int>();
|
|
}
|
|
}
|
|
|
|
if (!request.params[2].isNull()) {
|
|
LOCK(cs_main);
|
|
|
|
uint256 blockhash = ParseHashV(request.params[2], "parameter 3");
|
|
blockindex = chainman.m_blockman.LookupBlockIndex(blockhash);
|
|
if (!blockindex) {
|
|
throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Block hash not found");
|
|
}
|
|
}
|
|
|
|
bool f_txindex_ready = false;
|
|
if (g_txindex && !blockindex) {
|
|
f_txindex_ready = g_txindex->BlockUntilSyncedToCurrentChain();
|
|
}
|
|
|
|
uint256 hash_block;
|
|
const CTransactionRef tx = GetTransaction(blockindex, node.mempool.get(), hash, hash_block, chainman.m_blockman);
|
|
if (!tx) {
|
|
std::string errmsg;
|
|
if (blockindex) {
|
|
const bool block_has_data = WITH_LOCK(::cs_main, return blockindex->nStatus & BLOCK_HAVE_DATA);
|
|
if (!block_has_data) {
|
|
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 or provide a block hash 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 (verbosity <= 0) {
|
|
return EncodeHexTx(*tx, RPCSerializationFlags());
|
|
}
|
|
|
|
UniValue result(UniValue::VOBJ);
|
|
if (blockindex) {
|
|
LOCK(cs_main);
|
|
result.pushKV("in_active_chain", chainman.ActiveChain().Contains(blockindex));
|
|
}
|
|
// If request is verbosity >= 1 but no blockhash was given, then look up the blockindex
|
|
if (request.params[2].isNull()) {
|
|
LOCK(cs_main);
|
|
blockindex = chainman.m_blockman.LookupBlockIndex(hash_block);
|
|
}
|
|
if (verbosity == 1) {
|
|
TxToJSON(*tx, hash_block, result, chainman.ActiveChainstate());
|
|
return result;
|
|
}
|
|
|
|
CBlockUndo blockUndo;
|
|
CBlock block;
|
|
const bool is_block_pruned{WITH_LOCK(cs_main, return chainman.m_blockman.IsBlockPruned(blockindex))};
|
|
|
|
if (tx->IsCoinBase() ||
|
|
!blockindex || is_block_pruned ||
|
|
!(chainman.m_blockman.UndoReadFromDisk(blockUndo, *blockindex) && chainman.m_blockman.ReadBlockFromDisk(block, *blockindex))) {
|
|
TxToJSON(*tx, hash_block, result, chainman.ActiveChainstate());
|
|
return result;
|
|
}
|
|
|
|
CTxUndo* undoTX {nullptr};
|
|
auto it = std::find_if(block.vtx.begin(), block.vtx.end(), [tx](CTransactionRef t){ return *t == *tx; });
|
|
if (it != block.vtx.end()) {
|
|
// -1 as blockundo does not have coinbase tx
|
|
undoTX = &blockUndo.vtxundo.at(it - block.vtx.begin() - 1);
|
|
}
|
|
TxToJSON(*tx, hash_block, result, chainman.ActiveChainstate(), undoTX, TxVerbosity::SHOW_DETAILS_AND_PREVOUT);
|
|
return result;
|
|
},
|
|
};
|
|
}
|
|
|
|
static RPCHelpMan createrawtransaction()
|
|
{
|
|
return RPCHelpMan{"createrawtransaction",
|
|
"\nCreate 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",
|
|
CreateTxDoc(),
|
|
RPCResult{
|
|
RPCResult::Type::STR_HEX, "transaction", "hex string of the transaction"
|
|
},
|
|
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\\\"}]\"")
|
|
},
|
|
[&](const RPCHelpMan& self, const JSONRPCRequest& request) -> UniValue
|
|
{
|
|
std::optional<bool> rbf;
|
|
if (!request.params[3].isNull()) {
|
|
rbf = request.params[3].get_bool();
|
|
}
|
|
CMutableTransaction rawTx = ConstructTransaction(request.params[0], request.params[1], request.params[2], rbf);
|
|
|
|
return EncodeHexTx(CTransaction(rawTx));
|
|
},
|
|
};
|
|
}
|
|
|
|
static RPCHelpMan decoderawtransaction()
|
|
{
|
|
return RPCHelpMan{"decoderawtransaction",
|
|
"Return a JSON object representing the serialized, hex-encoded transaction.",
|
|
{
|
|
{"hexstring", RPCArg::Type::STR_HEX, RPCArg::Optional::NO, "The transaction hex string"},
|
|
{"iswitness", RPCArg::Type::BOOL, RPCArg::DefaultHint{"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.\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::Type::OBJ, "", "",
|
|
DecodeTxDoc(/*txid_field_doc=*/"The transaction id"),
|
|
},
|
|
RPCExamples{
|
|
HelpExampleCli("decoderawtransaction", "\"hexstring\"")
|
|
+ HelpExampleRpc("decoderawtransaction", "\"hexstring\"")
|
|
},
|
|
[&](const RPCHelpMan& self, const JSONRPCRequest& request) -> UniValue
|
|
{
|
|
CMutableTransaction mtx;
|
|
|
|
bool try_witness = request.params[1].isNull() ? true : request.params[1].get_bool();
|
|
bool try_no_witness = request.params[1].isNull() ? true : !request.params[1].get_bool();
|
|
|
|
if (!DecodeHexTx(mtx, request.params[0].get_str(), try_no_witness, try_witness)) {
|
|
throw JSONRPCError(RPC_DESERIALIZATION_ERROR, "TX decode failed");
|
|
}
|
|
|
|
UniValue result(UniValue::VOBJ);
|
|
TxToUniv(CTransaction(std::move(mtx)), /*block_hash=*/uint256(), /*entry=*/result, /*include_hex=*/false);
|
|
|
|
return result;
|
|
},
|
|
};
|
|
}
|
|
|
|
static RPCHelpMan decodescript()
|
|
{
|
|
return RPCHelpMan{
|
|
"decodescript",
|
|
"\nDecode a hex-encoded script.\n",
|
|
{
|
|
{"hexstring", RPCArg::Type::STR_HEX, RPCArg::Optional::NO, "the hex-encoded script"},
|
|
},
|
|
RPCResult{
|
|
RPCResult::Type::OBJ, "", "",
|
|
{
|
|
{RPCResult::Type::STR, "asm", "Script public key"},
|
|
{RPCResult::Type::STR, "desc", "Inferred descriptor for the script"},
|
|
{RPCResult::Type::STR, "type", "The output type (e.g. " + GetAllOutputTypes() + ")"},
|
|
{RPCResult::Type::STR, "address", /*optional=*/true, "The Bitcoin address (only if a well-defined address exists)"},
|
|
{RPCResult::Type::STR, "p2sh", /*optional=*/true,
|
|
"address of P2SH script wrapping this redeem script (not returned for types that should not be wrapped)"},
|
|
{RPCResult::Type::OBJ, "segwit", /*optional=*/true,
|
|
"Result of a witness script public key wrapping this redeem script (not returned for types that should not be wrapped)",
|
|
{
|
|
{RPCResult::Type::STR, "asm", "String representation of the script public key"},
|
|
{RPCResult::Type::STR_HEX, "hex", "Hex string of the script public key"},
|
|
{RPCResult::Type::STR, "type", "The type of the script public key (e.g. witness_v0_keyhash or witness_v0_scripthash)"},
|
|
{RPCResult::Type::STR, "address", /*optional=*/true, "The Bitcoin address (only if a well-defined address exists)"},
|
|
{RPCResult::Type::STR, "desc", "Inferred descriptor for the script"},
|
|
{RPCResult::Type::STR, "p2sh-segwit", "address of the P2SH script wrapping this witness redeem script"},
|
|
}},
|
|
},
|
|
},
|
|
RPCExamples{
|
|
HelpExampleCli("decodescript", "\"hexstring\"")
|
|
+ HelpExampleRpc("decodescript", "\"hexstring\"")
|
|
},
|
|
[&](const RPCHelpMan& self, const JSONRPCRequest& request) -> UniValue
|
|
{
|
|
UniValue r(UniValue::VOBJ);
|
|
CScript script;
|
|
if (request.params[0].get_str().size() > 0){
|
|
std::vector<unsigned char> scriptData(ParseHexV(request.params[0], "argument"));
|
|
script = CScript(scriptData.begin(), scriptData.end());
|
|
} else {
|
|
// Empty scripts are valid
|
|
}
|
|
ScriptToUniv(script, /*out=*/r, /*include_hex=*/false, /*include_address=*/true);
|
|
|
|
std::vector<std::vector<unsigned char>> solutions_data;
|
|
const TxoutType which_type{Solver(script, solutions_data)};
|
|
|
|
const bool can_wrap{[&] {
|
|
switch (which_type) {
|
|
case TxoutType::MULTISIG:
|
|
case TxoutType::NONSTANDARD:
|
|
case TxoutType::PUBKEY:
|
|
case TxoutType::PUBKEYHASH:
|
|
case TxoutType::WITNESS_V0_KEYHASH:
|
|
case TxoutType::WITNESS_V0_SCRIPTHASH:
|
|
// Can be wrapped if the checks below pass
|
|
break;
|
|
case TxoutType::NULL_DATA:
|
|
case TxoutType::SCRIPTHASH:
|
|
case TxoutType::WITNESS_UNKNOWN:
|
|
case TxoutType::WITNESS_V1_TAPROOT:
|
|
// Should not be wrapped
|
|
return false;
|
|
} // no default case, so the compiler can warn about missing cases
|
|
if (!script.HasValidOps() || script.IsUnspendable()) {
|
|
return false;
|
|
}
|
|
for (CScript::const_iterator it{script.begin()}; it != script.end();) {
|
|
opcodetype op;
|
|
CHECK_NONFATAL(script.GetOp(it, op));
|
|
if (op == OP_CHECKSIGADD || IsOpSuccess(op)) {
|
|
return false;
|
|
}
|
|
}
|
|
return true;
|
|
}()};
|
|
|
|
if (can_wrap) {
|
|
r.pushKV("p2sh", EncodeDestination(ScriptHash(script)));
|
|
// P2SH and witness programs cannot be wrapped in P2WSH, if this script
|
|
// is a witness program, don't return addresses for a segwit programs.
|
|
const bool can_wrap_P2WSH{[&] {
|
|
switch (which_type) {
|
|
case TxoutType::MULTISIG:
|
|
case TxoutType::PUBKEY:
|
|
// Uncompressed pubkeys cannot be used with segwit checksigs.
|
|
// If the script contains an uncompressed pubkey, skip encoding of a segwit program.
|
|
for (const auto& solution : solutions_data) {
|
|
if ((solution.size() != 1) && !CPubKey(solution).IsCompressed()) {
|
|
return false;
|
|
}
|
|
}
|
|
return true;
|
|
case TxoutType::NONSTANDARD:
|
|
case TxoutType::PUBKEYHASH:
|
|
// Can be P2WSH wrapped
|
|
return true;
|
|
case TxoutType::NULL_DATA:
|
|
case TxoutType::SCRIPTHASH:
|
|
case TxoutType::WITNESS_UNKNOWN:
|
|
case TxoutType::WITNESS_V0_KEYHASH:
|
|
case TxoutType::WITNESS_V0_SCRIPTHASH:
|
|
case TxoutType::WITNESS_V1_TAPROOT:
|
|
// Should not be wrapped
|
|
return false;
|
|
} // no default case, so the compiler can warn about missing cases
|
|
NONFATAL_UNREACHABLE();
|
|
}()};
|
|
if (can_wrap_P2WSH) {
|
|
UniValue sr(UniValue::VOBJ);
|
|
CScript segwitScr;
|
|
FlatSigningProvider provider;
|
|
if (which_type == TxoutType::PUBKEY) {
|
|
segwitScr = GetScriptForDestination(WitnessV0KeyHash(Hash160(solutions_data[0])));
|
|
} else if (which_type == TxoutType::PUBKEYHASH) {
|
|
segwitScr = GetScriptForDestination(WitnessV0KeyHash(uint160{solutions_data[0]}));
|
|
} else {
|
|
// Scripts that are not fit for P2WPKH are encoded as P2WSH.
|
|
provider.scripts[CScriptID(script)] = script;
|
|
segwitScr = GetScriptForDestination(WitnessV0ScriptHash(script));
|
|
}
|
|
ScriptToUniv(segwitScr, /*out=*/sr, /*include_hex=*/true, /*include_address=*/true, /*provider=*/&provider);
|
|
sr.pushKV("p2sh-segwit", EncodeDestination(ScriptHash(segwitScr)));
|
|
r.pushKV("segwit", sr);
|
|
}
|
|
}
|
|
|
|
return r;
|
|
},
|
|
};
|
|
}
|
|
|
|
static RPCHelpMan combinerawtransaction()
|
|
{
|
|
return RPCHelpMan{"combinerawtransaction",
|
|
"\nCombine 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, "The hex strings of partially signed transactions",
|
|
{
|
|
{"hexstring", RPCArg::Type::STR_HEX, RPCArg::Optional::OMITTED, "A hex-encoded raw transaction"},
|
|
},
|
|
},
|
|
},
|
|
RPCResult{
|
|
RPCResult::Type::STR, "", "The hex-encoded raw transaction with signature(s)"
|
|
},
|
|
RPCExamples{
|
|
HelpExampleCli("combinerawtransaction", R"('["myhex1", "myhex2", "myhex3"]')")
|
|
},
|
|
[&](const RPCHelpMan& self, const JSONRPCRequest& request) -> UniValue
|
|
{
|
|
|
|
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. Make sure the tx has at least one input.", 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);
|
|
{
|
|
NodeContext& node = EnsureAnyNodeContext(request.context);
|
|
const CTxMemPool& mempool = EnsureMemPool(node);
|
|
ChainstateManager& chainman = EnsureChainman(node);
|
|
LOCK2(cs_main, mempool.cs);
|
|
CCoinsViewCache &viewChain = chainman.ActiveChainstate().CoinsTip();
|
|
CCoinsViewMemPool viewMempool(&viewChain, mempool);
|
|
view.SetBackend(viewMempool); // temporarily switch cache backend to db+mempool view
|
|
|
|
for (const CTxIn& txin : mergedTx.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
|
|
}
|
|
|
|
// Use CTransaction for the constant parts of the
|
|
// transaction to avoid rehashing.
|
|
const CTransaction txConst(mergedTx);
|
|
// Sign what we can:
|
|
for (unsigned int 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;
|
|
|
|
// ... and merge in other signatures:
|
|
for (const CMutableTransaction& txv : txVariants) {
|
|
if (txv.vin.size() > i) {
|
|
sigdata.MergeSignatureData(DataFromTransaction(txv, i, coin.out));
|
|
}
|
|
}
|
|
ProduceSignature(DUMMY_SIGNING_PROVIDER, MutableTransactionSignatureCreator(mergedTx, i, coin.out.nValue, 1), coin.out.scriptPubKey, sigdata);
|
|
|
|
UpdateInput(txin, sigdata);
|
|
}
|
|
|
|
return EncodeHexTx(CTransaction(mergedTx));
|
|
},
|
|
};
|
|
}
|
|
|
|
static RPCHelpMan signrawtransactionwithkey()
|
|
{
|
|
return RPCHelpMan{"signrawtransactionwithkey",
|
|
"\nSign 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, RPCArg::Optional::NO, "The transaction hex string"},
|
|
{"privkeys", RPCArg::Type::ARR, RPCArg::Optional::NO, "The base58-encoded private keys for signing",
|
|
{
|
|
{"privatekey", RPCArg::Type::STR_HEX, RPCArg::Optional::OMITTED, "private key in base58-encoding"},
|
|
},
|
|
},
|
|
{"prevtxs", RPCArg::Type::ARR, RPCArg::Optional::OMITTED, "The 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"},
|
|
{"witnessScript", RPCArg::Type::STR_HEX, RPCArg::Optional::OMITTED, "(required for P2WSH or P2SH-P2WSH) witness script"},
|
|
{"amount", RPCArg::Type::AMOUNT, RPCArg::Optional::OMITTED, "(required for Segwit inputs) the amount spent"},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
{"sighashtype", RPCArg::Type::STR, RPCArg::Default{"DEFAULT for Taproot, ALL otherwise"}, "The signature hash type. Must be one of:\n"
|
|
" \"DEFAULT\"\n"
|
|
" \"ALL\"\n"
|
|
" \"NONE\"\n"
|
|
" \"SINGLE\"\n"
|
|
" \"ALL|ANYONECANPAY\"\n"
|
|
" \"NONE|ANYONECANPAY\"\n"
|
|
" \"SINGLE|ANYONECANPAY\"\n"
|
|
},
|
|
},
|
|
RPCResult{
|
|
RPCResult::Type::OBJ, "", "",
|
|
{
|
|
{RPCResult::Type::STR_HEX, "hex", "The hex-encoded raw transaction with signature(s)"},
|
|
{RPCResult::Type::BOOL, "complete", "If the transaction has a complete set of signatures"},
|
|
{RPCResult::Type::ARR, "errors", /*optional=*/true, "Script verification errors (if there are any)",
|
|
{
|
|
{RPCResult::Type::OBJ, "", "",
|
|
{
|
|
{RPCResult::Type::STR_HEX, "txid", "The hash of the referenced, previous transaction"},
|
|
{RPCResult::Type::NUM, "vout", "The index of the output to spent and used as input"},
|
|
{RPCResult::Type::ARR, "witness", "",
|
|
{
|
|
{RPCResult::Type::STR_HEX, "witness", ""},
|
|
}},
|
|
{RPCResult::Type::STR_HEX, "scriptSig", "The hex-encoded signature script"},
|
|
{RPCResult::Type::NUM, "sequence", "Script sequence number"},
|
|
{RPCResult::Type::STR, "error", "Verification or signing error related to the input"},
|
|
}},
|
|
}},
|
|
}
|
|
},
|
|
RPCExamples{
|
|
HelpExampleCli("signrawtransactionwithkey", "\"myhex\" \"[\\\"key1\\\",\\\"key2\\\"]\"")
|
|
+ HelpExampleRpc("signrawtransactionwithkey", "\"myhex\", \"[\\\"key1\\\",\\\"key2\\\"]\"")
|
|
},
|
|
[&](const RPCHelpMan& self, const JSONRPCRequest& request) -> UniValue
|
|
{
|
|
CMutableTransaction mtx;
|
|
if (!DecodeHexTx(mtx, request.params[0].get_str())) {
|
|
throw JSONRPCError(RPC_DESERIALIZATION_ERROR, "TX decode failed. Make sure the tx has at least one input.");
|
|
}
|
|
|
|
FillableSigningProvider keystore;
|
|
const UniValue& keys = request.params[1].get_array();
|
|
for (unsigned int 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) {
|
|
coins[txin.prevout]; // Create empty map entry keyed by prevout.
|
|
}
|
|
NodeContext& node = EnsureAnyNodeContext(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;
|
|
},
|
|
};
|
|
}
|
|
|
|
const RPCResult decodepsbt_inputs{
|
|
RPCResult::Type::ARR, "inputs", "",
|
|
{
|
|
{RPCResult::Type::OBJ, "", "",
|
|
{
|
|
{RPCResult::Type::OBJ, "non_witness_utxo", /*optional=*/true, "Decoded network transaction for non-witness UTXOs",
|
|
{
|
|
{RPCResult::Type::ELISION, "",""},
|
|
}},
|
|
{RPCResult::Type::OBJ, "witness_utxo", /*optional=*/true, "Transaction output for witness UTXOs",
|
|
{
|
|
{RPCResult::Type::NUM, "amount", "The value in " + CURRENCY_UNIT},
|
|
{RPCResult::Type::OBJ, "scriptPubKey", "",
|
|
{
|
|
{RPCResult::Type::STR, "asm", "Disassembly of the public key script"},
|
|
{RPCResult::Type::STR, "desc", "Inferred descriptor for the output"},
|
|
{RPCResult::Type::STR_HEX, "hex", "The raw public key script bytes, hex-encoded"},
|
|
{RPCResult::Type::STR, "type", "The type, eg 'pubkeyhash'"},
|
|
{RPCResult::Type::STR, "address", /*optional=*/true, "The Bitcoin address (only if a well-defined address exists)"},
|
|
}},
|
|
}},
|
|
{RPCResult::Type::OBJ_DYN, "partial_signatures", /*optional=*/true, "",
|
|
{
|
|
{RPCResult::Type::STR, "pubkey", "The public key and signature that corresponds to it."},
|
|
}},
|
|
{RPCResult::Type::STR, "sighash", /*optional=*/true, "The sighash type to be used"},
|
|
{RPCResult::Type::OBJ, "redeem_script", /*optional=*/true, "",
|
|
{
|
|
{RPCResult::Type::STR, "asm", "Disassembly of the redeem script"},
|
|
{RPCResult::Type::STR_HEX, "hex", "The raw redeem script bytes, hex-encoded"},
|
|
{RPCResult::Type::STR, "type", "The type, eg 'pubkeyhash'"},
|
|
}},
|
|
{RPCResult::Type::OBJ, "witness_script", /*optional=*/true, "",
|
|
{
|
|
{RPCResult::Type::STR, "asm", "Disassembly of the witness script"},
|
|
{RPCResult::Type::STR_HEX, "hex", "The raw witness script bytes, hex-encoded"},
|
|
{RPCResult::Type::STR, "type", "The type, eg 'pubkeyhash'"},
|
|
}},
|
|
{RPCResult::Type::ARR, "bip32_derivs", /*optional=*/true, "",
|
|
{
|
|
{RPCResult::Type::OBJ, "", "",
|
|
{
|
|
{RPCResult::Type::STR, "pubkey", "The public key with the derivation path as the value."},
|
|
{RPCResult::Type::STR, "master_fingerprint", "The fingerprint of the master key"},
|
|
{RPCResult::Type::STR, "path", "The path"},
|
|
}},
|
|
}},
|
|
{RPCResult::Type::OBJ, "final_scriptSig", /*optional=*/true, "",
|
|
{
|
|
{RPCResult::Type::STR, "asm", "Disassembly of the final signature script"},
|
|
{RPCResult::Type::STR_HEX, "hex", "The raw final signature script bytes, hex-encoded"},
|
|
}},
|
|
{RPCResult::Type::ARR, "final_scriptwitness", /*optional=*/true, "",
|
|
{
|
|
{RPCResult::Type::STR_HEX, "", "hex-encoded witness data (if any)"},
|
|
}},
|
|
{RPCResult::Type::OBJ_DYN, "ripemd160_preimages", /*optional=*/ true, "",
|
|
{
|
|
{RPCResult::Type::STR, "hash", "The hash and preimage that corresponds to it."},
|
|
}},
|
|
{RPCResult::Type::OBJ_DYN, "sha256_preimages", /*optional=*/ true, "",
|
|
{
|
|
{RPCResult::Type::STR, "hash", "The hash and preimage that corresponds to it."},
|
|
}},
|
|
{RPCResult::Type::OBJ_DYN, "hash160_preimages", /*optional=*/ true, "",
|
|
{
|
|
{RPCResult::Type::STR, "hash", "The hash and preimage that corresponds to it."},
|
|
}},
|
|
{RPCResult::Type::OBJ_DYN, "hash256_preimages", /*optional=*/ true, "",
|
|
{
|
|
{RPCResult::Type::STR, "hash", "The hash and preimage that corresponds to it."},
|
|
}},
|
|
{RPCResult::Type::STR_HEX, "taproot_key_path_sig", /*optional=*/ true, "hex-encoded signature for the Taproot key path spend"},
|
|
{RPCResult::Type::ARR, "taproot_script_path_sigs", /*optional=*/ true, "",
|
|
{
|
|
{RPCResult::Type::OBJ, "signature", /*optional=*/ true, "The signature for the pubkey and leaf hash combination",
|
|
{
|
|
{RPCResult::Type::STR, "pubkey", "The x-only pubkey for this signature"},
|
|
{RPCResult::Type::STR, "leaf_hash", "The leaf hash for this signature"},
|
|
{RPCResult::Type::STR, "sig", "The signature itself"},
|
|
}},
|
|
}},
|
|
{RPCResult::Type::ARR, "taproot_scripts", /*optional=*/ true, "",
|
|
{
|
|
{RPCResult::Type::OBJ, "", "",
|
|
{
|
|
{RPCResult::Type::STR_HEX, "script", "A leaf script"},
|
|
{RPCResult::Type::NUM, "leaf_ver", "The version number for the leaf script"},
|
|
{RPCResult::Type::ARR, "control_blocks", "The control blocks for this script",
|
|
{
|
|
{RPCResult::Type::STR_HEX, "control_block", "A hex-encoded control block for this script"},
|
|
}},
|
|
}},
|
|
}},
|
|
{RPCResult::Type::ARR, "taproot_bip32_derivs", /*optional=*/ true, "",
|
|
{
|
|
{RPCResult::Type::OBJ, "", "",
|
|
{
|
|
{RPCResult::Type::STR, "pubkey", "The x-only public key this path corresponds to"},
|
|
{RPCResult::Type::STR, "master_fingerprint", "The fingerprint of the master key"},
|
|
{RPCResult::Type::STR, "path", "The path"},
|
|
{RPCResult::Type::ARR, "leaf_hashes", "The hashes of the leaves this pubkey appears in",
|
|
{
|
|
{RPCResult::Type::STR_HEX, "hash", "The hash of a leaf this pubkey appears in"},
|
|
}},
|
|
}},
|
|
}},
|
|
{RPCResult::Type::STR_HEX, "taproot_internal_key", /*optional=*/ true, "The hex-encoded Taproot x-only internal key"},
|
|
{RPCResult::Type::STR_HEX, "taproot_merkle_root", /*optional=*/ true, "The hex-encoded Taproot merkle root"},
|
|
{RPCResult::Type::OBJ_DYN, "unknown", /*optional=*/ true, "The unknown input fields",
|
|
{
|
|
{RPCResult::Type::STR_HEX, "key", "(key-value pair) An unknown key-value pair"},
|
|
}},
|
|
{RPCResult::Type::ARR, "proprietary", /*optional=*/true, "The input proprietary map",
|
|
{
|
|
{RPCResult::Type::OBJ, "", "",
|
|
{
|
|
{RPCResult::Type::STR_HEX, "identifier", "The hex string for the proprietary identifier"},
|
|
{RPCResult::Type::NUM, "subtype", "The number for the subtype"},
|
|
{RPCResult::Type::STR_HEX, "key", "The hex for the key"},
|
|
{RPCResult::Type::STR_HEX, "value", "The hex for the value"},
|
|
}},
|
|
}},
|
|
}},
|
|
}
|
|
};
|
|
|
|
const RPCResult decodepsbt_outputs{
|
|
RPCResult::Type::ARR, "outputs", "",
|
|
{
|
|
{RPCResult::Type::OBJ, "", "",
|
|
{
|
|
{RPCResult::Type::OBJ, "redeem_script", /*optional=*/true, "",
|
|
{
|
|
{RPCResult::Type::STR, "asm", "Disassembly of the redeem script"},
|
|
{RPCResult::Type::STR_HEX, "hex", "The raw redeem script bytes, hex-encoded"},
|
|
{RPCResult::Type::STR, "type", "The type, eg 'pubkeyhash'"},
|
|
}},
|
|
{RPCResult::Type::OBJ, "witness_script", /*optional=*/true, "",
|
|
{
|
|
{RPCResult::Type::STR, "asm", "Disassembly of the witness script"},
|
|
{RPCResult::Type::STR_HEX, "hex", "The raw witness script bytes, hex-encoded"},
|
|
{RPCResult::Type::STR, "type", "The type, eg 'pubkeyhash'"},
|
|
}},
|
|
{RPCResult::Type::ARR, "bip32_derivs", /*optional=*/true, "",
|
|
{
|
|
{RPCResult::Type::OBJ, "", "",
|
|
{
|
|
{RPCResult::Type::STR, "pubkey", "The public key this path corresponds to"},
|
|
{RPCResult::Type::STR, "master_fingerprint", "The fingerprint of the master key"},
|
|
{RPCResult::Type::STR, "path", "The path"},
|
|
}},
|
|
}},
|
|
{RPCResult::Type::STR_HEX, "taproot_internal_key", /*optional=*/ true, "The hex-encoded Taproot x-only internal key"},
|
|
{RPCResult::Type::ARR, "taproot_tree", /*optional=*/ true, "The tuples that make up the Taproot tree, in depth first search order",
|
|
{
|
|
{RPCResult::Type::OBJ, "tuple", /*optional=*/ true, "A single leaf script in the taproot tree",
|
|
{
|
|
{RPCResult::Type::NUM, "depth", "The depth of this element in the tree"},
|
|
{RPCResult::Type::NUM, "leaf_ver", "The version of this leaf"},
|
|
{RPCResult::Type::STR, "script", "The hex-encoded script itself"},
|
|
}},
|
|
}},
|
|
{RPCResult::Type::ARR, "taproot_bip32_derivs", /*optional=*/ true, "",
|
|
{
|
|
{RPCResult::Type::OBJ, "", "",
|
|
{
|
|
{RPCResult::Type::STR, "pubkey", "The x-only public key this path corresponds to"},
|
|
{RPCResult::Type::STR, "master_fingerprint", "The fingerprint of the master key"},
|
|
{RPCResult::Type::STR, "path", "The path"},
|
|
{RPCResult::Type::ARR, "leaf_hashes", "The hashes of the leaves this pubkey appears in",
|
|
{
|
|
{RPCResult::Type::STR_HEX, "hash", "The hash of a leaf this pubkey appears in"},
|
|
}},
|
|
}},
|
|
}},
|
|
{RPCResult::Type::OBJ_DYN, "unknown", /*optional=*/true, "The unknown output fields",
|
|
{
|
|
{RPCResult::Type::STR_HEX, "key", "(key-value pair) An unknown key-value pair"},
|
|
}},
|
|
{RPCResult::Type::ARR, "proprietary", /*optional=*/true, "The output proprietary map",
|
|
{
|
|
{RPCResult::Type::OBJ, "", "",
|
|
{
|
|
{RPCResult::Type::STR_HEX, "identifier", "The hex string for the proprietary identifier"},
|
|
{RPCResult::Type::NUM, "subtype", "The number for the subtype"},
|
|
{RPCResult::Type::STR_HEX, "key", "The hex for the key"},
|
|
{RPCResult::Type::STR_HEX, "value", "The hex for the value"},
|
|
}},
|
|
}},
|
|
}},
|
|
}
|
|
};
|
|
|
|
static RPCHelpMan decodepsbt()
|
|
{
|
|
return RPCHelpMan{
|
|
"decodepsbt",
|
|
"Return a JSON object representing the serialized, base64-encoded partially signed Bitcoin transaction.",
|
|
{
|
|
{"psbt", RPCArg::Type::STR, RPCArg::Optional::NO, "The PSBT base64 string"},
|
|
},
|
|
RPCResult{
|
|
RPCResult::Type::OBJ, "", "",
|
|
{
|
|
{RPCResult::Type::OBJ, "tx", "The decoded network-serialized unsigned transaction.",
|
|
{
|
|
{RPCResult::Type::ELISION, "", "The layout is the same as the output of decoderawtransaction."},
|
|
}},
|
|
{RPCResult::Type::ARR, "global_xpubs", "",
|
|
{
|
|
{RPCResult::Type::OBJ, "", "",
|
|
{
|
|
{RPCResult::Type::STR, "xpub", "The extended public key this path corresponds to"},
|
|
{RPCResult::Type::STR_HEX, "master_fingerprint", "The fingerprint of the master key"},
|
|
{RPCResult::Type::STR, "path", "The path"},
|
|
}},
|
|
}},
|
|
{RPCResult::Type::NUM, "psbt_version", "The PSBT version number. Not to be confused with the unsigned transaction version"},
|
|
{RPCResult::Type::ARR, "proprietary", "The global proprietary map",
|
|
{
|
|
{RPCResult::Type::OBJ, "", "",
|
|
{
|
|
{RPCResult::Type::STR_HEX, "identifier", "The hex string for the proprietary identifier"},
|
|
{RPCResult::Type::NUM, "subtype", "The number for the subtype"},
|
|
{RPCResult::Type::STR_HEX, "key", "The hex for the key"},
|
|
{RPCResult::Type::STR_HEX, "value", "The hex for the value"},
|
|
}},
|
|
}},
|
|
{RPCResult::Type::OBJ_DYN, "unknown", "The unknown global fields",
|
|
{
|
|
{RPCResult::Type::STR_HEX, "key", "(key-value pair) An unknown key-value pair"},
|
|
}},
|
|
decodepsbt_inputs,
|
|
decodepsbt_outputs,
|
|
{RPCResult::Type::STR_AMOUNT, "fee", /*optional=*/true, "The transaction fee paid if all UTXOs slots in the PSBT have been filled."},
|
|
}
|
|
},
|
|
RPCExamples{
|
|
HelpExampleCli("decodepsbt", "\"psbt\"")
|
|
},
|
|
[&](const RPCHelpMan& self, const JSONRPCRequest& request) -> UniValue
|
|
{
|
|
// 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), /*block_hash=*/uint256(), /*entry=*/tx_univ, /*include_hex=*/false);
|
|
result.pushKV("tx", tx_univ);
|
|
|
|
// Add the global xpubs
|
|
UniValue global_xpubs(UniValue::VARR);
|
|
for (std::pair<KeyOriginInfo, std::set<CExtPubKey>> xpub_pair : psbtx.m_xpubs) {
|
|
for (auto& xpub : xpub_pair.second) {
|
|
std::vector<unsigned char> ser_xpub;
|
|
ser_xpub.assign(BIP32_EXTKEY_WITH_VERSION_SIZE, 0);
|
|
xpub.EncodeWithVersion(ser_xpub.data());
|
|
|
|
UniValue keypath(UniValue::VOBJ);
|
|
keypath.pushKV("xpub", EncodeBase58Check(ser_xpub));
|
|
keypath.pushKV("master_fingerprint", HexStr(Span<unsigned char>(xpub_pair.first.fingerprint, xpub_pair.first.fingerprint + 4)));
|
|
keypath.pushKV("path", WriteHDKeypath(xpub_pair.first.path));
|
|
global_xpubs.push_back(keypath);
|
|
}
|
|
}
|
|
result.pushKV("global_xpubs", global_xpubs);
|
|
|
|
// PSBT version
|
|
result.pushKV("psbt_version", static_cast<uint64_t>(psbtx.GetVersion()));
|
|
|
|
// Proprietary
|
|
UniValue proprietary(UniValue::VARR);
|
|
for (const auto& entry : psbtx.m_proprietary) {
|
|
UniValue this_prop(UniValue::VOBJ);
|
|
this_prop.pushKV("identifier", HexStr(entry.identifier));
|
|
this_prop.pushKV("subtype", entry.subtype);
|
|
this_prop.pushKV("key", HexStr(entry.key));
|
|
this_prop.pushKV("value", HexStr(entry.value));
|
|
proprietary.push_back(this_prop);
|
|
}
|
|
result.pushKV("proprietary", proprietary);
|
|
|
|
// Unknown data
|
|
UniValue unknowns(UniValue::VOBJ);
|
|
for (auto entry : psbtx.unknown) {
|
|
unknowns.pushKV(HexStr(entry.first), HexStr(entry.second));
|
|
}
|
|
result.pushKV("unknown", unknowns);
|
|
|
|
// inputs
|
|
CAmount total_in = 0;
|
|
bool have_all_utxos = true;
|
|
UniValue inputs(UniValue::VARR);
|
|
for (unsigned int i = 0; i < psbtx.inputs.size(); ++i) {
|
|
const PSBTInput& input = psbtx.inputs[i];
|
|
UniValue in(UniValue::VOBJ);
|
|
// UTXOs
|
|
bool have_a_utxo = false;
|
|
CTxOut txout;
|
|
if (!input.witness_utxo.IsNull()) {
|
|
txout = input.witness_utxo;
|
|
|
|
UniValue o(UniValue::VOBJ);
|
|
ScriptToUniv(txout.scriptPubKey, /*out=*/o, /*include_hex=*/true, /*include_address=*/true);
|
|
|
|
UniValue out(UniValue::VOBJ);
|
|
out.pushKV("amount", ValueFromAmount(txout.nValue));
|
|
out.pushKV("scriptPubKey", o);
|
|
|
|
in.pushKV("witness_utxo", out);
|
|
|
|
have_a_utxo = true;
|
|
}
|
|
if (input.non_witness_utxo) {
|
|
txout = input.non_witness_utxo->vout[psbtx.tx->vin[i].prevout.n];
|
|
|
|
UniValue non_wit(UniValue::VOBJ);
|
|
TxToUniv(*input.non_witness_utxo, /*block_hash=*/uint256(), /*entry=*/non_wit, /*include_hex=*/false);
|
|
in.pushKV("non_witness_utxo", non_wit);
|
|
|
|
have_a_utxo = true;
|
|
}
|
|
if (have_a_utxo) {
|
|
if (MoneyRange(txout.nValue) && MoneyRange(total_in + txout.nValue)) {
|
|
total_in += txout.nValue;
|
|
} else {
|
|
// Hack to just not show fee later
|
|
have_all_utxos = false;
|
|
}
|
|
} 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
|
|
if (input.sighash_type != std::nullopt) {
|
|
in.pushKV("sighash", SighashToStr((unsigned char)*input.sighash_type));
|
|
}
|
|
|
|
// Redeem script and witness script
|
|
if (!input.redeem_script.empty()) {
|
|
UniValue r(UniValue::VOBJ);
|
|
ScriptToUniv(input.redeem_script, /*out=*/r);
|
|
in.pushKV("redeem_script", r);
|
|
}
|
|
if (!input.witness_script.empty()) {
|
|
UniValue r(UniValue::VOBJ);
|
|
ScriptToUniv(input.witness_script, /*out=*/r);
|
|
in.pushKV("witness_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 and scriptwitness
|
|
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);
|
|
}
|
|
if (!input.final_script_witness.IsNull()) {
|
|
UniValue txinwitness(UniValue::VARR);
|
|
for (const auto& item : input.final_script_witness.stack) {
|
|
txinwitness.push_back(HexStr(item));
|
|
}
|
|
in.pushKV("final_scriptwitness", txinwitness);
|
|
}
|
|
|
|
// Ripemd160 hash preimages
|
|
if (!input.ripemd160_preimages.empty()) {
|
|
UniValue ripemd160_preimages(UniValue::VOBJ);
|
|
for (const auto& [hash, preimage] : input.ripemd160_preimages) {
|
|
ripemd160_preimages.pushKV(HexStr(hash), HexStr(preimage));
|
|
}
|
|
in.pushKV("ripemd160_preimages", ripemd160_preimages);
|
|
}
|
|
|
|
// Sha256 hash preimages
|
|
if (!input.sha256_preimages.empty()) {
|
|
UniValue sha256_preimages(UniValue::VOBJ);
|
|
for (const auto& [hash, preimage] : input.sha256_preimages) {
|
|
sha256_preimages.pushKV(HexStr(hash), HexStr(preimage));
|
|
}
|
|
in.pushKV("sha256_preimages", sha256_preimages);
|
|
}
|
|
|
|
// Hash160 hash preimages
|
|
if (!input.hash160_preimages.empty()) {
|
|
UniValue hash160_preimages(UniValue::VOBJ);
|
|
for (const auto& [hash, preimage] : input.hash160_preimages) {
|
|
hash160_preimages.pushKV(HexStr(hash), HexStr(preimage));
|
|
}
|
|
in.pushKV("hash160_preimages", hash160_preimages);
|
|
}
|
|
|
|
// Hash256 hash preimages
|
|
if (!input.hash256_preimages.empty()) {
|
|
UniValue hash256_preimages(UniValue::VOBJ);
|
|
for (const auto& [hash, preimage] : input.hash256_preimages) {
|
|
hash256_preimages.pushKV(HexStr(hash), HexStr(preimage));
|
|
}
|
|
in.pushKV("hash256_preimages", hash256_preimages);
|
|
}
|
|
|
|
// Taproot key path signature
|
|
if (!input.m_tap_key_sig.empty()) {
|
|
in.pushKV("taproot_key_path_sig", HexStr(input.m_tap_key_sig));
|
|
}
|
|
|
|
// Taproot script path signatures
|
|
if (!input.m_tap_script_sigs.empty()) {
|
|
UniValue script_sigs(UniValue::VARR);
|
|
for (const auto& [pubkey_leaf, sig] : input.m_tap_script_sigs) {
|
|
const auto& [xonly, leaf_hash] = pubkey_leaf;
|
|
UniValue sigobj(UniValue::VOBJ);
|
|
sigobj.pushKV("pubkey", HexStr(xonly));
|
|
sigobj.pushKV("leaf_hash", HexStr(leaf_hash));
|
|
sigobj.pushKV("sig", HexStr(sig));
|
|
script_sigs.push_back(sigobj);
|
|
}
|
|
in.pushKV("taproot_script_path_sigs", script_sigs);
|
|
}
|
|
|
|
// Taproot leaf scripts
|
|
if (!input.m_tap_scripts.empty()) {
|
|
UniValue tap_scripts(UniValue::VARR);
|
|
for (const auto& [leaf, control_blocks] : input.m_tap_scripts) {
|
|
const auto& [script, leaf_ver] = leaf;
|
|
UniValue script_info(UniValue::VOBJ);
|
|
script_info.pushKV("script", HexStr(script));
|
|
script_info.pushKV("leaf_ver", leaf_ver);
|
|
UniValue control_blocks_univ(UniValue::VARR);
|
|
for (const auto& control_block : control_blocks) {
|
|
control_blocks_univ.push_back(HexStr(control_block));
|
|
}
|
|
script_info.pushKV("control_blocks", control_blocks_univ);
|
|
tap_scripts.push_back(script_info);
|
|
}
|
|
in.pushKV("taproot_scripts", tap_scripts);
|
|
}
|
|
|
|
// Taproot bip32 keypaths
|
|
if (!input.m_tap_bip32_paths.empty()) {
|
|
UniValue keypaths(UniValue::VARR);
|
|
for (const auto& [xonly, leaf_origin] : input.m_tap_bip32_paths) {
|
|
const auto& [leaf_hashes, origin] = leaf_origin;
|
|
UniValue path_obj(UniValue::VOBJ);
|
|
path_obj.pushKV("pubkey", HexStr(xonly));
|
|
path_obj.pushKV("master_fingerprint", strprintf("%08x", ReadBE32(origin.fingerprint)));
|
|
path_obj.pushKV("path", WriteHDKeypath(origin.path));
|
|
UniValue leaf_hashes_arr(UniValue::VARR);
|
|
for (const auto& leaf_hash : leaf_hashes) {
|
|
leaf_hashes_arr.push_back(HexStr(leaf_hash));
|
|
}
|
|
path_obj.pushKV("leaf_hashes", leaf_hashes_arr);
|
|
keypaths.push_back(path_obj);
|
|
}
|
|
in.pushKV("taproot_bip32_derivs", keypaths);
|
|
}
|
|
|
|
// Taproot internal key
|
|
if (!input.m_tap_internal_key.IsNull()) {
|
|
in.pushKV("taproot_internal_key", HexStr(input.m_tap_internal_key));
|
|
}
|
|
|
|
// Write taproot merkle root
|
|
if (!input.m_tap_merkle_root.IsNull()) {
|
|
in.pushKV("taproot_merkle_root", HexStr(input.m_tap_merkle_root));
|
|
}
|
|
|
|
// Proprietary
|
|
if (!input.m_proprietary.empty()) {
|
|
UniValue proprietary(UniValue::VARR);
|
|
for (const auto& entry : input.m_proprietary) {
|
|
UniValue this_prop(UniValue::VOBJ);
|
|
this_prop.pushKV("identifier", HexStr(entry.identifier));
|
|
this_prop.pushKV("subtype", entry.subtype);
|
|
this_prop.pushKV("key", HexStr(entry.key));
|
|
this_prop.pushKV("value", HexStr(entry.value));
|
|
proprietary.push_back(this_prop);
|
|
}
|
|
in.pushKV("proprietary", proprietary);
|
|
}
|
|
|
|
// 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
|
|
CAmount output_value = 0;
|
|
UniValue outputs(UniValue::VARR);
|
|
for (unsigned int i = 0; i < psbtx.outputs.size(); ++i) {
|
|
const PSBTOutput& output = psbtx.outputs[i];
|
|
UniValue out(UniValue::VOBJ);
|
|
// Redeem script and witness script
|
|
if (!output.redeem_script.empty()) {
|
|
UniValue r(UniValue::VOBJ);
|
|
ScriptToUniv(output.redeem_script, /*out=*/r);
|
|
out.pushKV("redeem_script", r);
|
|
}
|
|
if (!output.witness_script.empty()) {
|
|
UniValue r(UniValue::VOBJ);
|
|
ScriptToUniv(output.witness_script, /*out=*/r);
|
|
out.pushKV("witness_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);
|
|
}
|
|
|
|
// Taproot internal key
|
|
if (!output.m_tap_internal_key.IsNull()) {
|
|
out.pushKV("taproot_internal_key", HexStr(output.m_tap_internal_key));
|
|
}
|
|
|
|
// Taproot tree
|
|
if (!output.m_tap_tree.empty()) {
|
|
UniValue tree(UniValue::VARR);
|
|
for (const auto& [depth, leaf_ver, script] : output.m_tap_tree) {
|
|
UniValue elem(UniValue::VOBJ);
|
|
elem.pushKV("depth", (int)depth);
|
|
elem.pushKV("leaf_ver", (int)leaf_ver);
|
|
elem.pushKV("script", HexStr(script));
|
|
tree.push_back(elem);
|
|
}
|
|
out.pushKV("taproot_tree", tree);
|
|
}
|
|
|
|
// Taproot bip32 keypaths
|
|
if (!output.m_tap_bip32_paths.empty()) {
|
|
UniValue keypaths(UniValue::VARR);
|
|
for (const auto& [xonly, leaf_origin] : output.m_tap_bip32_paths) {
|
|
const auto& [leaf_hashes, origin] = leaf_origin;
|
|
UniValue path_obj(UniValue::VOBJ);
|
|
path_obj.pushKV("pubkey", HexStr(xonly));
|
|
path_obj.pushKV("master_fingerprint", strprintf("%08x", ReadBE32(origin.fingerprint)));
|
|
path_obj.pushKV("path", WriteHDKeypath(origin.path));
|
|
UniValue leaf_hashes_arr(UniValue::VARR);
|
|
for (const auto& leaf_hash : leaf_hashes) {
|
|
leaf_hashes_arr.push_back(HexStr(leaf_hash));
|
|
}
|
|
path_obj.pushKV("leaf_hashes", leaf_hashes_arr);
|
|
keypaths.push_back(path_obj);
|
|
}
|
|
out.pushKV("taproot_bip32_derivs", keypaths);
|
|
}
|
|
|
|
// Proprietary
|
|
if (!output.m_proprietary.empty()) {
|
|
UniValue proprietary(UniValue::VARR);
|
|
for (const auto& entry : output.m_proprietary) {
|
|
UniValue this_prop(UniValue::VOBJ);
|
|
this_prop.pushKV("identifier", HexStr(entry.identifier));
|
|
this_prop.pushKV("subtype", entry.subtype);
|
|
this_prop.pushKV("key", HexStr(entry.key));
|
|
this_prop.pushKV("value", HexStr(entry.value));
|
|
proprietary.push_back(this_prop);
|
|
}
|
|
out.pushKV("proprietary", proprietary);
|
|
}
|
|
|
|
// 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
|
|
if (MoneyRange(psbtx.tx->vout[i].nValue) && MoneyRange(output_value + psbtx.tx->vout[i].nValue)) {
|
|
output_value += psbtx.tx->vout[i].nValue;
|
|
} else {
|
|
// Hack to just not show fee later
|
|
have_all_utxos = false;
|
|
}
|
|
}
|
|
result.pushKV("outputs", outputs);
|
|
if (have_all_utxos) {
|
|
result.pushKV("fee", ValueFromAmount(total_in - output_value));
|
|
}
|
|
|
|
return result;
|
|
},
|
|
};
|
|
}
|
|
|
|
static RPCHelpMan combinepsbt()
|
|
{
|
|
return RPCHelpMan{"combinepsbt",
|
|
"\nCombine multiple partially signed Bitcoin transactions into one transaction.\n"
|
|
"Implements the Combiner role.\n",
|
|
{
|
|
{"txs", RPCArg::Type::ARR, RPCArg::Optional::NO, "The base64 strings of partially signed transactions",
|
|
{
|
|
{"psbt", RPCArg::Type::STR, RPCArg::Optional::OMITTED, "A base64 string of a PSBT"},
|
|
},
|
|
},
|
|
},
|
|
RPCResult{
|
|
RPCResult::Type::STR, "", "The base64-encoded partially signed transaction"
|
|
},
|
|
RPCExamples{
|
|
HelpExampleCli("combinepsbt", R"('["mybase64_1", "mybase64_2", "mybase64_3"]')")
|
|
},
|
|
[&](const RPCHelpMan& self, const JSONRPCRequest& request) -> UniValue
|
|
{
|
|
// 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 (unsigned int 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(ssTx);
|
|
},
|
|
};
|
|
}
|
|
|
|
static RPCHelpMan finalizepsbt()
|
|
{
|
|
return 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_scriptSig and final_scriptWitness fields 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, RPCArg::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{
|
|
RPCResult::Type::OBJ, "", "",
|
|
{
|
|
{RPCResult::Type::STR, "psbt", /*optional=*/true, "The base64-encoded partially signed transaction if not extracted"},
|
|
{RPCResult::Type::STR_HEX, "hex", /*optional=*/true, "The hex-encoded network transaction if extracted"},
|
|
{RPCResult::Type::BOOL, "complete", "If the transaction has a complete set of signatures"},
|
|
}
|
|
},
|
|
RPCExamples{
|
|
HelpExampleCli("finalizepsbt", "\"psbt\"")
|
|
},
|
|
[&](const RPCHelpMan& self, const JSONRPCRequest& request) -> UniValue
|
|
{
|
|
// 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);
|
|
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 RPCHelpMan createpsbt()
|
|
{
|
|
return RPCHelpMan{"createpsbt",
|
|
"\nCreates a transaction in the Partially Signed Transaction format.\n"
|
|
"Implements the Creator role.\n",
|
|
CreateTxDoc(),
|
|
RPCResult{
|
|
RPCResult::Type::STR, "", "The resulting raw transaction (base64-encoded string)"
|
|
},
|
|
RPCExamples{
|
|
HelpExampleCli("createpsbt", "\"[{\\\"txid\\\":\\\"myid\\\",\\\"vout\\\":0}]\" \"[{\\\"data\\\":\\\"00010203\\\"}]\"")
|
|
},
|
|
[&](const RPCHelpMan& self, const JSONRPCRequest& request) -> UniValue
|
|
{
|
|
|
|
std::optional<bool> rbf;
|
|
if (!request.params[3].isNull()) {
|
|
rbf = request.params[3].get_bool();
|
|
}
|
|
CMutableTransaction rawTx = ConstructTransaction(request.params[0], request.params[1], request.params[2], rbf);
|
|
|
|
// Make a blank psbt
|
|
PartiallySignedTransaction psbtx;
|
|
psbtx.tx = rawTx;
|
|
for (unsigned int i = 0; i < rawTx.vin.size(); ++i) {
|
|
psbtx.inputs.push_back(PSBTInput());
|
|
}
|
|
for (unsigned int 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(ssTx);
|
|
},
|
|
};
|
|
}
|
|
|
|
static RPCHelpMan converttopsbt()
|
|
{
|
|
return RPCHelpMan{"converttopsbt",
|
|
"\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",
|
|
{
|
|
{"hexstring", RPCArg::Type::STR_HEX, RPCArg::Optional::NO, "The hex string of a raw transaction"},
|
|
{"permitsigdata", RPCArg::Type::BOOL, RPCArg::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."},
|
|
{"iswitness", RPCArg::Type::BOOL, RPCArg::DefaultHint{"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.\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::Type::STR, "", "The resulting raw transaction (base64-encoded string)"
|
|
},
|
|
RPCExamples{
|
|
"\nCreate a transaction\n"
|
|
+ HelpExampleCli("createrawtransaction", "\"[{\\\"txid\\\":\\\"myid\\\",\\\"vout\\\":0}]\" \"[{\\\"data\\\":\\\"00010203\\\"}]\"") +
|
|
"\nConvert the transaction to a PSBT\n"
|
|
+ HelpExampleCli("converttopsbt", "\"rawtransaction\"")
|
|
},
|
|
[&](const RPCHelpMan& self, const JSONRPCRequest& request) -> UniValue
|
|
{
|
|
// parse hex string from parameter
|
|
CMutableTransaction tx;
|
|
bool permitsigdata = request.params[1].isNull() ? false : request.params[1].get_bool();
|
|
bool witness_specified = !request.params[2].isNull();
|
|
bool iswitness = witness_specified ? request.params[2].get_bool() : false;
|
|
const bool try_witness = witness_specified ? iswitness : true;
|
|
const bool try_no_witness = witness_specified ? !iswitness : true;
|
|
if (!DecodeHexTx(tx, request.params[0].get_str(), try_no_witness, try_witness)) {
|
|
throw JSONRPCError(RPC_DESERIALIZATION_ERROR, "TX decode failed");
|
|
}
|
|
|
|
// Remove all scriptSigs and scriptWitnesses from inputs
|
|
for (CTxIn& input : tx.vin) {
|
|
if ((!input.scriptSig.empty() || !input.scriptWitness.IsNull()) && !permitsigdata) {
|
|
throw JSONRPCError(RPC_DESERIALIZATION_ERROR, "Inputs must not have scriptSigs and scriptWitnesses");
|
|
}
|
|
input.scriptSig.clear();
|
|
input.scriptWitness.SetNull();
|
|
}
|
|
|
|
// Make a blank psbt
|
|
PartiallySignedTransaction psbtx;
|
|
psbtx.tx = tx;
|
|
for (unsigned int i = 0; i < tx.vin.size(); ++i) {
|
|
psbtx.inputs.push_back(PSBTInput());
|
|
}
|
|
for (unsigned int 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(ssTx);
|
|
},
|
|
};
|
|
}
|
|
|
|
static RPCHelpMan utxoupdatepsbt()
|
|
{
|
|
return RPCHelpMan{"utxoupdatepsbt",
|
|
"\nUpdates all segwit inputs and outputs in a PSBT with data from output descriptors, the UTXO set, txindex, or the mempool.\n",
|
|
{
|
|
{"psbt", RPCArg::Type::STR, RPCArg::Optional::NO, "A base64 string of a PSBT"},
|
|
{"descriptors", RPCArg::Type::ARR, RPCArg::Optional::OMITTED, "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, RPCArg::Default{1000}, "Up to what index HD chains should be explored (either end or [begin,end])"},
|
|
}},
|
|
}},
|
|
},
|
|
RPCResult {
|
|
RPCResult::Type::STR, "", "The base64-encoded partially signed transaction with inputs updated"
|
|
},
|
|
RPCExamples {
|
|
HelpExampleCli("utxoupdatepsbt", "\"psbt\"")
|
|
},
|
|
[&](const RPCHelpMan& self, const JSONRPCRequest& request) -> UniValue
|
|
{
|
|
// 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.
|
|
const PartiallySignedTransaction& psbtx = ProcessPSBT(
|
|
request.params[0].get_str(),
|
|
request.context,
|
|
HidingSigningProvider(&provider, /*hide_secret=*/true, /*hide_origin=*/false),
|
|
/*sighash_type=*/SIGHASH_ALL,
|
|
/*finalize=*/false);
|
|
|
|
CDataStream ssTx(SER_NETWORK, PROTOCOL_VERSION);
|
|
ssTx << psbtx;
|
|
return EncodeBase64(ssTx);
|
|
},
|
|
};
|
|
}
|
|
|
|
static RPCHelpMan joinpsbts()
|
|
{
|
|
return RPCHelpMan{"joinpsbts",
|
|
"\nJoins 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, "The base64 strings of partially signed transactions",
|
|
{
|
|
{"psbt", RPCArg::Type::STR, RPCArg::Optional::NO, "A base64 string of a PSBT"}
|
|
}}
|
|
},
|
|
RPCResult {
|
|
RPCResult::Type::STR, "", "The base64-encoded partially signed transaction"
|
|
},
|
|
RPCExamples {
|
|
HelpExampleCli("joinpsbts", "\"psbt\"")
|
|
},
|
|
[&](const RPCHelpMan& self, const JSONRPCRequest& request) -> UniValue
|
|
{
|
|
// 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.");
|
|
}
|
|
|
|
uint32_t best_version = 1;
|
|
uint32_t best_locktime = 0xffffffff;
|
|
for (unsigned int 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 (static_cast<uint32_t>(psbtx.tx->nVersion) > best_version) {
|
|
best_version = static_cast<uint32_t>(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 = static_cast<int32_t>(best_version);
|
|
merged_psbt.tx->nLockTime = best_locktime;
|
|
|
|
// Merge
|
|
for (auto& psbt : psbtxs) {
|
|
for (unsigned int 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.hash.ToString(), psbt.tx->vin[i].prevout.n));
|
|
}
|
|
}
|
|
for (unsigned int i = 0; i < psbt.tx->vout.size(); ++i) {
|
|
merged_psbt.AddOutput(psbt.tx->vout[i], psbt.outputs[i]);
|
|
}
|
|
for (auto& xpub_pair : psbt.m_xpubs) {
|
|
if (merged_psbt.m_xpubs.count(xpub_pair.first) == 0) {
|
|
merged_psbt.m_xpubs[xpub_pair.first] = xpub_pair.second;
|
|
} else {
|
|
merged_psbt.m_xpubs[xpub_pair.first].insert(xpub_pair.second.begin(), xpub_pair.second.end());
|
|
}
|
|
}
|
|
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 indices 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(ssTx);
|
|
},
|
|
};
|
|
}
|
|
|
|
static RPCHelpMan analyzepsbt()
|
|
{
|
|
return RPCHelpMan{"analyzepsbt",
|
|
"\nAnalyzes 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 {
|
|
RPCResult::Type::OBJ, "", "",
|
|
{
|
|
{RPCResult::Type::ARR, "inputs", /*optional=*/true, "",
|
|
{
|
|
{RPCResult::Type::OBJ, "", "",
|
|
{
|
|
{RPCResult::Type::BOOL, "has_utxo", "Whether a UTXO is provided"},
|
|
{RPCResult::Type::BOOL, "is_final", "Whether the input is finalized"},
|
|
{RPCResult::Type::OBJ, "missing", /*optional=*/true, "Things that are missing that are required to complete this input",
|
|
{
|
|
{RPCResult::Type::ARR, "pubkeys", /*optional=*/true, "",
|
|
{
|
|
{RPCResult::Type::STR_HEX, "keyid", "Public key ID, hash160 of the public key, of a public key whose BIP 32 derivation path is missing"},
|
|
}},
|
|
{RPCResult::Type::ARR, "signatures", /*optional=*/true, "",
|
|
{
|
|
{RPCResult::Type::STR_HEX, "keyid", "Public key ID, hash160 of the public key, of a public key whose signature is missing"},
|
|
}},
|
|
{RPCResult::Type::STR_HEX, "redeemscript", /*optional=*/true, "Hash160 of the redeemScript that is missing"},
|
|
{RPCResult::Type::STR_HEX, "witnessscript", /*optional=*/true, "SHA256 of the witnessScript that is missing"},
|
|
}},
|
|
{RPCResult::Type::STR, "next", /*optional=*/true, "Role of the next person that this input needs to go to"},
|
|
}},
|
|
}},
|
|
{RPCResult::Type::NUM, "estimated_vsize", /*optional=*/true, "Estimated vsize of the final signed transaction"},
|
|
{RPCResult::Type::STR_AMOUNT, "estimated_feerate", /*optional=*/true, "Estimated feerate of the final signed transaction in " + CURRENCY_UNIT + "/kvB. Shown only if all UTXO slots in the PSBT have been filled"},
|
|
{RPCResult::Type::STR_AMOUNT, "fee", /*optional=*/true, "The transaction fee paid. Shown only if all UTXO slots in the PSBT have been filled"},
|
|
{RPCResult::Type::STR, "next", "Role of the next person that this psbt needs to go to"},
|
|
{RPCResult::Type::STR, "error", /*optional=*/true, "Error message (if there is one)"},
|
|
}
|
|
},
|
|
RPCExamples {
|
|
HelpExampleCli("analyzepsbt", "\"psbt\"")
|
|
},
|
|
[&](const RPCHelpMan& self, const JSONRPCRequest& request) -> UniValue
|
|
{
|
|
// 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_witness_script.IsNull()) {
|
|
missing.pushKV("witnessscript", HexStr(input.missing_witness_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 != std::nullopt) {
|
|
result.pushKV("estimated_vsize", (int)*psbta.estimated_vsize);
|
|
}
|
|
if (psbta.estimated_feerate != std::nullopt) {
|
|
result.pushKV("estimated_feerate", ValueFromAmount(psbta.estimated_feerate->GetFeePerK()));
|
|
}
|
|
if (psbta.fee != std::nullopt) {
|
|
result.pushKV("fee", ValueFromAmount(*psbta.fee));
|
|
}
|
|
result.pushKV("next", PSBTRoleName(psbta.next));
|
|
if (!psbta.error.empty()) {
|
|
result.pushKV("error", psbta.error);
|
|
}
|
|
|
|
return result;
|
|
},
|
|
};
|
|
}
|
|
|
|
RPCHelpMan descriptorprocesspsbt()
|
|
{
|
|
return RPCHelpMan{"descriptorprocesspsbt",
|
|
"\nUpdate all segwit inputs in a PSBT with information from output descriptors, the UTXO set or the mempool. \n"
|
|
"Then, sign the inputs we are able to with information from the output descriptors. ",
|
|
{
|
|
{"psbt", RPCArg::Type::STR, RPCArg::Optional::NO, "The transaction base64 string"},
|
|
{"descriptors", RPCArg::Type::ARR, RPCArg::Optional::NO, "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, RPCArg::Default{1000}, "Up to what index HD chains should be explored (either end or [begin,end])"},
|
|
}},
|
|
}},
|
|
{"sighashtype", RPCArg::Type::STR, RPCArg::Default{"DEFAULT for Taproot, ALL otherwise"}, "The signature hash type to sign with if not specified by the PSBT. Must be one of\n"
|
|
" \"DEFAULT\"\n"
|
|
" \"ALL\"\n"
|
|
" \"NONE\"\n"
|
|
" \"SINGLE\"\n"
|
|
" \"ALL|ANYONECANPAY\"\n"
|
|
" \"NONE|ANYONECANPAY\"\n"
|
|
" \"SINGLE|ANYONECANPAY\""},
|
|
{"bip32derivs", RPCArg::Type::BOOL, RPCArg::Default{true}, "Include BIP 32 derivation paths for public keys if we know them"},
|
|
{"finalize", RPCArg::Type::BOOL, RPCArg::Default{true}, "Also finalize inputs if possible"},
|
|
},
|
|
RPCResult{
|
|
RPCResult::Type::OBJ, "", "",
|
|
{
|
|
{RPCResult::Type::STR, "psbt", "The base64-encoded partially signed transaction"},
|
|
{RPCResult::Type::BOOL, "complete", "If the transaction has a complete set of signatures"},
|
|
}
|
|
},
|
|
RPCExamples{
|
|
HelpExampleCli("descriptorprocesspsbt", "\"psbt\" \"[\\\"descriptor1\\\", \\\"descriptor2\\\"]\"") +
|
|
HelpExampleCli("descriptorprocesspsbt", "\"psbt\" \"[{\\\"desc\\\":\\\"mydescriptor\\\", \\\"range\\\":21}]\"")
|
|
},
|
|
[&](const RPCHelpMan& self, const JSONRPCRequest& request) -> UniValue
|
|
{
|
|
// Add descriptor information to a signing provider
|
|
FlatSigningProvider provider;
|
|
|
|
auto descs = request.params[1].get_array();
|
|
for (size_t i = 0; i < descs.size(); ++i) {
|
|
EvalDescriptorStringOrObject(descs[i], provider, /*expand_priv=*/true);
|
|
}
|
|
|
|
int sighash_type = ParseSighashString(request.params[2]);
|
|
bool bip32derivs = request.params[3].isNull() ? true : request.params[3].get_bool();
|
|
bool finalize = request.params[4].isNull() ? true : request.params[4].get_bool();
|
|
|
|
const PartiallySignedTransaction& psbtx = ProcessPSBT(
|
|
request.params[0].get_str(),
|
|
request.context,
|
|
HidingSigningProvider(&provider, /*hide_secret=*/false, !bip32derivs),
|
|
sighash_type,
|
|
finalize);
|
|
|
|
// Check whether or not all of the inputs are now signed
|
|
bool complete = true;
|
|
for (const auto& input : psbtx.inputs) {
|
|
complete &= PSBTInputSigned(input);
|
|
}
|
|
|
|
CDataStream ssTx(SER_NETWORK, PROTOCOL_VERSION);
|
|
ssTx << psbtx;
|
|
|
|
UniValue result(UniValue::VOBJ);
|
|
|
|
result.pushKV("psbt", EncodeBase64(ssTx));
|
|
result.pushKV("complete", complete);
|
|
|
|
return result;
|
|
},
|
|
};
|
|
}
|
|
|
|
void RegisterRawTransactionRPCCommands(CRPCTable& t)
|
|
{
|
|
static const CRPCCommand commands[]{
|
|
{"rawtransactions", &getrawtransaction},
|
|
{"rawtransactions", &createrawtransaction},
|
|
{"rawtransactions", &decoderawtransaction},
|
|
{"rawtransactions", &decodescript},
|
|
{"rawtransactions", &combinerawtransaction},
|
|
{"rawtransactions", &signrawtransactionwithkey},
|
|
{"rawtransactions", &decodepsbt},
|
|
{"rawtransactions", &combinepsbt},
|
|
{"rawtransactions", &finalizepsbt},
|
|
{"rawtransactions", &createpsbt},
|
|
{"rawtransactions", &converttopsbt},
|
|
{"rawtransactions", &utxoupdatepsbt},
|
|
{"rawtransactions", &descriptorprocesspsbt},
|
|
{"rawtransactions", &joinpsbts},
|
|
{"rawtransactions", &analyzepsbt},
|
|
};
|
|
for (const auto& c : commands) {
|
|
t.appendCommand(c.name, &c);
|
|
}
|
|
}
|