aboutsummaryrefslogtreecommitdiff
path: root/src/rpc/rawtransaction.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'src/rpc/rawtransaction.cpp')
-rw-r--r--src/rpc/rawtransaction.cpp232
1 files changed, 174 insertions, 58 deletions
diff --git a/src/rpc/rawtransaction.cpp b/src/rpc/rawtransaction.cpp
index 21d49fda9d..eb0200ccf5 100644
--- a/src/rpc/rawtransaction.cpp
+++ b/src/rpc/rawtransaction.cpp
@@ -51,8 +51,6 @@ using node::FindCoins;
using node::GetTransaction;
using node::NodeContext;
using node::PSBTAnalysis;
-using node::ReadBlockFromDisk;
-using node::UndoReadFromDisk;
static void TxToJSON(const CTransaction& tx, const uint256 hashBlock, UniValue& entry,
Chainstate& active_chainstate, const CTxUndo* txundo = nullptr,
@@ -172,6 +170,93 @@ static std::vector<RPCArg> CreateTxDoc()
};
}
+// Update PSBT with information from the mempool, the UTXO set, the txindex, and the provided descriptors.
+// Optionally, sign the inputs that we can using information from the descriptors.
+PartiallySignedTransaction ProcessPSBT(const std::string& psbt_string, const std::any& context, const HidingSigningProvider& provider, int sighash_type, bool finalize)
+{
+ // Unserialize the transactions
+ PartiallySignedTransaction psbtx;
+ std::string error;
+ if (!DecodeBase64PSBT(psbtx, psbt_string, error)) {
+ throw JSONRPCError(RPC_DESERIALIZATION_ERROR, strprintf("TX decode failed %s", error));
+ }
+
+ if (g_txindex) g_txindex->BlockUntilSyncedToCurrentChain();
+ const NodeContext& node = EnsureAnyNodeContext(context);
+
+ // If we can't find the corresponding full transaction for all of our inputs,
+ // this will be used to find just the utxos for the segwit inputs for which
+ // the full transaction isn't found
+ std::map<COutPoint, Coin> coins;
+
+ // Fetch previous transactions:
+ // First, look in the txindex and the mempool
+ for (unsigned int i = 0; i < psbtx.tx->vin.size(); ++i) {
+ PSBTInput& psbt_input = psbtx.inputs.at(i);
+ const CTxIn& tx_in = psbtx.tx->vin.at(i);
+
+ // The `non_witness_utxo` is the whole previous transaction
+ if (psbt_input.non_witness_utxo) continue;
+
+ CTransactionRef tx;
+
+ // Look in the txindex
+ if (g_txindex) {
+ uint256 block_hash;
+ g_txindex->FindTx(tx_in.prevout.hash, block_hash, tx);
+ }
+ // If we still don't have it look in the mempool
+ if (!tx) {
+ tx = node.mempool->get(tx_in.prevout.hash);
+ }
+ if (tx) {
+ psbt_input.non_witness_utxo = tx;
+ } else {
+ coins[tx_in.prevout]; // Create empty map entry keyed by prevout
+ }
+ }
+
+ // If we still haven't found all of the inputs, look for the missing ones in the utxo set
+ if (!coins.empty()) {
+ FindCoins(node, coins);
+ for (unsigned int i = 0; i < psbtx.tx->vin.size(); ++i) {
+ PSBTInput& input = psbtx.inputs.at(i);
+
+ // If there are still missing utxos, add them if they were found in the utxo set
+ if (!input.non_witness_utxo) {
+ const CTxIn& tx_in = psbtx.tx->vin.at(i);
+ const Coin& coin = coins.at(tx_in.prevout);
+ if (!coin.out.IsNull() && IsSegWitOutput(provider, coin.out.scriptPubKey)) {
+ input.witness_utxo = coin.out;
+ }
+ }
+ }
+ }
+
+ const PrecomputedTransactionData& txdata = PrecomputePSBTData(psbtx);
+
+ for (unsigned int i = 0; i < psbtx.tx->vin.size(); ++i) {
+ if (PSBTInputSigned(psbtx.inputs.at(i))) {
+ continue;
+ }
+
+ // Update script/keypath information using descriptor data.
+ // Note that SignPSBTInput does a lot more than just constructing ECDSA signatures.
+ // We only actually care about those if our signing provider doesn't hide private
+ // information, as is the case with `descriptorprocesspsbt`
+ SignPSBTInput(provider, psbtx, /*index=*/i, &txdata, sighash_type, /*out_sigdata=*/nullptr, finalize);
+ }
+
+ // Update script/keypath information using descriptor data.
+ for (unsigned int i = 0; i < psbtx.tx->vout.size(); ++i) {
+ UpdatePSBTOutput(provider, psbtx, i);
+ }
+
+ RemoveUnnecessaryTransactions(psbtx, /*sighash_type=*/1);
+
+ return psbtx;
+}
+
static RPCHelpMan getrawtransaction()
{
return RPCHelpMan{
@@ -277,7 +362,7 @@ static RPCHelpMan getrawtransaction()
}
uint256 hash_block;
- const CTransactionRef tx = GetTransaction(blockindex, node.mempool.get(), hash, chainman.GetConsensus(), hash_block);
+ const CTransactionRef tx = GetTransaction(blockindex, node.mempool.get(), hash, hash_block, chainman.m_blockman);
if (!tx) {
std::string errmsg;
if (blockindex) {
@@ -321,7 +406,7 @@ static RPCHelpMan getrawtransaction()
if (tx->IsCoinBase() ||
!blockindex || is_block_pruned ||
- !(UndoReadFromDisk(blockUndo, blockindex) && ReadBlockFromDisk(block, blockindex, Params().GetConsensus()))) {
+ !(chainman.m_blockman.UndoReadFromDisk(blockUndo, *blockindex) && chainman.m_blockman.ReadBlockFromDisk(block, *blockindex))) {
TxToJSON(*tx, hash_block, result, chainman.ActiveChainstate());
return result;
}
@@ -1580,7 +1665,7 @@ static RPCHelpMan converttopsbt()
static RPCHelpMan utxoupdatepsbt()
{
return RPCHelpMan{"utxoupdatepsbt",
- "\nUpdates all segwit inputs and outputs in a PSBT with data from output descriptors, the UTXO set or the mempool.\n",
+ "\nUpdates all segwit inputs and outputs in a PSBT with data from output descriptors, the UTXO set, txindex, or the mempool.\n",
{
{"psbt", RPCArg::Type::STR, RPCArg::Optional::NO, "A base64 string of a PSBT"},
{"descriptors", RPCArg::Type::ARR, RPCArg::Optional::OMITTED, "An array of either strings or objects", {
@@ -1599,13 +1684,6 @@ static RPCHelpMan utxoupdatepsbt()
},
[&](const RPCHelpMan& self, const JSONRPCRequest& request) -> UniValue
{
- // Unserialize the transactions
- PartiallySignedTransaction psbtx;
- std::string error;
- if (!DecodeBase64PSBT(psbtx, request.params[0].get_str(), error)) {
- throw JSONRPCError(RPC_DESERIALIZATION_ERROR, strprintf("TX decode failed %s", error));
- }
-
// Parse descriptors, if any.
FlatSigningProvider provider;
if (!request.params[1].isNull()) {
@@ -1614,53 +1692,14 @@ static RPCHelpMan utxoupdatepsbt()
EvalDescriptorStringOrObject(descs[i], provider);
}
}
- // We don't actually need private keys further on; hide them as a precaution.
- HidingSigningProvider public_provider(&provider, /*hide_secret=*/true, /*hide_origin=*/false);
-
- // Fetch previous transactions (inputs):
- CCoinsView viewDummy;
- CCoinsViewCache view(&viewDummy);
- {
- NodeContext& node = EnsureAnyNodeContext(request.context);
- const CTxMemPool& mempool = EnsureMemPool(node);
- ChainstateManager& chainman = EnsureChainman(node);
- LOCK2(cs_main, mempool.cs);
- CCoinsViewCache &viewChain = chainman.ActiveChainstate().CoinsTip();
- CCoinsViewMemPool viewMempool(&viewChain, mempool);
- view.SetBackend(viewMempool); // temporarily switch cache backend to db+mempool view
-
- for (const CTxIn& txin : psbtx.tx->vin) {
- view.AccessCoin(txin.prevout); // Load entries from viewChain into view; can fail.
- }
-
- view.SetBackend(viewDummy); // switch back to avoid locking mempool for too long
- }
-
- // Fill the inputs
- const PrecomputedTransactionData txdata = PrecomputePSBTData(psbtx);
- for (unsigned int i = 0; i < psbtx.tx->vin.size(); ++i) {
- PSBTInput& input = psbtx.inputs.at(i);
-
- if (input.non_witness_utxo || !input.witness_utxo.IsNull()) {
- continue;
- }
-
- const Coin& coin = view.AccessCoin(psbtx.tx->vin[i].prevout);
-
- if (IsSegWitOutput(provider, coin.out.scriptPubKey)) {
- input.witness_utxo = coin.out;
- }
-
- // 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, &txdata, /*sighash=*/1);
- }
- // Update script/keypath information using descriptor data.
- for (unsigned int i = 0; i < psbtx.tx->vout.size(); ++i) {
- UpdatePSBTOutput(public_provider, psbtx, i);
- }
+ // We don't actually need private keys further on; hide them as a precaution.
+ const PartiallySignedTransaction& psbtx = ProcessPSBT(
+ request.params[0].get_str(),
+ request.context,
+ HidingSigningProvider(&provider, /*hide_secret=*/true, /*hide_origin=*/false),
+ /*sighash_type=*/SIGHASH_ALL,
+ /*finalize=*/false);
CDataStream ssTx(SER_NETWORK, PROTOCOL_VERSION);
ssTx << psbtx;
@@ -1879,6 +1918,82 @@ static RPCHelpMan analyzepsbt()
};
}
+RPCHelpMan descriptorprocesspsbt()
+{
+ return RPCHelpMan{"descriptorprocesspsbt",
+ "\nUpdate all segwit inputs in a PSBT with information from output descriptors, the UTXO set or the mempool. \n"
+ "Then, sign the inputs we are able to with information from the output descriptors. ",
+ {
+ {"psbt", RPCArg::Type::STR, RPCArg::Optional::NO, "The transaction base64 string"},
+ {"descriptors", RPCArg::Type::ARR, RPCArg::Optional::NO, "An array of either strings or objects", {
+ {"", RPCArg::Type::STR, RPCArg::Optional::OMITTED, "An output descriptor"},
+ {"", RPCArg::Type::OBJ, RPCArg::Optional::OMITTED, "An object with an output descriptor and extra information", {
+ {"desc", RPCArg::Type::STR, RPCArg::Optional::NO, "An output descriptor"},
+ {"range", RPCArg::Type::RANGE, RPCArg::Default{1000}, "Up to what index HD chains should be explored (either end or [begin,end])"},
+ }},
+ }},
+ {"sighashtype", RPCArg::Type::STR, RPCArg::Default{"DEFAULT for Taproot, ALL otherwise"}, "The signature hash type to sign with if not specified by the PSBT. Must be one of\n"
+ " \"DEFAULT\"\n"
+ " \"ALL\"\n"
+ " \"NONE\"\n"
+ " \"SINGLE\"\n"
+ " \"ALL|ANYONECANPAY\"\n"
+ " \"NONE|ANYONECANPAY\"\n"
+ " \"SINGLE|ANYONECANPAY\""},
+ {"bip32derivs", RPCArg::Type::BOOL, RPCArg::Default{true}, "Include BIP 32 derivation paths for public keys if we know them"},
+ {"finalize", RPCArg::Type::BOOL, RPCArg::Default{true}, "Also finalize inputs if possible"},
+ },
+ RPCResult{
+ RPCResult::Type::OBJ, "", "",
+ {
+ {RPCResult::Type::STR, "psbt", "The base64-encoded partially signed transaction"},
+ {RPCResult::Type::BOOL, "complete", "If the transaction has a complete set of signatures"},
+ }
+ },
+ RPCExamples{
+ HelpExampleCli("descriptorprocesspsbt", "\"psbt\" \"[\\\"descriptor1\\\", \\\"descriptor2\\\"]\"") +
+ HelpExampleCli("descriptorprocesspsbt", "\"psbt\" \"[{\\\"desc\\\":\\\"mydescriptor\\\", \\\"range\\\":21}]\"")
+ },
+ [&](const RPCHelpMan& self, const JSONRPCRequest& request) -> UniValue
+{
+ // Add descriptor information to a signing provider
+ FlatSigningProvider provider;
+
+ auto descs = request.params[1].get_array();
+ for (size_t i = 0; i < descs.size(); ++i) {
+ EvalDescriptorStringOrObject(descs[i], provider, /*expand_priv=*/true);
+ }
+
+ int sighash_type = ParseSighashString(request.params[2]);
+ bool bip32derivs = request.params[3].isNull() ? true : request.params[3].get_bool();
+ bool finalize = request.params[4].isNull() ? true : request.params[4].get_bool();
+
+ const PartiallySignedTransaction& psbtx = ProcessPSBT(
+ request.params[0].get_str(),
+ request.context,
+ HidingSigningProvider(&provider, /*hide_secret=*/false, !bip32derivs),
+ sighash_type,
+ finalize);
+
+ // Check whether or not all of the inputs are now signed
+ bool complete = true;
+ for (const auto& input : psbtx.inputs) {
+ complete &= PSBTInputSigned(input);
+ }
+
+ CDataStream ssTx(SER_NETWORK, PROTOCOL_VERSION);
+ ssTx << psbtx;
+
+ UniValue result(UniValue::VOBJ);
+
+ result.pushKV("psbt", EncodeBase64(ssTx));
+ result.pushKV("complete", complete);
+
+ return result;
+},
+ };
+}
+
void RegisterRawTransactionRPCCommands(CRPCTable& t)
{
static const CRPCCommand commands[]{
@@ -1894,6 +2009,7 @@ void RegisterRawTransactionRPCCommands(CRPCTable& t)
{"rawtransactions", &createpsbt},
{"rawtransactions", &converttopsbt},
{"rawtransactions", &utxoupdatepsbt},
+ {"rawtransactions", &descriptorprocesspsbt},
{"rawtransactions", &joinpsbts},
{"rawtransactions", &analyzepsbt},
};