aboutsummaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
authorAndrew Chow <achow101-github@achow101.com>2018-07-31 17:58:01 -0700
committerAndrew Chow <achow101-github@achow101.com>2019-02-16 11:51:02 -0500
commit540729ef4bf1b6c6da1ec795e441d2ce56a9a58b (patch)
treef7984f7df6df66f36b45a8d9a0fc52b5edd10963 /src
parent77542cf2a5f8abb97dd46f782c1b0199cc062033 (diff)
downloadbitcoin-540729ef4bf1b6c6da1ec795e441d2ce56a9a58b.tar.xz
Implement analyzepsbt RPC and tests
Diffstat (limited to 'src')
-rw-r--r--src/rpc/rawtransaction.cpp198
1 files changed, 198 insertions, 0 deletions
diff --git a/src/rpc/rawtransaction.cpp b/src/rpc/rawtransaction.cpp
index 866158e003..ac41207404 100644
--- a/src/rpc/rawtransaction.cpp
+++ b/src/rpc/rawtransaction.cpp
@@ -7,6 +7,7 @@
#include <coins.h>
#include <compat/byteswap.h>
#include <consensus/validation.h>
+#include <consensus/tx_verify.h>
#include <core_io.h>
#include <index/txindex.h>
#include <init.h>
@@ -30,6 +31,8 @@
#include <validation.h>
#include <validationinterface.h>
+
+#include <numeric>
#include <stdint.h>
#include <univalue.h>
@@ -1829,6 +1832,200 @@ UniValue joinpsbts(const JSONRPCRequest& request)
return EncodeBase64((unsigned char*)ssTx.data(), ssTx.size());
}
+UniValue analyzepsbt(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, RPCArg::Optional::NO, "A base64 string of a PSBT"}
+ },
+ RPCResult {
+ "{\n"
+ " \"inputs\" : [ (array of json objects)\n"
+ " {\n"
+ " \"has_utxo\" : true|false (boolean) Whether a UTXO is provided\n"
+ " \"is_final\" : true|false (boolean) Whether the input is finalized\n"
+ " \"missing\" : { (json object, optional) Things that are missing that are required to complete this input\n"
+ " \"pubkeys\" : [ (array)\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"
+ " \"witnessscript\" : \"hash\" (string) SHA256 of the witnessScript 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;
+ CAmount in_amt = 0;
+ for (unsigned int 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, 1, &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_witness_script.IsNull()) {
+ // Missing witnessScript
+ missing.pushKV("witnessscript", HexStr(outdata.missing_witness_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_witness_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
+ CAmount out_amt = std::accumulate(psbtx.tx->vout.begin(), psbtx.tx->vout.end(), 0,
+ [](int a, const CTxOut& b) {
+ return a += b.nValue;
+ }
+ );
+
+ // Get the fee
+ CAmount fee = in_amt - out_amt;
+
+ // Estimate the size
+ CMutableTransaction mtx(*psbtx.tx);
+ CCoinsView view_dummy;
+ CCoinsViewCache view(&view_dummy);
+ bool success = true;
+
+ for (unsigned int i = 0; i < psbtx.tx->vin.size(); ++i) {
+ PSBTInput& input = psbtx.inputs[i];
+ if (SignPSBTInput(DUMMY_SIGNING_PROVIDER, psbtx, i, 1, nullptr, true)) {
+ mtx.vin[i].scriptSig = input.final_script_sig;
+ mtx.vin[i].scriptWitness = input.final_script_witness;
+
+ Coin newcoin;
+ if (!psbtx.GetInputUTXO(newcoin.out, i)) {
+ success = false;
+ break;
+ }
+ newcoin.nHeight = 1;
+ view.AddCoin(psbtx.tx->vin[i].prevout, std::move(newcoin), true);
+ } else {
+ success = false;
+ break;
+ }
+ }
+
+ if (success) {
+ CTransaction ctx = CTransaction(mtx);
+ size_t size = GetVirtualTransactionSize(ctx, GetTransactionSigOpCost(ctx, view, STANDARD_SCRIPT_VERIFY_FLAGS));
+ result.pushKV("estimated_vsize", (int)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
static const CRPCCommand commands[] =
{ // category name actor (function) argNames
@@ -1849,6 +2046,7 @@ static const CRPCCommand commands[] =
{ "rawtransactions", "converttopsbt", &converttopsbt, {"hexstring","permitsigdata","iswitness"} },
{ "rawtransactions", "utxoupdatepsbt", &utxoupdatepsbt, {"psbt"} },
{ "rawtransactions", "joinpsbts", &joinpsbts, {"txs"} },
+ { "rawtransactions", "analyzepsbt", &analyzepsbt, {"psbt"} },
{ "blockchain", "gettxoutproof", &gettxoutproof, {"txids", "blockhash"} },
{ "blockchain", "verifytxoutproof", &verifytxoutproof, {"proof"} },