From 540729ef4bf1b6c6da1ec795e441d2ce56a9a58b Mon Sep 17 00:00:00 2001 From: Andrew Chow Date: Tue, 31 Jul 2018 17:58:01 -0700 Subject: Implement analyzepsbt RPC and tests --- src/rpc/rawtransaction.cpp | 198 +++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 198 insertions(+) (limited to 'src/rpc') 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 #include #include +#include #include #include #include @@ -30,6 +31,8 @@ #include #include + +#include #include #include @@ -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"} }, -- cgit v1.2.3