Changeset View
Changeset View
Standalone View
Standalone View
src/rpc/rawtransaction.cpp
// Copyright (c) 2010 Satoshi Nakamoto | // Copyright (c) 2010 Satoshi Nakamoto | ||||
// Copyright (c) 2009-2016 The Bitcoin Core developers | // Copyright (c) 2009-2016 The Bitcoin Core developers | ||||
// Distributed under the MIT software license, see the accompanying | // Distributed under the MIT software license, see the accompanying | ||||
// file COPYING or http://www.opensource.org/licenses/mit-license.php. | // file COPYING or http://www.opensource.org/licenses/mit-license.php. | ||||
#include <chain.h> | #include <chain.h> | ||||
#include <chainparams.h> | #include <chainparams.h> | ||||
#include <coins.h> | #include <coins.h> | ||||
#include <compat/byteswap.h> | #include <compat/byteswap.h> | ||||
#include <config.h> | #include <config.h> | ||||
#include <consensus/tx_verify.h> | |||||
#include <consensus/validation.h> | #include <consensus/validation.h> | ||||
#include <core_io.h> | #include <core_io.h> | ||||
#include <index/txindex.h> | #include <index/txindex.h> | ||||
#include <init.h> | #include <init.h> | ||||
#include <interfaces/chain.h> | #include <interfaces/chain.h> | ||||
#include <key_io.h> | #include <key_io.h> | ||||
#include <keystore.h> | #include <keystore.h> | ||||
#include <merkleblock.h> | #include <merkleblock.h> | ||||
Show All 11 Lines | |||||
#include <txmempool.h> | #include <txmempool.h> | ||||
#include <uint256.h> | #include <uint256.h> | ||||
#include <util/error.h> | #include <util/error.h> | ||||
#include <util/strencodings.h> | #include <util/strencodings.h> | ||||
#include <validation.h> | #include <validation.h> | ||||
#include <validationinterface.h> | #include <validationinterface.h> | ||||
#include <cstdint> | #include <cstdint> | ||||
#include <numeric> | |||||
#include <univalue.h> | #include <univalue.h> | ||||
static void TxToJSON(const CTransaction &tx, const BlockHash &hashBlock, | static void TxToJSON(const CTransaction &tx, const BlockHash &hashBlock, | ||||
UniValue &entry) { | UniValue &entry) { | ||||
// Call into TxToUniv() in bitcoin-common to decode the transaction hex. | // Call into TxToUniv() in bitcoin-common to decode the transaction hex. | ||||
// | // | ||||
// Blockchain contextual information (confirmations and blocktime) is not | // Blockchain contextual information (confirmations and blocktime) is not | ||||
▲ Show 20 Lines • Show All 1,784 Lines • ▼ Show 20 Lines | for (auto &psbt : psbtxs) { | ||||
merged_psbt.unknown.insert(psbt.unknown.begin(), psbt.unknown.end()); | merged_psbt.unknown.insert(psbt.unknown.begin(), psbt.unknown.end()); | ||||
} | } | ||||
CDataStream ssTx(SER_NETWORK, PROTOCOL_VERSION); | CDataStream ssTx(SER_NETWORK, PROTOCOL_VERSION); | ||||
ssTx << merged_psbt; | ssTx << merged_psbt; | ||||
return EncodeBase64((uint8_t *)ssTx.data(), ssTx.size()); | return EncodeBase64((uint8_t *)ssTx.data(), ssTx.size()); | ||||
} | } | ||||
UniValue analyzepsbt(const Config &config, const JSONRPCRequest &request) { | |||||
if (request.fHelp || request.params.size() != 1) { | |||||
throw std::runtime_error(RPCHelpMan{ | |||||
"analyzepsbt", | |||||
"\nAnalyzes and provides information about the current status of a " | |||||
"PSBT and its inputs\n", | |||||
{{"psbt", RPCArg::Type::STR, /* opt */ false, /* default_var */ "", | |||||
"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)\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)\n" | |||||
" \"keyid\" (string) Public key ID, " | |||||
"hash160 of the public key, of a public key whose signature is " | |||||
"missing\n" | |||||
" ]\n" | |||||
" \"redeemscript\" : \"hash\" (string) Hash160 of the " | |||||
"redeemScript that is missing\n" | |||||
" }\n" | |||||
" \"next\" : \"role\" (string) Role of the next " | |||||
"person that this input needs to go to\n" | |||||
" }\n" | |||||
" ,...\n" | |||||
" ]\n" | |||||
" \"estimated_vsize\" : vsize (numeric) Estimated vsize " | |||||
"of the final signed transaction\n" | |||||
" \"estimated_feerate\" : feerate (numeric, optional) " | |||||
"Estimated feerate of the final signed transaction. 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" | |||||
"}\n"}, | |||||
RPCExamples{HelpExampleCli("analyzepsbt", "\"psbt\"")}} | |||||
.ToString()); | |||||
} | |||||
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)); | |||||
} | |||||
// Go through each input and build status | |||||
UniValue result(UniValue::VOBJ); | |||||
UniValue inputs_result(UniValue::VARR); | |||||
bool calc_fee = true; | |||||
bool all_final = true; | |||||
bool only_missing_sigs = true; | |||||
bool only_missing_final = false; | |||||
Amount in_amt{Amount::zero()}; | |||||
for (size_t i = 0; i < psbtx.tx->vin.size(); ++i) { | |||||
PSBTInput &input = psbtx.inputs[i]; | |||||
UniValue input_univ(UniValue::VOBJ); | |||||
UniValue missing(UniValue::VOBJ); | |||||
// Check for a UTXO | |||||
CTxOut utxo; | |||||
if (psbtx.GetInputUTXO(utxo, i)) { | |||||
in_amt += utxo.nValue; | |||||
input_univ.pushKV("has_utxo", true); | |||||
} else { | |||||
input_univ.pushKV("has_utxo", false); | |||||
input_univ.pushKV("is_final", false); | |||||
input_univ.pushKV("next", "updater"); | |||||
calc_fee = false; | |||||
} | |||||
// Check if it is final | |||||
if (!utxo.IsNull() && !PSBTInputSigned(input)) { | |||||
input_univ.pushKV("is_final", false); | |||||
all_final = false; | |||||
// Figure out what is missing | |||||
SignatureData outdata; | |||||
bool complete = SignPSBTInput(DUMMY_SIGNING_PROVIDER, psbtx, i, | |||||
SigHashType().withForkId(), &outdata); | |||||
// Things are missing | |||||
if (!complete) { | |||||
if (!outdata.missing_pubkeys.empty()) { | |||||
// Missing pubkeys | |||||
UniValue missing_pubkeys_univ(UniValue::VARR); | |||||
for (const CKeyID &pubkey : outdata.missing_pubkeys) { | |||||
missing_pubkeys_univ.push_back(HexStr(pubkey)); | |||||
} | |||||
missing.pushKV("pubkeys", missing_pubkeys_univ); | |||||
} | |||||
if (!outdata.missing_redeem_script.IsNull()) { | |||||
// Missing redeemScript | |||||
missing.pushKV("redeemscript", | |||||
HexStr(outdata.missing_redeem_script)); | |||||
} | |||||
if (!outdata.missing_sigs.empty()) { | |||||
// Missing sigs | |||||
UniValue missing_sigs_univ(UniValue::VARR); | |||||
for (const CKeyID &pubkey : outdata.missing_sigs) { | |||||
missing_sigs_univ.push_back(HexStr(pubkey)); | |||||
} | |||||
missing.pushKV("signatures", missing_sigs_univ); | |||||
} | |||||
input_univ.pushKV("missing", missing); | |||||
// If we are only missing signatures and nothing else, then next | |||||
// is signer | |||||
if (outdata.missing_pubkeys.empty() && | |||||
outdata.missing_redeem_script.IsNull() && | |||||
!outdata.missing_sigs.empty()) { | |||||
input_univ.pushKV("next", "signer"); | |||||
} else { | |||||
only_missing_sigs = false; | |||||
input_univ.pushKV("next", "updater"); | |||||
} | |||||
} else { | |||||
only_missing_final = true; | |||||
input_univ.pushKV("next", "finalizer"); | |||||
} | |||||
} else if (!utxo.IsNull()) { | |||||
input_univ.pushKV("is_final", true); | |||||
} | |||||
inputs_result.push_back(input_univ); | |||||
} | |||||
result.pushKV("inputs", inputs_result); | |||||
if (all_final) { | |||||
only_missing_sigs = false; | |||||
result.pushKV("next", "extractor"); | |||||
} | |||||
if (calc_fee) { | |||||
// Get the output amount | |||||
Amount out_amt = std::accumulate( | |||||
psbtx.tx->vout.begin(), psbtx.tx->vout.end(), Amount::zero(), | |||||
[](Amount a, const CTxOut &b) { return a += b.nValue; }); | |||||
// Get the fee | |||||
Amount fee = in_amt - out_amt; | |||||
// Estimate the size | |||||
CMutableTransaction mtx(*psbtx.tx); | |||||
CCoinsView view_dummy; | |||||
CCoinsViewCache view(&view_dummy); | |||||
bool success = true; | |||||
for (size_t i = 0; i < psbtx.tx->vin.size(); ++i) { | |||||
PSBTInput &input = psbtx.inputs[i]; | |||||
if (SignPSBTInput(DUMMY_SIGNING_PROVIDER, psbtx, i, | |||||
SigHashType().withForkId(), nullptr, true)) { | |||||
mtx.vin[i].scriptSig = input.final_script_sig; | |||||
CTxOut newUtxo; | |||||
if (!psbtx.GetInputUTXO(newUtxo, i)) { | |||||
success = false; | |||||
break; | |||||
} | |||||
view.AddCoin(psbtx.tx->vin[i].prevout, Coin(newUtxo, 1, false), | |||||
true); | |||||
} else { | |||||
success = false; | |||||
break; | |||||
} | |||||
} | |||||
if (success) { | |||||
CTransaction ctx = CTransaction(mtx); | |||||
size_t size = ctx.GetTotalSize(); | |||||
result.pushKV("estimated_vsize", uint64_t(size)); | |||||
// Estimate fee rate | |||||
CFeeRate feerate(fee, size); | |||||
result.pushKV("estimated_feerate", feerate.ToString()); | |||||
} | |||||
result.pushKV("fee", ValueFromAmount(fee)); | |||||
if (only_missing_sigs) { | |||||
result.pushKV("next", "signer"); | |||||
} else if (only_missing_final) { | |||||
result.pushKV("next", "finalizer"); | |||||
} else if (all_final) { | |||||
result.pushKV("next", "extractor"); | |||||
} else { | |||||
result.pushKV("next", "updater"); | |||||
} | |||||
} else { | |||||
result.pushKV("next", "updater"); | |||||
} | |||||
return result; | |||||
} | |||||
// clang-format off | // clang-format off | ||||
static const CRPCCommand commands[] = { | static const CRPCCommand commands[] = { | ||||
// category name actor (function) argNames | // category name actor (function) argNames | ||||
// ------------------- ------------------------ ---------------------- ---------- | // ------------------- ------------------------ ---------------------- ---------- | ||||
{ "rawtransactions", "getrawtransaction", getrawtransaction, {"txid","verbose","blockhash"} }, | { "rawtransactions", "getrawtransaction", getrawtransaction, {"txid","verbose","blockhash"} }, | ||||
{ "rawtransactions", "createrawtransaction", createrawtransaction, {"inputs","outputs","locktime"} }, | { "rawtransactions", "createrawtransaction", createrawtransaction, {"inputs","outputs","locktime"} }, | ||||
{ "rawtransactions", "decoderawtransaction", decoderawtransaction, {"hexstring"} }, | { "rawtransactions", "decoderawtransaction", decoderawtransaction, {"hexstring"} }, | ||||
{ "rawtransactions", "decodescript", decodescript, {"hexstring"} }, | { "rawtransactions", "decodescript", decodescript, {"hexstring"} }, | ||||
{ "rawtransactions", "sendrawtransaction", sendrawtransaction, {"hexstring","allowhighfees"} }, | { "rawtransactions", "sendrawtransaction", sendrawtransaction, {"hexstring","allowhighfees"} }, | ||||
{ "rawtransactions", "combinerawtransaction", combinerawtransaction, {"txs"} }, | { "rawtransactions", "combinerawtransaction", combinerawtransaction, {"txs"} }, | ||||
{ "rawtransactions", "signrawtransactionwithkey", signrawtransactionwithkey, {"hexstring","privkeys","prevtxs","sighashtype"} }, | { "rawtransactions", "signrawtransactionwithkey", signrawtransactionwithkey, {"hexstring","privkeys","prevtxs","sighashtype"} }, | ||||
{ "rawtransactions", "testmempoolaccept", testmempoolaccept, {"rawtxs","allowhighfees"} }, | { "rawtransactions", "testmempoolaccept", testmempoolaccept, {"rawtxs","allowhighfees"} }, | ||||
{ "rawtransactions", "decodepsbt", decodepsbt, {"psbt"} }, | { "rawtransactions", "decodepsbt", decodepsbt, {"psbt"} }, | ||||
{ "rawtransactions", "combinepsbt", combinepsbt, {"txs"} }, | { "rawtransactions", "combinepsbt", combinepsbt, {"txs"} }, | ||||
{ "rawtransactions", "finalizepsbt", finalizepsbt, {"psbt", "extract"} }, | { "rawtransactions", "finalizepsbt", finalizepsbt, {"psbt", "extract"} }, | ||||
{ "rawtransactions", "createpsbt", createpsbt, {"inputs","outputs","locktime"} }, | { "rawtransactions", "createpsbt", createpsbt, {"inputs","outputs","locktime"} }, | ||||
{ "rawtransactions", "converttopsbt", converttopsbt, {"hexstring","permitsigdata"} }, | { "rawtransactions", "converttopsbt", converttopsbt, {"hexstring","permitsigdata"} }, | ||||
{ "rawtransactions", "utxoupdatepsbt", utxoupdatepsbt, {"psbt"} }, | { "rawtransactions", "utxoupdatepsbt", utxoupdatepsbt, {"psbt"} }, | ||||
{ "rawtransactions", "joinpsbts", joinpsbts, {"txs"} }, | { "rawtransactions", "joinpsbts", joinpsbts, {"txs"} }, | ||||
{ "rawtransactions", "analyzepsbt", analyzepsbt, {"psbt"} }, | |||||
{ "blockchain", "gettxoutproof", gettxoutproof, {"txids", "blockhash"} }, | { "blockchain", "gettxoutproof", gettxoutproof, {"txids", "blockhash"} }, | ||||
{ "blockchain", "verifytxoutproof", verifytxoutproof, {"proof"} }, | { "blockchain", "verifytxoutproof", verifytxoutproof, {"proof"} }, | ||||
}; | }; | ||||
// clang-format on | // clang-format on | ||||
void RegisterRawTransactionRPCCommands(CRPCTable &t) { | void RegisterRawTransactionRPCCommands(CRPCTable &t) { | ||||
for (unsigned int vcidx = 0; vcidx < ARRAYLEN(commands); vcidx++) { | for (unsigned int vcidx = 0; vcidx < ARRAYLEN(commands); vcidx++) { | ||||
t.appendCommand(commands[vcidx].name, &commands[vcidx]); | t.appendCommand(commands[vcidx].name, &commands[vcidx]); | ||||
} | } | ||||
} | } |