diff --git a/src/rpc/rawtransaction.cpp b/src/rpc/rawtransaction.cpp --- a/src/rpc/rawtransaction.cpp +++ b/src/rpc/rawtransaction.cpp @@ -72,9 +72,8 @@ } } -static UniValue getrawtransaction(const Config &config, - const JSONRPCRequest &request) { - RPCHelpMan{ +static RPCHelpMan getrawtransaction() { + return RPCHelpMan{ "getrawtransaction", "By default this function only works for mempool transactions. When " "called with a blockhash\n" @@ -190,88 +189,95 @@ "\"mytxid\" false \"myblockhash\"") + HelpExampleCli("getrawtransaction", "\"mytxid\" true \"myblockhash\"")}, - } - .Check(request); - - const NodeContext &node = EnsureNodeContext(request.context); + [&](const RPCHelpMan &self, const Config &config, + const JSONRPCRequest &request) -> UniValue { + const NodeContext &node = EnsureNodeContext(request.context); - bool in_active_chain = true; - TxId txid = TxId(ParseHashV(request.params[0], "parameter 1")); - CBlockIndex *blockindex = nullptr; + bool in_active_chain = true; + TxId txid = TxId(ParseHashV(request.params[0], "parameter 1")); + CBlockIndex *blockindex = nullptr; - const CChainParams ¶ms = config.GetChainParams(); - if (txid == params.GenesisBlock().hashMerkleRoot) { - // Special exception for the genesis block coinbase transaction - throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, - "The genesis block coinbase is not considered an " - "ordinary transaction and cannot be retrieved"); - } - - // Accept either a bool (true) or a num (>=1) to indicate verbose output. - bool fVerbose = false; - if (!request.params[1].isNull()) { - fVerbose = request.params[1].isNum() - ? (request.params[1].get_int() != 0) - : request.params[1].get_bool(); - } + const CChainParams ¶ms = config.GetChainParams(); + if (txid == params.GenesisBlock().hashMerkleRoot) { + // Special exception for the genesis block coinbase transaction + throw JSONRPCError( + RPC_INVALID_ADDRESS_OR_KEY, + "The genesis block coinbase is not considered an " + "ordinary transaction and cannot be retrieved"); + } - if (!request.params[2].isNull()) { - LOCK(cs_main); + // Accept either a bool (true) or a num (>=1) to indicate verbose + // output. + bool fVerbose = false; + if (!request.params[1].isNull()) { + fVerbose = request.params[1].isNum() + ? (request.params[1].get_int() != 0) + : request.params[1].get_bool(); + } - BlockHash blockhash(ParseHashV(request.params[2], "parameter 3")); - blockindex = LookupBlockIndex(blockhash); - if (!blockindex) { - throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, - "Block hash not found"); - } - in_active_chain = ::ChainActive().Contains(blockindex); - } + if (!request.params[2].isNull()) { + LOCK(cs_main); + + BlockHash blockhash( + ParseHashV(request.params[2], "parameter 3")); + blockindex = LookupBlockIndex(blockhash); + if (!blockindex) { + throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, + "Block hash not found"); + } + in_active_chain = ::ChainActive().Contains(blockindex); + } - bool f_txindex_ready = false; - if (g_txindex && !blockindex) { - f_txindex_ready = g_txindex->BlockUntilSyncedToCurrentChain(); - } + bool f_txindex_ready = false; + if (g_txindex && !blockindex) { + f_txindex_ready = g_txindex->BlockUntilSyncedToCurrentChain(); + } - BlockHash hash_block; - const CTransactionRef tx = - GetTransaction(blockindex, node.mempool.get(), txid, - params.GetConsensus(), hash_block); - if (!tx) { - std::string errmsg; - if (blockindex) { - if (!blockindex->nStatus.hasData()) { - throw JSONRPCError(RPC_MISC_ERROR, "Block not available"); - } - errmsg = "No such transaction found in the provided block"; - } else if (!g_txindex) { - errmsg = "No such mempool transaction. Use -txindex 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."); - } + BlockHash hash_block; + const CTransactionRef tx = + GetTransaction(blockindex, node.mempool.get(), txid, + params.GetConsensus(), hash_block); + if (!tx) { + std::string errmsg; + if (blockindex) { + if (!blockindex->nStatus.hasData()) { + throw JSONRPCError(RPC_MISC_ERROR, + "Block not available"); + } + errmsg = "No such transaction found in the provided block"; + } else if (!g_txindex) { + errmsg = + "No such mempool transaction. Use -txindex 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 (!fVerbose) { - return EncodeHexTx(*tx, RPCSerializationFlags()); - } + if (!fVerbose) { + return EncodeHexTx(*tx, RPCSerializationFlags()); + } - UniValue result(UniValue::VOBJ); - if (blockindex) { - result.pushKV("in_active_chain", in_active_chain); - } - TxToJSON(*tx, hash_block, result); - return result; + UniValue result(UniValue::VOBJ); + if (blockindex) { + result.pushKV("in_active_chain", in_active_chain); + } + TxToJSON(*tx, hash_block, result); + return result; + }, + }; } -static UniValue gettxoutproof(const Config &config, - const JSONRPCRequest &request) { - RPCHelpMan{ +static RPCHelpMan gettxoutproof() { + return RPCHelpMan{ "gettxoutproof", "Returns a hex-encoded proof that \"txid\" was included in a block.\n" "\nNOTE: By default this function only works sometimes. " @@ -301,103 +307,109 @@ RPCResult::Type::STR, "data", "A string that is a serialized, hex-encoded data for the proof."}, RPCExamples{""}, - } - .Check(request); - - std::set setTxIds; - TxId oneTxId; - UniValue txids = request.params[0].get_array(); - for (unsigned int idx = 0; idx < txids.size(); idx++) { - const UniValue &utxid = txids[idx]; - TxId txid(ParseHashV(utxid, "txid")); - if (setTxIds.count(txid)) { - throw JSONRPCError( - RPC_INVALID_PARAMETER, - std::string("Invalid parameter, duplicated txid: ") + - utxid.get_str()); - } - - setTxIds.insert(txid); - oneTxId = txid; - } - - CBlockIndex *pblockindex = nullptr; - - BlockHash hashBlock; - if (!request.params[1].isNull()) { - LOCK(cs_main); - hashBlock = BlockHash(ParseHashV(request.params[1], "blockhash")); - pblockindex = LookupBlockIndex(hashBlock); - if (!pblockindex) { - throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Block not found"); - } - } else { - LOCK(cs_main); - // Loop through txids and try to find which block they're in. Exit loop - // once a block is found. - for (const auto &txid : setTxIds) { - const Coin &coin = - AccessByTxid(::ChainstateActive().CoinsTip(), txid); - if (!coin.IsSpent()) { - pblockindex = ::ChainActive()[coin.GetHeight()]; - break; + [&](const RPCHelpMan &self, const Config &config, + const JSONRPCRequest &request) -> UniValue { + std::set setTxIds; + TxId oneTxId; + UniValue txids = request.params[0].get_array(); + for (unsigned int idx = 0; idx < txids.size(); idx++) { + const UniValue &utxid = txids[idx]; + TxId txid(ParseHashV(utxid, "txid")); + if (setTxIds.count(txid)) { + throw JSONRPCError( + RPC_INVALID_PARAMETER, + std::string("Invalid parameter, duplicated txid: ") + + utxid.get_str()); + } + + setTxIds.insert(txid); + oneTxId = txid; } - } - } - - // Allow txindex to catch up if we need to query it and before we acquire - // cs_main. - if (g_txindex && !pblockindex) { - g_txindex->BlockUntilSyncedToCurrentChain(); - } - - const Consensus::Params ¶ms = config.GetChainParams().GetConsensus(); - LOCK(cs_main); + CBlockIndex *pblockindex = nullptr; + + BlockHash hashBlock; + if (!request.params[1].isNull()) { + LOCK(cs_main); + hashBlock = + BlockHash(ParseHashV(request.params[1], "blockhash")); + pblockindex = LookupBlockIndex(hashBlock); + if (!pblockindex) { + throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, + "Block not found"); + } + } else { + LOCK(cs_main); + // Loop through txids and try to find which block they're in. + // Exit loop once a block is found. + for (const auto &txid : setTxIds) { + const Coin &coin = + AccessByTxid(::ChainstateActive().CoinsTip(), txid); + if (!coin.IsSpent()) { + pblockindex = ::ChainActive()[coin.GetHeight()]; + break; + } + } + } - if (pblockindex == nullptr) { - const CTransactionRef tx = GetTransaction( - /* block_index */ nullptr, - /* mempool */ nullptr, oneTxId, Params().GetConsensus(), hashBlock); - if (!tx || hashBlock.IsNull()) { - throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, - "Transaction not yet in block"); - } + // Allow txindex to catch up if we need to query it and before we + // acquire cs_main. + if (g_txindex && !pblockindex) { + g_txindex->BlockUntilSyncedToCurrentChain(); + } - pblockindex = LookupBlockIndex(hashBlock); - if (!pblockindex) { - throw JSONRPCError(RPC_INTERNAL_ERROR, "Transaction index corrupt"); - } - } + const Consensus::Params ¶ms = + config.GetChainParams().GetConsensus(); + + LOCK(cs_main); + + if (pblockindex == nullptr) { + const CTransactionRef tx = GetTransaction( + /* block_index */ nullptr, + /* mempool */ nullptr, oneTxId, Params().GetConsensus(), + hashBlock); + if (!tx || hashBlock.IsNull()) { + throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, + "Transaction not yet in block"); + } + + pblockindex = LookupBlockIndex(hashBlock); + if (!pblockindex) { + throw JSONRPCError(RPC_INTERNAL_ERROR, + "Transaction index corrupt"); + } + } - CBlock block; - if (!ReadBlockFromDisk(block, pblockindex, params)) { - throw JSONRPCError(RPC_INTERNAL_ERROR, "Can't read block from disk"); - } + CBlock block; + if (!ReadBlockFromDisk(block, pblockindex, params)) { + throw JSONRPCError(RPC_INTERNAL_ERROR, + "Can't read block from disk"); + } - unsigned int ntxFound = 0; - for (const auto &tx : block.vtx) { - if (setTxIds.count(tx->GetId())) { - ntxFound++; - } - } + unsigned int ntxFound = 0; + for (const auto &tx : block.vtx) { + if (setTxIds.count(tx->GetId())) { + ntxFound++; + } + } - if (ntxFound != setTxIds.size()) { - throw JSONRPCError( - RPC_INVALID_ADDRESS_OR_KEY, - "Not all transactions found in specified or retrieved block"); - } + if (ntxFound != setTxIds.size()) { + throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, + "Not all transactions found in specified or " + "retrieved block"); + } - CDataStream ssMB(SER_NETWORK, PROTOCOL_VERSION); - CMerkleBlock mb(block, setTxIds); - ssMB << mb; - std::string strHex = HexStr(ssMB); - return strHex; + CDataStream ssMB(SER_NETWORK, PROTOCOL_VERSION); + CMerkleBlock mb(block, setTxIds); + ssMB << mb; + std::string strHex = HexStr(ssMB); + return strHex; + }, + }; } -static UniValue verifytxoutproof(const Config &config, - const JSONRPCRequest &request) { - RPCHelpMan{ +static RPCHelpMan verifytxoutproof() { + return RPCHelpMan{ "verifytxoutproof", "Verifies that a proof points to a transaction in a block, returning " "the transaction it commits to\n" @@ -415,44 +427,46 @@ "if the proof can not be validated."}, }}, RPCExamples{""}, - } - .Check(request); - - CDataStream ssMB(ParseHexV(request.params[0], "proof"), SER_NETWORK, - PROTOCOL_VERSION); - CMerkleBlock merkleBlock; - ssMB >> merkleBlock; - - UniValue res(UniValue::VARR); - - std::vector vMatch; - std::vector vIndex; - if (merkleBlock.txn.ExtractMatches(vMatch, vIndex) != - merkleBlock.header.hashMerkleRoot) { - return res; - } + [&](const RPCHelpMan &self, const Config &config, + const JSONRPCRequest &request) -> UniValue { + CDataStream ssMB(ParseHexV(request.params[0], "proof"), SER_NETWORK, + PROTOCOL_VERSION); + CMerkleBlock merkleBlock; + ssMB >> merkleBlock; + + UniValue res(UniValue::VARR); + + std::vector vMatch; + std::vector vIndex; + if (merkleBlock.txn.ExtractMatches(vMatch, vIndex) != + merkleBlock.header.hashMerkleRoot) { + return res; + } - LOCK(cs_main); + LOCK(cs_main); - const CBlockIndex *pindex = LookupBlockIndex(merkleBlock.header.GetHash()); - if (!pindex || !::ChainActive().Contains(pindex) || pindex->nTx == 0) { - throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, - "Block not found in chain"); - } + const CBlockIndex *pindex = + LookupBlockIndex(merkleBlock.header.GetHash()); + if (!pindex || !::ChainActive().Contains(pindex) || + pindex->nTx == 0) { + throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, + "Block not found in chain"); + } - // Check if proof is valid, only add results if so - if (pindex->nTx == merkleBlock.txn.GetNumTransactions()) { - for (const uint256 &hash : vMatch) { - res.push_back(hash.GetHex()); - } - } + // Check if proof is valid, only add results if so + if (pindex->nTx == merkleBlock.txn.GetNumTransactions()) { + for (const uint256 &hash : vMatch) { + res.push_back(hash.GetHex()); + } + } - return res; + return res; + }, + }; } -static UniValue createrawtransaction(const Config &config, - const JSONRPCRequest &request) { - RPCHelpMan{ +static RPCHelpMan createrawtransaction() { + return RPCHelpMan{ "createrawtransaction", "Create a transaction spending the given inputs and creating new " "outputs.\n" @@ -542,25 +556,25 @@ HelpExampleRpc("createrawtransaction", "\"[{\\\"txid\\\":\\\"myid\\\",\\\"vout\\\":0}]" "\", \"[{\\\"data\\\":\\\"00010203\\\"}]\"")}, - } - .Check(request); - - RPCTypeCheck(request.params, - {UniValue::VARR, - UniValueType(), // ARR or OBJ, checked later - UniValue::VNUM}, - true); - - CMutableTransaction rawTx = - ConstructTransaction(config.GetChainParams(), request.params[0], - request.params[1], request.params[2]); - - return EncodeHexTx(CTransaction(rawTx)); + [&](const RPCHelpMan &self, const Config &config, + const JSONRPCRequest &request) -> UniValue { + RPCTypeCheck(request.params, + {UniValue::VARR, + UniValueType(), // ARR or OBJ, checked later + UniValue::VNUM}, + true); + + CMutableTransaction rawTx = + ConstructTransaction(config.GetChainParams(), request.params[0], + request.params[1], request.params[2]); + + return EncodeHexTx(CTransaction(rawTx)); + }, + }; } -static UniValue decoderawtransaction(const Config &config, - const JSONRPCRequest &request) { - RPCHelpMan{ +static RPCHelpMan decoderawtransaction() { + return RPCHelpMan{ "decoderawtransaction", "Return a JSON object representing the serialized, hex-encoded " "transaction.\n", @@ -634,21 +648,23 @@ }}, RPCExamples{HelpExampleCli("decoderawtransaction", "\"hexstring\"") + HelpExampleRpc("decoderawtransaction", "\"hexstring\"")}, - } - .Check(request); - - RPCTypeCheck(request.params, {UniValue::VSTR}); + [&](const RPCHelpMan &self, const Config &config, + const JSONRPCRequest &request) -> UniValue { + RPCTypeCheck(request.params, {UniValue::VSTR}); - CMutableTransaction mtx; + CMutableTransaction mtx; - if (!DecodeHexTx(mtx, request.params[0].get_str())) { - throw JSONRPCError(RPC_DESERIALIZATION_ERROR, "TX decode failed"); - } + if (!DecodeHexTx(mtx, request.params[0].get_str())) { + throw JSONRPCError(RPC_DESERIALIZATION_ERROR, + "TX decode failed"); + } - UniValue result(UniValue::VOBJ); - TxToUniv(CTransaction(std::move(mtx)), uint256(), result, false); + UniValue result(UniValue::VOBJ); + TxToUniv(CTransaction(std::move(mtx)), uint256(), result, false); - return result; + return result; + }, + }; } static std::string GetAllOutputTypes() { @@ -660,9 +676,8 @@ return Join(ret, ", "); } -static UniValue decodescript(const Config &config, - const JSONRPCRequest &request) { - RPCHelpMan{ +static RPCHelpMan decodescript() { + return RPCHelpMan{ "decodescript", "Decode a hex-encoded script.\n", { @@ -690,38 +705,38 @@ }}, RPCExamples{HelpExampleCli("decodescript", "\"hexstring\"") + HelpExampleRpc("decodescript", "\"hexstring\"")}, - } - .Check(request); - - RPCTypeCheck(request.params, {UniValue::VSTR}); - - UniValue r(UniValue::VOBJ); - CScript script; - if (request.params[0].get_str().size() > 0) { - std::vector scriptData( - ParseHexV(request.params[0], "argument")); - script = CScript(scriptData.begin(), scriptData.end()); - } else { - // Empty scripts are valid. - } + [&](const RPCHelpMan &self, const Config &config, + const JSONRPCRequest &request) -> UniValue { + RPCTypeCheck(request.params, {UniValue::VSTR}); - ScriptPubKeyToUniv(script, r, /* fIncludeHex */ false); + UniValue r(UniValue::VOBJ); + CScript script; + if (request.params[0].get_str().size() > 0) { + std::vector scriptData( + ParseHexV(request.params[0], "argument")); + script = CScript(scriptData.begin(), scriptData.end()); + } else { + // Empty scripts are valid. + } - UniValue type; - type = find_value(r, "type"); + ScriptPubKeyToUniv(script, r, /* fIncludeHex */ false); - if (type.isStr() && type.get_str() != "scripthash") { - // P2SH cannot be wrapped in a P2SH. If this script is already a P2SH, - // don't return the address for a P2SH of the P2SH. - r.pushKV("p2sh", EncodeDestination(ScriptHash(script), config)); - } + UniValue type; + type = find_value(r, "type"); - return r; + if (type.isStr() && type.get_str() != "scripthash") { + // P2SH cannot be wrapped in a P2SH. If this script is already a + // P2SH, don't return the address for a P2SH of the P2SH. + r.pushKV("p2sh", EncodeDestination(ScriptHash(script), config)); + } + + return r; + }, + }; } -static UniValue combinerawtransaction(const Config &config, - const JSONRPCRequest &request) { - RPCHelpMan{ +static RPCHelpMan combinerawtransaction() { + return RPCHelpMan{ "combinerawtransaction", "Combine multiple partially signed transactions into one " "transaction.\n" @@ -746,83 +761,86 @@ "The hex-encoded raw transaction with signature(s)"}, RPCExamples{HelpExampleCli("combinerawtransaction", R"('["myhex1", "myhex2", "myhex3"]')")}, - } - .Check(request); - - UniValue txs = request.params[0].get_array(); - std::vector txVariants(txs.size()); - - for (unsigned int idx = 0; idx < txs.size(); idx++) { - if (!DecodeHexTx(txVariants[idx], txs[idx].get_str())) { - throw JSONRPCError(RPC_DESERIALIZATION_ERROR, - strprintf("TX decode failed for tx %d", idx)); - } - } - - if (txVariants.empty()) { - throw JSONRPCError(RPC_DESERIALIZATION_ERROR, "Missing transactions"); - } - - // mergedTx will end up with all the signatures; it - // starts as a clone of the rawtx: - CMutableTransaction mergedTx(txVariants[0]); - - // Fetch previous transactions (inputs): - CCoinsView viewDummy; - CCoinsViewCache view(&viewDummy); - { - const CTxMemPool &mempool = EnsureMemPool(request.context); - LOCK(cs_main); - LOCK(mempool.cs); - CCoinsViewCache &viewChain = ::ChainstateActive().CoinsTip(); - CCoinsViewMemPool viewMempool(&viewChain, mempool); - // temporarily switch cache backend to db+mempool view - view.SetBackend(viewMempool); - - for (const CTxIn &txin : mergedTx.vin) { - // Load entries from viewChain into view; can fail. - view.AccessCoin(txin.prevout); - } - - // switch back to avoid locking mempool for too long - view.SetBackend(viewDummy); - } + [&](const RPCHelpMan &self, const Config &config, + const JSONRPCRequest &request) -> UniValue { + UniValue txs = request.params[0].get_array(); + std::vector txVariants(txs.size()); + + for (unsigned int idx = 0; idx < txs.size(); idx++) { + if (!DecodeHexTx(txVariants[idx], txs[idx].get_str())) { + throw JSONRPCError( + RPC_DESERIALIZATION_ERROR, + strprintf("TX decode failed for tx %d", idx)); + } + } - // Use CTransaction for the constant parts of the - // transaction to avoid rehashing. - const CTransaction txConst(mergedTx); - // Sign what we can: - for (size_t i = 0; i < mergedTx.vin.size(); i++) { - CTxIn &txin = mergedTx.vin[i]; - const Coin &coin = view.AccessCoin(txin.prevout); - if (coin.IsSpent()) { - throw JSONRPCError(RPC_VERIFY_ERROR, - "Input not found or already spent"); - } - SignatureData sigdata; + if (txVariants.empty()) { + throw JSONRPCError(RPC_DESERIALIZATION_ERROR, + "Missing transactions"); + } - const CTxOut &txout = coin.GetTxOut(); + // mergedTx will end up with all the signatures; it + // starts as a clone of the rawtx: + CMutableTransaction mergedTx(txVariants[0]); - // ... and merge in other signatures: - for (const CMutableTransaction &txv : txVariants) { - if (txv.vin.size() > i) { - sigdata.MergeSignatureData(DataFromTransaction(txv, i, txout)); + // Fetch previous transactions (inputs): + CCoinsView viewDummy; + CCoinsViewCache view(&viewDummy); + { + const CTxMemPool &mempool = EnsureMemPool(request.context); + LOCK(cs_main); + LOCK(mempool.cs); + CCoinsViewCache &viewChain = ::ChainstateActive().CoinsTip(); + CCoinsViewMemPool viewMempool(&viewChain, mempool); + // temporarily switch cache backend to db+mempool view + view.SetBackend(viewMempool); + + for (const CTxIn &txin : mergedTx.vin) { + // Load entries from viewChain into view; can fail. + view.AccessCoin(txin.prevout); + } + + // switch back to avoid locking mempool for too long + view.SetBackend(viewDummy); } - } - ProduceSignature( - DUMMY_SIGNING_PROVIDER, - MutableTransactionSignatureCreator(&mergedTx, i, txout.nValue), - txout.scriptPubKey, sigdata); - UpdateInput(txin, sigdata); - } + // Use CTransaction for the constant parts of the + // transaction to avoid rehashing. + const CTransaction txConst(mergedTx); + // Sign what we can: + for (size_t i = 0; i < mergedTx.vin.size(); i++) { + CTxIn &txin = mergedTx.vin[i]; + const Coin &coin = view.AccessCoin(txin.prevout); + if (coin.IsSpent()) { + throw JSONRPCError(RPC_VERIFY_ERROR, + "Input not found or already spent"); + } + SignatureData sigdata; + + const CTxOut &txout = coin.GetTxOut(); + + // ... and merge in other signatures: + for (const CMutableTransaction &txv : txVariants) { + if (txv.vin.size() > i) { + sigdata.MergeSignatureData( + DataFromTransaction(txv, i, txout)); + } + } + ProduceSignature(DUMMY_SIGNING_PROVIDER, + MutableTransactionSignatureCreator( + &mergedTx, i, txout.nValue), + txout.scriptPubKey, sigdata); + + UpdateInput(txin, sigdata); + } - return EncodeHexTx(CTransaction(mergedTx)); + return EncodeHexTx(CTransaction(mergedTx)); + }, + }; } -static UniValue signrawtransactionwithkey(const Config &config, - const JSONRPCRequest &request) { - RPCHelpMan{ +static RPCHelpMan signrawtransactionwithkey() { + return RPCHelpMan{ "signrawtransactionwithkey", "Sign inputs for raw transaction (serialized, hex-encoded).\n" "The second argument is an array of base58-encoded private\n" @@ -917,50 +935,52 @@ "\"myhex\" \"[\\\"key1\\\",\\\"key2\\\"]\"") + HelpExampleRpc("signrawtransactionwithkey", "\"myhex\", \"[\\\"key1\\\",\\\"key2\\\"]\"")}, - } - .Check(request); - - RPCTypeCheck( - request.params, - {UniValue::VSTR, UniValue::VARR, UniValue::VARR, UniValue::VSTR}, true); - - CMutableTransaction mtx; - if (!DecodeHexTx(mtx, request.params[0].get_str())) { - throw JSONRPCError(RPC_DESERIALIZATION_ERROR, "TX decode failed"); - } + [&](const RPCHelpMan &self, const Config &config, + const JSONRPCRequest &request) -> UniValue { + RPCTypeCheck(request.params, + {UniValue::VSTR, UniValue::VARR, UniValue::VARR, + UniValue::VSTR}, + true); + + CMutableTransaction mtx; + if (!DecodeHexTx(mtx, request.params[0].get_str())) { + throw JSONRPCError(RPC_DESERIALIZATION_ERROR, + "TX decode failed"); + } - FillableSigningProvider keystore; - const UniValue &keys = request.params[1].get_array(); - for (size_t idx = 0; idx < keys.size(); ++idx) { - UniValue k = keys[idx]; - CKey key = DecodeSecret(k.get_str()); - if (!key.IsValid()) { - throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, - "Invalid private key"); - } - keystore.AddKey(key); - } + FillableSigningProvider keystore; + const UniValue &keys = request.params[1].get_array(); + for (size_t idx = 0; idx < keys.size(); ++idx) { + UniValue k = keys[idx]; + CKey key = DecodeSecret(k.get_str()); + if (!key.IsValid()) { + throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, + "Invalid private key"); + } + keystore.AddKey(key); + } - // Fetch previous transactions (inputs): - std::map coins; - for (const CTxIn &txin : mtx.vin) { - // Create empty map entry keyed by prevout. - coins[txin.prevout]; - } - NodeContext &node = EnsureNodeContext(request.context); - FindCoins(node, coins); + // Fetch previous transactions (inputs): + std::map coins; + for (const CTxIn &txin : mtx.vin) { + // Create empty map entry keyed by prevout. + coins[txin.prevout]; + } + NodeContext &node = EnsureNodeContext(request.context); + FindCoins(node, coins); - // Parse the prevtxs array - ParsePrevouts(request.params[2], &keystore, coins); + // Parse the prevtxs array + ParsePrevouts(request.params[2], &keystore, coins); - UniValue result(UniValue::VOBJ); - SignTransaction(mtx, &keystore, coins, request.params[3], result); - return result; + UniValue result(UniValue::VOBJ); + SignTransaction(mtx, &keystore, coins, request.params[3], result); + return result; + }, + }; } -static UniValue sendrawtransaction(const Config &config, - const JSONRPCRequest &request) { - RPCHelpMan{ +static RPCHelpMan sendrawtransaction() { + return RPCHelpMan{ "sendrawtransaction", "Submits raw transaction (serialized, hex-encoded) to local node and " "network.\n" @@ -990,48 +1010,49 @@ HelpExampleCli("sendrawtransaction", "\"signedhex\"") + "\nAs a JSON-RPC call\n" + HelpExampleRpc("sendrawtransaction", "\"signedhex\"")}, - } - .Check(request); - - RPCTypeCheck(request.params, - { - UniValue::VSTR, - // VNUM or VSTR, checked inside AmountFromValue() - UniValueType(), - }); - - // parse hex string from parameter - CMutableTransaction mtx; - if (!DecodeHexTx(mtx, request.params[0].get_str())) { - throw JSONRPCError(RPC_DESERIALIZATION_ERROR, "TX decode failed"); - } + [&](const RPCHelpMan &self, const Config &config, + const JSONRPCRequest &request) -> UniValue { + RPCTypeCheck(request.params, + { + UniValue::VSTR, + // VNUM or VSTR, checked inside AmountFromValue() + UniValueType(), + }); + + // parse hex string from parameter + CMutableTransaction mtx; + if (!DecodeHexTx(mtx, request.params[0].get_str())) { + throw JSONRPCError(RPC_DESERIALIZATION_ERROR, + "TX decode failed"); + } - CTransactionRef tx(MakeTransactionRef(std::move(mtx))); + CTransactionRef tx(MakeTransactionRef(std::move(mtx))); - const CFeeRate max_raw_tx_fee_rate = - request.params[1].isNull() - ? DEFAULT_MAX_RAW_TX_FEE_RATE - : CFeeRate(AmountFromValue(request.params[1])); + const CFeeRate max_raw_tx_fee_rate = + request.params[1].isNull() + ? DEFAULT_MAX_RAW_TX_FEE_RATE + : CFeeRate(AmountFromValue(request.params[1])); - int64_t virtual_size = GetVirtualTransactionSize(*tx); - Amount max_raw_tx_fee = max_raw_tx_fee_rate.GetFee(virtual_size); + int64_t virtual_size = GetVirtualTransactionSize(*tx); + Amount max_raw_tx_fee = max_raw_tx_fee_rate.GetFee(virtual_size); - std::string err_string; - AssertLockNotHeld(cs_main); - NodeContext &node = EnsureNodeContext(request.context); - const TransactionError err = BroadcastTransaction( - node, config, tx, err_string, max_raw_tx_fee, /*relay*/ true, - /*wait_callback*/ true); - if (err != TransactionError::OK) { - throw JSONRPCTransactionError(err, err_string); - } + std::string err_string; + AssertLockNotHeld(cs_main); + NodeContext &node = EnsureNodeContext(request.context); + const TransactionError err = BroadcastTransaction( + node, config, tx, err_string, max_raw_tx_fee, /*relay*/ true, + /*wait_callback*/ true); + if (err != TransactionError::OK) { + throw JSONRPCTransactionError(err, err_string); + } - return tx->GetHash().GetHex(); + return tx->GetHash().GetHex(); + }, + }; } -static UniValue testmempoolaccept(const Config &config, - const JSONRPCRequest &request) { - RPCHelpMan{ +static RPCHelpMan testmempoolaccept() { + return RPCHelpMan{ "testmempoolaccept", "Returns result of mempool acceptance tests indicating if raw" " transaction (serialized, hex-encoded) would be accepted" @@ -1098,80 +1119,84 @@ HelpExampleCli("testmempoolaccept", R"('["signedhex"]')") + "\nAs a JSON-RPC call\n" + HelpExampleRpc("testmempoolaccept", "[\"signedhex\"]")}, - } - .Check(request); + [&](const RPCHelpMan &self, const Config &config, + const JSONRPCRequest &request) -> UniValue { + RPCTypeCheck(request.params, + { + UniValue::VARR, + // VNUM or VSTR, checked inside AmountFromValue() + UniValueType(), + }); + + if (request.params[0].get_array().size() != 1) { + throw JSONRPCError( + RPC_INVALID_PARAMETER, + "Array must contain exactly one raw transaction for now"); + } - RPCTypeCheck(request.params, - { - UniValue::VARR, - // VNUM or VSTR, checked inside AmountFromValue() - UniValueType(), - }); - - if (request.params[0].get_array().size() != 1) { - throw JSONRPCError( - RPC_INVALID_PARAMETER, - "Array must contain exactly one raw transaction for now"); - } + CMutableTransaction mtx; + if (!DecodeHexTx(mtx, request.params[0].get_array()[0].get_str())) { + throw JSONRPCError(RPC_DESERIALIZATION_ERROR, + "TX decode failed"); + } + CTransactionRef tx(MakeTransactionRef(std::move(mtx))); + const TxId &txid = tx->GetId(); - CMutableTransaction mtx; - if (!DecodeHexTx(mtx, request.params[0].get_array()[0].get_str())) { - throw JSONRPCError(RPC_DESERIALIZATION_ERROR, "TX decode failed"); - } - CTransactionRef tx(MakeTransactionRef(std::move(mtx))); - const TxId &txid = tx->GetId(); - - const CFeeRate max_raw_tx_fee_rate = - request.params[1].isNull() - ? DEFAULT_MAX_RAW_TX_FEE_RATE - : CFeeRate(AmountFromValue(request.params[1])); - - CTxMemPool &mempool = EnsureMemPool(request.context); - int64_t virtual_size = GetVirtualTransactionSize(*tx); - Amount max_raw_tx_fee = max_raw_tx_fee_rate.GetFee(virtual_size); - - UniValue result(UniValue::VARR); - UniValue result_0(UniValue::VOBJ); - result_0.pushKV("txid", txid.GetHex()); - - TxValidationState state; - bool test_accept_res; - Amount fee; - { - LOCK(cs_main); - test_accept_res = AcceptToMemoryPool( - config, mempool, state, std::move(tx), false /* bypass_limits */, - max_raw_tx_fee, true /* test_accept */, &fee); - } - result_0.pushKV("allowed", test_accept_res); - - // Only return the fee and size if the transaction would pass ATMP. - // These can be used to calculate the feerate. - if (test_accept_res) { - result_0.pushKV("size", virtual_size); - UniValue fees(UniValue::VOBJ); - fees.pushKV("base", fee); - result_0.pushKV("fees", fees); - } else { - if (state.IsInvalid()) { - if (state.GetResult() == TxValidationResult::TX_MISSING_INPUTS) { - result_0.pushKV("reject-reason", "missing-inputs"); + const CFeeRate max_raw_tx_fee_rate = + request.params[1].isNull() + ? DEFAULT_MAX_RAW_TX_FEE_RATE + : CFeeRate(AmountFromValue(request.params[1])); + + CTxMemPool &mempool = EnsureMemPool(request.context); + int64_t virtual_size = GetVirtualTransactionSize(*tx); + Amount max_raw_tx_fee = max_raw_tx_fee_rate.GetFee(virtual_size); + + UniValue result(UniValue::VARR); + UniValue result_0(UniValue::VOBJ); + result_0.pushKV("txid", txid.GetHex()); + + TxValidationState state; + bool test_accept_res; + Amount fee; + { + LOCK(cs_main); + test_accept_res = AcceptToMemoryPool( + config, mempool, state, std::move(tx), + false /* bypass_limits */, max_raw_tx_fee, + true /* test_accept */, &fee); + } + result_0.pushKV("allowed", test_accept_res); + + // Only return the fee and size if the transaction would pass ATMP. + // These can be used to calculate the feerate. + if (test_accept_res) { + result_0.pushKV("size", virtual_size); + UniValue fees(UniValue::VOBJ); + fees.pushKV("base", fee); + result_0.pushKV("fees", fees); } else { - result_0.pushKV("reject-reason", - strprintf("%s", state.GetRejectReason())); + if (state.IsInvalid()) { + if (state.GetResult() == + TxValidationResult::TX_MISSING_INPUTS) { + result_0.pushKV("reject-reason", "missing-inputs"); + } else { + result_0.pushKV( + "reject-reason", + strprintf("%s", state.GetRejectReason())); + } + } else { + result_0.pushKV("reject-reason", state.GetRejectReason()); + } } - } else { - result_0.pushKV("reject-reason", state.GetRejectReason()); - } - } - result.push_back(std::move(result_0)); - return result; + result.push_back(std::move(result_0)); + return result; + }, + }; } -static UniValue decodepsbt(const Config &config, - const JSONRPCRequest &request) { - RPCHelpMan{ +static RPCHelpMan decodepsbt() { + return RPCHelpMan{ "decodepsbt", "Return a JSON object representing the serialized, base64-encoded " "partially signed Bitcoin transaction.\n", @@ -1329,185 +1354,192 @@ "been filled."}, }}, RPCExamples{HelpExampleCli("decodepsbt", "\"psbt\"")}, - } - .Check(request); - - RPCTypeCheck(request.params, {UniValue::VSTR}); - - // Unserialize the transactions - PartiallySignedTransaction psbtx; - std::string error; - if (!DecodeBase64PSBT(psbtx, request.params[0].get_str(), error)) { - throw JSONRPCError(RPC_DESERIALIZATION_ERROR, - strprintf("TX decode failed %s", error)); - } - - UniValue result(UniValue::VOBJ); - - // Add the decoded tx - UniValue tx_univ(UniValue::VOBJ); - TxToUniv(CTransaction(*psbtx.tx), uint256(), tx_univ, false); - result.pushKV("tx", tx_univ); - - // Unknown data - if (psbtx.unknown.size() > 0) { - UniValue unknowns(UniValue::VOBJ); - for (auto entry : psbtx.unknown) { - unknowns.pushKV(HexStr(entry.first), HexStr(entry.second)); - } - result.pushKV("unknown", unknowns); - } - - // inputs - Amount total_in = Amount::zero(); - bool have_all_utxos = true; - UniValue inputs(UniValue::VARR); - for (size_t i = 0; i < psbtx.inputs.size(); ++i) { - const PSBTInput &input = psbtx.inputs[i]; - UniValue in(UniValue::VOBJ); - // UTXOs - if (!input.utxo.IsNull()) { - const CTxOut &txout = input.utxo; - - UniValue out(UniValue::VOBJ); - - out.pushKV("amount", txout.nValue); - 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; + [&](const RPCHelpMan &self, const Config &config, + const JSONRPCRequest &request) -> UniValue { + RPCTypeCheck(request.params, {UniValue::VSTR}); + + // Unserialize the transactions + PartiallySignedTransaction psbtx; + std::string error; + if (!DecodeBase64PSBT(psbtx, request.params[0].get_str(), error)) { + throw JSONRPCError(RPC_DESERIALIZATION_ERROR, + strprintf("TX decode failed %s", error)); } - UniValue o(UniValue::VOBJ); - ScriptToUniv(txout.scriptPubKey, o, true); - out.pushKV("scriptPubKey", o); - in.pushKV("utxo", out); - } else { - have_all_utxos = false; - } - - // Partial sigs - if (!input.partial_sigs.empty()) { - UniValue partial_sigs(UniValue::VOBJ); - for (const auto &sig : input.partial_sigs) { - partial_sigs.pushKV(HexStr(sig.second.first), - HexStr(sig.second.second)); - } - in.pushKV("partial_signatures", partial_sigs); - } - - // Sighash - uint8_t sighashbyte = input.sighash_type.getRawSigHashType() & 0xff; - if (sighashbyte > 0) { - in.pushKV("sighash", SighashToStr(sighashbyte)); - } - - // Redeem script - if (!input.redeem_script.empty()) { - UniValue r(UniValue::VOBJ); - ScriptToUniv(input.redeem_script, r, false); - in.pushKV("redeem_script", r); - } + UniValue result(UniValue::VOBJ); - // 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)); + // Add the decoded tx + UniValue tx_univ(UniValue::VOBJ); + TxToUniv(CTransaction(*psbtx.tx), uint256(), tx_univ, false); + result.pushKV("tx", tx_univ); - keypath.pushKV( - "master_fingerprint", - strprintf("%08x", ReadBE32(entry.second.fingerprint))); - keypath.pushKV("path", WriteHDKeypath(entry.second.path)); - keypaths.push_back(keypath); + // Unknown data + if (psbtx.unknown.size() > 0) { + UniValue unknowns(UniValue::VOBJ); + for (auto entry : psbtx.unknown) { + unknowns.pushKV(HexStr(entry.first), HexStr(entry.second)); + } + result.pushKV("unknown", unknowns); } - in.pushKV("bip32_derivs", keypaths); - } - - // Final scriptSig - if (!input.final_script_sig.empty()) { - UniValue scriptsig(UniValue::VOBJ); - scriptsig.pushKV("asm", - ScriptToAsmStr(input.final_script_sig, true)); - scriptsig.pushKV("hex", HexStr(input.final_script_sig)); - in.pushKV("final_scriptSig", scriptsig); - } - // Unknown data - if (input.unknown.size() > 0) { - UniValue unknowns(UniValue::VOBJ); - for (auto entry : input.unknown) { - unknowns.pushKV(HexStr(entry.first), HexStr(entry.second)); + // inputs + Amount total_in = Amount::zero(); + bool have_all_utxos = true; + UniValue inputs(UniValue::VARR); + for (size_t i = 0; i < psbtx.inputs.size(); ++i) { + const PSBTInput &input = psbtx.inputs[i]; + UniValue in(UniValue::VOBJ); + // UTXOs + if (!input.utxo.IsNull()) { + const CTxOut &txout = input.utxo; + + UniValue out(UniValue::VOBJ); + + out.pushKV("amount", txout.nValue); + 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; + } + + UniValue o(UniValue::VOBJ); + ScriptToUniv(txout.scriptPubKey, o, true); + out.pushKV("scriptPubKey", o); + in.pushKV("utxo", out); + } else { + have_all_utxos = false; + } + + // Partial sigs + if (!input.partial_sigs.empty()) { + UniValue partial_sigs(UniValue::VOBJ); + for (const auto &sig : input.partial_sigs) { + partial_sigs.pushKV(HexStr(sig.second.first), + HexStr(sig.second.second)); + } + in.pushKV("partial_signatures", partial_sigs); + } + + // Sighash + uint8_t sighashbyte = + input.sighash_type.getRawSigHashType() & 0xff; + if (sighashbyte > 0) { + in.pushKV("sighash", SighashToStr(sighashbyte)); + } + + // Redeem script + if (!input.redeem_script.empty()) { + UniValue r(UniValue::VOBJ); + ScriptToUniv(input.redeem_script, r, false); + in.pushKV("redeem_script", r); + } + + // keypaths + if (!input.hd_keypaths.empty()) { + UniValue keypaths(UniValue::VARR); + for (auto entry : input.hd_keypaths) { + UniValue keypath(UniValue::VOBJ); + keypath.pushKV("pubkey", HexStr(entry.first)); + + keypath.pushKV( + "master_fingerprint", + strprintf("%08x", + ReadBE32(entry.second.fingerprint))); + keypath.pushKV("path", + WriteHDKeypath(entry.second.path)); + keypaths.push_back(keypath); + } + in.pushKV("bip32_derivs", keypaths); + } + + // Final scriptSig + if (!input.final_script_sig.empty()) { + UniValue scriptsig(UniValue::VOBJ); + scriptsig.pushKV( + "asm", ScriptToAsmStr(input.final_script_sig, true)); + scriptsig.pushKV("hex", HexStr(input.final_script_sig)); + in.pushKV("final_scriptSig", scriptsig); + } + + // Unknown data + if (input.unknown.size() > 0) { + UniValue unknowns(UniValue::VOBJ); + for (auto entry : input.unknown) { + unknowns.pushKV(HexStr(entry.first), + HexStr(entry.second)); + } + in.pushKV("unknown", unknowns); + } + + inputs.push_back(in); } - in.pushKV("unknown", unknowns); - } - - inputs.push_back(in); - } - result.pushKV("inputs", inputs); - - // outputs - Amount output_value = Amount::zero(); - UniValue outputs(UniValue::VARR); - for (size_t i = 0; i < psbtx.outputs.size(); ++i) { - const PSBTOutput &output = psbtx.outputs[i]; - UniValue out(UniValue::VOBJ); - // Redeem script - if (!output.redeem_script.empty()) { - UniValue r(UniValue::VOBJ); - ScriptToUniv(output.redeem_script, r, false); - out.pushKV("redeem_script", r); - } - - // keypaths - if (!output.hd_keypaths.empty()) { - UniValue keypaths(UniValue::VARR); - for (auto entry : output.hd_keypaths) { - UniValue keypath(UniValue::VOBJ); - keypath.pushKV("pubkey", HexStr(entry.first)); - keypath.pushKV( - "master_fingerprint", - strprintf("%08x", ReadBE32(entry.second.fingerprint))); - keypath.pushKV("path", WriteHDKeypath(entry.second.path)); - keypaths.push_back(keypath); - } - out.pushKV("bip32_derivs", keypaths); - } - - // Unknown data - if (output.unknown.size() > 0) { - UniValue unknowns(UniValue::VOBJ); - for (auto entry : output.unknown) { - unknowns.pushKV(HexStr(entry.first), HexStr(entry.second)); + result.pushKV("inputs", inputs); + + // outputs + Amount output_value = Amount::zero(); + UniValue outputs(UniValue::VARR); + for (size_t i = 0; i < psbtx.outputs.size(); ++i) { + const PSBTOutput &output = psbtx.outputs[i]; + UniValue out(UniValue::VOBJ); + // Redeem script + if (!output.redeem_script.empty()) { + UniValue r(UniValue::VOBJ); + ScriptToUniv(output.redeem_script, r, false); + out.pushKV("redeem_script", r); + } + + // keypaths + if (!output.hd_keypaths.empty()) { + UniValue keypaths(UniValue::VARR); + for (auto entry : output.hd_keypaths) { + UniValue keypath(UniValue::VOBJ); + keypath.pushKV("pubkey", HexStr(entry.first)); + keypath.pushKV( + "master_fingerprint", + strprintf("%08x", + ReadBE32(entry.second.fingerprint))); + keypath.pushKV("path", + WriteHDKeypath(entry.second.path)); + keypaths.push_back(keypath); + } + out.pushKV("bip32_derivs", keypaths); + } + + // Unknown data + if (output.unknown.size() > 0) { + UniValue unknowns(UniValue::VOBJ); + for (auto entry : output.unknown) { + unknowns.pushKV(HexStr(entry.first), + HexStr(entry.second)); + } + out.pushKV("unknown", unknowns); + } + + outputs.push_back(out); + + // Fee calculation + 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", total_in - output_value); } - 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", total_in - output_value); - } - return result; + return result; + }, + }; } -static UniValue combinepsbt(const Config &config, - const JSONRPCRequest &request) { - RPCHelpMan{ +static RPCHelpMan combinepsbt() { + return RPCHelpMan{ "combinepsbt", "Combine multiple partially signed Bitcoin transactions into one " "transaction.\n" @@ -1528,42 +1560,42 @@ "The base64-encoded partially signed transaction"}, RPCExamples{HelpExampleCli( "combinepsbt", R"('["mybase64_1", "mybase64_2", "mybase64_3"]')")}, - } - .Check(request); - - RPCTypeCheck(request.params, {UniValue::VARR}, true); - - // Unserialize the transactions - std::vector psbtxs; - UniValue txs = request.params[0].get_array(); - if (txs.empty()) { - throw JSONRPCError(RPC_INVALID_PARAMETER, - "Parameter 'txs' cannot be empty"); - } - for (size_t i = 0; i < txs.size(); ++i) { - PartiallySignedTransaction psbtx; - std::string error; - if (!DecodeBase64PSBT(psbtx, txs[i].get_str(), error)) { - throw JSONRPCError(RPC_DESERIALIZATION_ERROR, - strprintf("TX decode failed %s", error)); - } - psbtxs.push_back(psbtx); - } + [&](const RPCHelpMan &self, const Config &config, + const JSONRPCRequest &request) -> UniValue { + RPCTypeCheck(request.params, {UniValue::VARR}, true); + + // Unserialize the transactions + std::vector psbtxs; + UniValue txs = request.params[0].get_array(); + if (txs.empty()) { + throw JSONRPCError(RPC_INVALID_PARAMETER, + "Parameter 'txs' cannot be empty"); + } + for (size_t i = 0; i < txs.size(); ++i) { + PartiallySignedTransaction psbtx; + std::string error; + if (!DecodeBase64PSBT(psbtx, txs[i].get_str(), error)) { + throw JSONRPCError(RPC_DESERIALIZATION_ERROR, + strprintf("TX decode failed %s", error)); + } + psbtxs.push_back(psbtx); + } - PartiallySignedTransaction merged_psbt; - const TransactionError error = CombinePSBTs(merged_psbt, psbtxs); - if (error != TransactionError::OK) { - throw JSONRPCTransactionError(error); - } + 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(MakeUCharSpan(ssTx)); + CDataStream ssTx(SER_NETWORK, PROTOCOL_VERSION); + ssTx << merged_psbt; + return EncodeBase64(MakeUCharSpan(ssTx)); + }, + }; } -static UniValue finalizepsbt(const Config &config, - const JSONRPCRequest &request) { - RPCHelpMan{ +static RPCHelpMan finalizepsbt() { + return RPCHelpMan{ "finalizepsbt", "Finalize the inputs of a PSBT. If the transaction is fully signed, it " "will produce a\n" @@ -1594,46 +1626,48 @@ "If the transaction has a complete set of signatures"}, }}, RPCExamples{HelpExampleCli("finalizepsbt", "\"psbt\"")}, - } - .Check(request); - - RPCTypeCheck(request.params, {UniValue::VSTR, UniValue::VBOOL}, true); - - // Unserialize the transactions - PartiallySignedTransaction psbtx; - std::string error; - if (!DecodeBase64PSBT(psbtx, request.params[0].get_str(), error)) { - throw JSONRPCError(RPC_DESERIALIZATION_ERROR, - strprintf("TX decode failed %s", error)); - } + [&](const RPCHelpMan &self, const Config &config, + const JSONRPCRequest &request) -> UniValue { + RPCTypeCheck(request.params, {UniValue::VSTR, UniValue::VBOOL}, + true); + + // Unserialize the transactions + PartiallySignedTransaction psbtx; + std::string error; + if (!DecodeBase64PSBT(psbtx, request.params[0].get_str(), error)) { + throw JSONRPCError(RPC_DESERIALIZATION_ERROR, + strprintf("TX decode failed %s", error)); + } - bool extract = request.params[1].isNull() || (!request.params[1].isNull() && - request.params[1].get_bool()); + bool extract = + request.params[1].isNull() || + (!request.params[1].isNull() && request.params[1].get_bool()); - CMutableTransaction mtx; - bool complete = FinalizeAndExtractPSBT(psbtx, mtx); + CMutableTransaction mtx; + bool complete = FinalizeAndExtractPSBT(psbtx, mtx); - UniValue result(UniValue::VOBJ); - CDataStream ssTx(SER_NETWORK, PROTOCOL_VERSION); - std::string result_str; + 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); + 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; + return result; + }, + }; } -static UniValue createpsbt(const Config &config, - const JSONRPCRequest &request) { - RPCHelpMan{ +static RPCHelpMan createpsbt() { + return RPCHelpMan{ "createpsbt", "Creates a transaction in the Partially Signed Transaction format.\n" "Implements the Creator role.\n", @@ -1709,41 +1743,41 @@ RPCExamples{HelpExampleCli( "createpsbt", "\"[{\\\"txid\\\":\\\"myid\\\",\\\"vout\\\":0}]" "\" \"[{\\\"data\\\":\\\"00010203\\\"}]\"")}, - } - .Check(request); - - RPCTypeCheck(request.params, - { - UniValue::VARR, - UniValueType(), // ARR or OBJ, checked later - UniValue::VNUM, - }, - true); - - CMutableTransaction rawTx = - ConstructTransaction(config.GetChainParams(), request.params[0], - request.params[1], request.params[2]); - - // Make a blank psbt - PartiallySignedTransaction psbtx; - psbtx.tx = rawTx; - for (size_t i = 0; i < rawTx.vin.size(); ++i) { - psbtx.inputs.push_back(PSBTInput()); - } - for (size_t i = 0; i < rawTx.vout.size(); ++i) { - psbtx.outputs.push_back(PSBTOutput()); - } + [&](const RPCHelpMan &self, const Config &config, + const JSONRPCRequest &request) -> UniValue { + RPCTypeCheck(request.params, + { + UniValue::VARR, + UniValueType(), // ARR or OBJ, checked later + UniValue::VNUM, + }, + true); + + CMutableTransaction rawTx = + ConstructTransaction(config.GetChainParams(), request.params[0], + request.params[1], request.params[2]); + + // Make a blank psbt + PartiallySignedTransaction psbtx; + psbtx.tx = rawTx; + for (size_t i = 0; i < rawTx.vin.size(); ++i) { + psbtx.inputs.push_back(PSBTInput()); + } + for (size_t i = 0; i < rawTx.vout.size(); ++i) { + psbtx.outputs.push_back(PSBTOutput()); + } - // Serialize the PSBT - CDataStream ssTx(SER_NETWORK, PROTOCOL_VERSION); - ssTx << psbtx; + // Serialize the PSBT + CDataStream ssTx(SER_NETWORK, PROTOCOL_VERSION); + ssTx << psbtx; - return EncodeBase64(MakeUCharSpan(ssTx)); + return EncodeBase64(MakeUCharSpan(ssTx)); + }, + }; } -static UniValue converttopsbt(const Config &config, - const JSONRPCRequest &request) { - RPCHelpMan{ +static RPCHelpMan converttopsbt() { + return RPCHelpMan{ "converttopsbt", "Converts a network serialized transaction to a PSBT. " "This should be used only with createrawtransaction and " @@ -1768,47 +1802,51 @@ "\" \"[{\\\"data\\\":\\\"00010203\\\"}]\"") + "\nConvert the transaction to a PSBT\n" + HelpExampleCli("converttopsbt", "\"rawtransaction\"")}, - } - .Check(request); - - RPCTypeCheck(request.params, {UniValue::VSTR, UniValue::VBOOL}, true); - - // parse hex string from parameter - CMutableTransaction tx; - bool permitsigdata = - request.params[1].isNull() ? false : request.params[1].get_bool(); - if (!DecodeHexTx(tx, request.params[0].get_str())) { - throw JSONRPCError(RPC_DESERIALIZATION_ERROR, "TX decode failed"); - } + [&](const RPCHelpMan &self, const Config &config, + const JSONRPCRequest &request) -> UniValue { + RPCTypeCheck(request.params, {UniValue::VSTR, UniValue::VBOOL}, + true); + + // parse hex string from parameter + CMutableTransaction tx; + bool permitsigdata = request.params[1].isNull() + ? false + : request.params[1].get_bool(); + if (!DecodeHexTx(tx, request.params[0].get_str())) { + throw JSONRPCError(RPC_DESERIALIZATION_ERROR, + "TX decode failed"); + } - // Remove all scriptSigs from inputs - for (CTxIn &input : tx.vin) { - if (!input.scriptSig.empty() && !permitsigdata) { - throw JSONRPCError(RPC_DESERIALIZATION_ERROR, - "Inputs must not have scriptSigs"); - } - input.scriptSig.clear(); - } + // Remove all scriptSigs from inputs + for (CTxIn &input : tx.vin) { + if (!input.scriptSig.empty() && !permitsigdata) { + throw JSONRPCError(RPC_DESERIALIZATION_ERROR, + "Inputs must not have scriptSigs"); + } + input.scriptSig.clear(); + } - // Make a blank psbt - PartiallySignedTransaction psbtx; - psbtx.tx = tx; - for (size_t i = 0; i < tx.vin.size(); ++i) { - psbtx.inputs.push_back(PSBTInput()); - } - for (size_t i = 0; i < tx.vout.size(); ++i) { - psbtx.outputs.push_back(PSBTOutput()); - } + // Make a blank psbt + PartiallySignedTransaction psbtx; + psbtx.tx = tx; + for (size_t i = 0; i < tx.vin.size(); ++i) { + psbtx.inputs.push_back(PSBTInput()); + } + for (size_t i = 0; i < tx.vout.size(); ++i) { + psbtx.outputs.push_back(PSBTOutput()); + } - // Serialize the PSBT - CDataStream ssTx(SER_NETWORK, PROTOCOL_VERSION); - ssTx << psbtx; + // Serialize the PSBT + CDataStream ssTx(SER_NETWORK, PROTOCOL_VERSION); + ssTx << psbtx; - return EncodeBase64(MakeUCharSpan(ssTx)); + return EncodeBase64(MakeUCharSpan(ssTx)); + }, + }; } -UniValue utxoupdatepsbt(const Config &config, const JSONRPCRequest &request) { - RPCHelpMan{ +RPCHelpMan utxoupdatepsbt() { + return RPCHelpMan{ "utxoupdatepsbt", "Updates all inputs and outputs in a PSBT with data from output " "descriptors, the UTXO set or the mempool.\n", @@ -1838,79 +1876,83 @@ RPCResult{RPCResult::Type::STR, "", "The base64-encoded partially signed transaction with inputs " "updated"}, - RPCExamples{HelpExampleCli("utxoupdatepsbt", "\"psbt\"")}} - .Check(request); - - RPCTypeCheck(request.params, {UniValue::VSTR, UniValue::VARR}, true); - - // Unserialize the transactions - PartiallySignedTransaction psbtx; - std::string error; - if (!DecodeBase64PSBT(psbtx, request.params[0].get_str(), error)) { - throw JSONRPCError(RPC_DESERIALIZATION_ERROR, - strprintf("TX decode failed %s", error)); - } - - // Parse descriptors, if any. - FlatSigningProvider provider; - if (!request.params[1].isNull()) { - auto descs = request.params[1].get_array(); - for (size_t i = 0; i < descs.size(); ++i) { - EvalDescriptorStringOrObject(descs[i], provider); - } - } - // We don't actually need private keys further on; hide them as a - // precaution. - HidingSigningProvider public_provider(&provider, /* nosign */ true, - /* nobip32derivs */ false); - - // Fetch previous transactions (inputs): - CCoinsView viewDummy; - CCoinsViewCache view(&viewDummy); - { - const CTxMemPool &mempool = EnsureMemPool(request.context); - LOCK2(cs_main, mempool.cs); - CCoinsViewCache &viewChain = ::ChainstateActive().CoinsTip(); - CCoinsViewMemPool viewMempool(&viewChain, mempool); - // temporarily switch cache backend to db+mempool view - view.SetBackend(viewMempool); - - for (const CTxIn &txin : psbtx.tx->vin) { - // Load entries from viewChain into view; can fail. - view.AccessCoin(txin.prevout); - } + RPCExamples{HelpExampleCli("utxoupdatepsbt", "\"psbt\"")}, + [&](const RPCHelpMan &self, const Config &config, + const JSONRPCRequest &request) -> UniValue { + RPCTypeCheck(request.params, {UniValue::VSTR, UniValue::VARR}, + true); + + // Unserialize the transactions + PartiallySignedTransaction psbtx; + std::string error; + if (!DecodeBase64PSBT(psbtx, request.params[0].get_str(), error)) { + throw JSONRPCError(RPC_DESERIALIZATION_ERROR, + strprintf("TX decode failed %s", error)); + } - // switch back to avoid locking mempool for too long - view.SetBackend(viewDummy); - } + // Parse descriptors, if any. + FlatSigningProvider provider; + if (!request.params[1].isNull()) { + auto descs = request.params[1].get_array(); + for (size_t i = 0; i < descs.size(); ++i) { + EvalDescriptorStringOrObject(descs[i], provider); + } + } + // We don't actually need private keys further on; hide them as a + // precaution. + HidingSigningProvider public_provider(&provider, /* nosign */ true, + /* nobip32derivs */ false); + + // Fetch previous transactions (inputs): + CCoinsView viewDummy; + CCoinsViewCache view(&viewDummy); + { + const CTxMemPool &mempool = EnsureMemPool(request.context); + LOCK2(cs_main, mempool.cs); + CCoinsViewCache &viewChain = ::ChainstateActive().CoinsTip(); + CCoinsViewMemPool viewMempool(&viewChain, mempool); + // temporarily switch cache backend to db+mempool view + view.SetBackend(viewMempool); + + for (const CTxIn &txin : psbtx.tx->vin) { + // Load entries from viewChain into view; can fail. + view.AccessCoin(txin.prevout); + } + + // switch back to avoid locking mempool for too long + view.SetBackend(viewDummy); + } - // Fill the inputs - for (size_t i = 0; i < psbtx.tx->vin.size(); ++i) { - PSBTInput &input = psbtx.inputs.at(i); + // Fill the inputs + for (size_t i = 0; i < psbtx.tx->vin.size(); ++i) { + PSBTInput &input = psbtx.inputs.at(i); - if (!input.utxo.IsNull()) { - continue; - } + if (!input.utxo.IsNull()) { + continue; + } - // Update script/keypath information using descriptor data. - // Note that SignPSBTInput does a lot more than just constructing ECDSA - // signatures we don't actually care about those here, in fact. - SignPSBTInput(public_provider, psbtx, i, - /* sighash_type */ SigHashType().withForkId()); - } + // Update script/keypath information using descriptor data. + // Note that SignPSBTInput does a lot more than just + // constructing ECDSA signatures we don't actually care about + // those here, in fact. + SignPSBTInput(public_provider, psbtx, i, + /* sighash_type */ SigHashType().withForkId()); + } - // Update script/keypath information using descriptor data. - for (unsigned int i = 0; i < psbtx.tx->vout.size(); ++i) { - UpdatePSBTOutput(public_provider, psbtx, i); - } + // Update script/keypath information using descriptor data. + for (unsigned int i = 0; i < psbtx.tx->vout.size(); ++i) { + UpdatePSBTOutput(public_provider, psbtx, i); + } - CDataStream ssTx(SER_NETWORK, PROTOCOL_VERSION); - ssTx << psbtx; - return EncodeBase64(MakeUCharSpan(ssTx)); + CDataStream ssTx(SER_NETWORK, PROTOCOL_VERSION); + ssTx << psbtx; + return EncodeBase64(MakeUCharSpan(ssTx)); + }, + }; } -UniValue joinpsbts(const Config &config, const JSONRPCRequest &request) { - RPCHelpMan{ +RPCHelpMan joinpsbts() { + return RPCHelpMan{ "joinpsbts", "Joins multiple distinct PSBTs with different inputs and outputs " "into one PSBT with inputs and outputs from all of the PSBTs\n" @@ -1923,96 +1965,106 @@ "A base64 string of a PSBT"}}}}, RPCResult{RPCResult::Type::STR, "", "The base64-encoded partially signed transaction"}, - RPCExamples{HelpExampleCli("joinpsbts", "\"psbt\"")}} - .Check(request); - - RPCTypeCheck(request.params, {UniValue::VARR}, true); - - // Unserialize the transactions - std::vector 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."); - } + RPCExamples{HelpExampleCli("joinpsbts", "\"psbt\"")}, + [&](const RPCHelpMan &self, const Config &config, + const JSONRPCRequest &request) -> UniValue { + RPCTypeCheck(request.params, {UniValue::VARR}, true); - uint32_t best_version = 1; - uint32_t best_locktime = 0xffffffff; - for (size_t i = 0; i < txs.size(); ++i) { - PartiallySignedTransaction psbtx; - std::string error; - if (!DecodeBase64PSBT(psbtx, txs[i].get_str(), error)) { - throw JSONRPCError(RPC_DESERIALIZATION_ERROR, - strprintf("TX decode failed %s", error)); - } - psbtxs.push_back(psbtx); - // Choose the highest version number - if (static_cast(psbtx.tx->nVersion) > best_version) { - best_version = static_cast(psbtx.tx->nVersion); - } - // Choose the lowest lock time - if (psbtx.tx->nLockTime < best_locktime) { - best_locktime = psbtx.tx->nLockTime; - } - } + // Unserialize the transactions + std::vector psbtxs; + UniValue txs = request.params[0].get_array(); - // Create a blank psbt where everything will be added - PartiallySignedTransaction merged_psbt; - merged_psbt.tx = CMutableTransaction(); - merged_psbt.tx->nVersion = static_cast(best_version); - merged_psbt.tx->nLockTime = best_locktime; - - // Merge - for (auto &psbt : psbtxs) { - for (size_t i = 0; i < psbt.tx->vin.size(); ++i) { - if (!merged_psbt.AddInput(psbt.tx->vin[i], psbt.inputs[i])) { + if (txs.size() <= 1) { throw JSONRPCError( RPC_INVALID_PARAMETER, - strprintf( - "Input %s:%d exists in multiple PSBTs", - psbt.tx->vin[i].prevout.GetTxId().ToString().c_str(), - psbt.tx->vin[i].prevout.GetN())); + "At least two PSBTs are required to join PSBTs."); } - } - for (size_t i = 0; i < psbt.tx->vout.size(); ++i) { - merged_psbt.AddOutput(psbt.tx->vout[i], psbt.outputs[i]); - } - merged_psbt.unknown.insert(psbt.unknown.begin(), psbt.unknown.end()); - } - // Generate list of shuffled indices for shuffling inputs and outputs of the - // merged PSBT - std::vector input_indices(merged_psbt.inputs.size()); - std::iota(input_indices.begin(), input_indices.end(), 0); - std::vector 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()); + uint32_t best_version = 1; + uint32_t best_locktime = 0xffffffff; + for (size_t i = 0; i < txs.size(); ++i) { + PartiallySignedTransaction psbtx; + std::string error; + if (!DecodeBase64PSBT(psbtx, txs[i].get_str(), error)) { + throw JSONRPCError(RPC_DESERIALIZATION_ERROR, + strprintf("TX decode failed %s", error)); + } + psbtxs.push_back(psbtx); + // Choose the highest version number + if (static_cast(psbtx.tx->nVersion) > best_version) { + best_version = static_cast(psbtx.tx->nVersion); + } + // Choose the lowest lock time + if (psbtx.tx->nLockTime < best_locktime) { + best_locktime = psbtx.tx->nLockTime; + } + } - CDataStream ssTx(SER_NETWORK, PROTOCOL_VERSION); - ssTx << shuffled_psbt; - return EncodeBase64(MakeUCharSpan(ssTx)); + // Create a blank psbt where everything will be added + PartiallySignedTransaction merged_psbt; + merged_psbt.tx = CMutableTransaction(); + merged_psbt.tx->nVersion = static_cast(best_version); + merged_psbt.tx->nLockTime = best_locktime; + + // Merge + for (auto &psbt : psbtxs) { + for (size_t i = 0; i < psbt.tx->vin.size(); ++i) { + if (!merged_psbt.AddInput(psbt.tx->vin[i], + psbt.inputs[i])) { + throw JSONRPCError( + RPC_INVALID_PARAMETER, + strprintf("Input %s:%d exists in multiple PSBTs", + psbt.tx->vin[i] + .prevout.GetTxId() + .ToString() + .c_str(), + psbt.tx->vin[i].prevout.GetN())); + } + } + for (size_t i = 0; i < psbt.tx->vout.size(); ++i) { + merged_psbt.AddOutput(psbt.tx->vout[i], psbt.outputs[i]); + } + merged_psbt.unknown.insert(psbt.unknown.begin(), + psbt.unknown.end()); + } + + // Generate list of shuffled indices for shuffling inputs and + // outputs of the merged PSBT + std::vector input_indices(merged_psbt.inputs.size()); + std::iota(input_indices.begin(), input_indices.end(), 0); + std::vector 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(MakeUCharSpan(ssTx)); + }, + }; } -UniValue analyzepsbt(const Config &config, const JSONRPCRequest &request) { - RPCHelpMan{ +RPCHelpMan analyzepsbt() { + return RPCHelpMan{ "analyzepsbt", "Analyzes and provides information about the current status of a " "PSBT and its inputs\n", @@ -2086,72 +2138,75 @@ {RPCResult::Type::STR, "error", /* optional */ true, "Error message (if there is one)"}, }}, - RPCExamples{HelpExampleCli("analyzepsbt", "\"psbt\"")}} - .Check(request); - - RPCTypeCheck(request.params, {UniValue::VSTR}); - - // Unserialize the transaction - PartiallySignedTransaction psbtx; - std::string error; - if (!DecodeBase64PSBT(psbtx, request.params[0].get_str(), error)) { - throw JSONRPCError(RPC_DESERIALIZATION_ERROR, - strprintf("TX decode failed %s", error)); - } - - PSBTAnalysis psbta = AnalyzePSBT(psbtx); - - UniValue result(UniValue::VOBJ); - UniValue inputs_result(UniValue::VARR); - for (const auto &input : psbta.inputs) { - UniValue input_univ(UniValue::VOBJ); - UniValue missing(UniValue::VOBJ); - - input_univ.pushKV("has_utxo", input.has_utxo); - input_univ.pushKV("is_final", input.is_final); - input_univ.pushKV("next", PSBTRoleName(input.next)); + RPCExamples{HelpExampleCli("analyzepsbt", "\"psbt\"")}, + [&](const RPCHelpMan &self, const Config &config, + const JSONRPCRequest &request) -> UniValue { + 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)); + } - 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)); + PSBTAnalysis psbta = AnalyzePSBT(psbtx); + + UniValue result(UniValue::VOBJ); + UniValue inputs_result(UniValue::VARR); + for (const auto &input : psbta.inputs) { + UniValue input_univ(UniValue::VOBJ); + UniValue missing(UniValue::VOBJ); + + input_univ.pushKV("has_utxo", input.has_utxo); + input_univ.pushKV("is_final", input.is_final); + input_univ.pushKV("next", PSBTRoleName(input.next)); + + if (!input.missing_pubkeys.empty()) { + UniValue missing_pubkeys_univ(UniValue::VARR); + for (const CKeyID &pubkey : input.missing_pubkeys) { + missing_pubkeys_univ.push_back(HexStr(pubkey)); + } + missing.pushKV("pubkeys", missing_pubkeys_univ); + } + if (!input.missing_redeem_script.IsNull()) { + missing.pushKV("redeemscript", + HexStr(input.missing_redeem_script)); + } + if (!input.missing_sigs.empty()) { + UniValue missing_sigs_univ(UniValue::VARR); + for (const CKeyID &pubkey : input.missing_sigs) { + missing_sigs_univ.push_back(HexStr(pubkey)); + } + missing.pushKV("signatures", missing_sigs_univ); + } + if (!missing.getKeys().empty()) { + input_univ.pushKV("missing", missing); + } + inputs_result.push_back(input_univ); } - missing.pushKV("pubkeys", missing_pubkeys_univ); - } - if (!input.missing_redeem_script.IsNull()) { - missing.pushKV("redeemscript", HexStr(input.missing_redeem_script)); - } - if (!input.missing_sigs.empty()) { - UniValue missing_sigs_univ(UniValue::VARR); - for (const CKeyID &pubkey : input.missing_sigs) { - missing_sigs_univ.push_back(HexStr(pubkey)); + 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", + psbta.estimated_feerate->GetFeePerK()); + } + if (psbta.fee != std::nullopt) { + result.pushKV("fee", *psbta.fee); + } + result.pushKV("next", PSBTRoleName(psbta.next)); + if (!psbta.error.empty()) { + result.pushKV("error", psbta.error); } - 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", - psbta.estimated_feerate->GetFeePerK()); - } - if (psbta.fee != std::nullopt) { - result.pushKV("fee", *psbta.fee); - } - result.pushKV("next", PSBTRoleName(psbta.next)); - if (!psbta.error.empty()) { - result.pushKV("error", psbta.error); - } - return result; + return result; + }, + }; } void RegisterRawTransactionRPCCommands(CRPCTable &t) {