Skip to content

Commit ef22fe8

Browse files
committed
Refactor analyzepsbt for use outside RPC code
Refactor the analyzepsbt RPC into (1) an AnalyzePSBT function, which returns its output as a new strongly-typed PSBTAnalysis struct, and (2) a thin wrapper which converts the struct into a UniValue for RPC use.
1 parent afd20a2 commit ef22fe8

File tree

3 files changed

+213
-130
lines changed

3 files changed

+213
-130
lines changed

src/psbt.cpp

Lines changed: 138 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,9 +2,14 @@
22
// Distributed under the MIT software license, see the accompanying
33
// file COPYING or http://www.opensource.org/licenses/mit-license.php.
44

5+
#include <coins.h>
6+
#include <consensus/tx_verify.h>
7+
#include <policy/policy.h>
58
#include <psbt.h>
69
#include <util/strencodings.h>
710

11+
#include <numeric>
12+
813
PartiallySignedTransaction::PartiallySignedTransaction(const CMutableTransaction& tx) : tx(tx)
914
{
1015
inputs.resize(tx.vin.size());
@@ -205,7 +210,7 @@ void PSBTOutput::Merge(const PSBTOutput& output)
205210
if (redeem_script.empty() && !output.redeem_script.empty()) redeem_script = output.redeem_script;
206211
if (witness_script.empty() && !output.witness_script.empty()) witness_script = output.witness_script;
207212
}
208-
bool PSBTInputSigned(PSBTInput& input)
213+
bool PSBTInputSigned(const PSBTInput& input)
209214
{
210215
return !input.final_script_sig.empty() || !input.final_script_witness.IsNull();
211216
}
@@ -326,6 +331,138 @@ TransactionError CombinePSBTs(PartiallySignedTransaction& out, const std::vector
326331
return TransactionError::OK;
327332
}
328333

334+
std::string PSBTRoleName(PSBTRole role) {
335+
switch (role) {
336+
case PSBTRole::UPDATER: return "updater";
337+
case PSBTRole::SIGNER: return "signer";
338+
case PSBTRole::FINALIZER: return "finalizer";
339+
case PSBTRole::EXTRACTOR: return "extractor";
340+
}
341+
}
342+
343+
PSBTAnalysis AnalyzePSBT(PartiallySignedTransaction psbtx)
344+
{
345+
// Go through each input and build status
346+
PSBTAnalysis result;
347+
348+
bool calc_fee = true;
349+
bool all_final = true;
350+
bool only_missing_sigs = true;
351+
bool only_missing_final = false;
352+
CAmount in_amt = 0;
353+
354+
result.inputs.resize(psbtx.tx->vin.size());
355+
356+
for (unsigned int i = 0; i < psbtx.tx->vin.size(); ++i) {
357+
PSBTInput& input = psbtx.inputs[i];
358+
PSBTInputAnalysis& input_analysis = result.inputs[i];
359+
360+
// Check for a UTXO
361+
CTxOut utxo;
362+
if (psbtx.GetInputUTXO(utxo, i)) {
363+
in_amt += utxo.nValue;
364+
input_analysis.has_utxo = true;
365+
} else {
366+
input_analysis.has_utxo = false;
367+
input_analysis.is_final = false;
368+
input_analysis.next = PSBTRole::UPDATER;
369+
calc_fee = false;
370+
}
371+
372+
// Check if it is final
373+
if (!utxo.IsNull() && !PSBTInputSigned(input)) {
374+
input_analysis.is_final = false;
375+
all_final = false;
376+
377+
// Figure out what is missing
378+
SignatureData outdata;
379+
bool complete = SignPSBTInput(DUMMY_SIGNING_PROVIDER, psbtx, i, 1, &outdata);
380+
381+
// Things are missing
382+
if (!complete) {
383+
input_analysis.missing_pubkeys = outdata.missing_pubkeys;
384+
input_analysis.missing_redeem_script = outdata.missing_redeem_script;
385+
input_analysis.missing_witness_script = outdata.missing_witness_script;
386+
input_analysis.missing_sigs = outdata.missing_sigs;
387+
388+
// If we are only missing signatures and nothing else, then next is signer
389+
if (outdata.missing_pubkeys.empty() && outdata.missing_redeem_script.IsNull() && outdata.missing_witness_script.IsNull() && !outdata.missing_sigs.empty()) {
390+
input_analysis.next = PSBTRole::SIGNER;
391+
} else {
392+
only_missing_sigs = false;
393+
input_analysis.next = PSBTRole::UPDATER;
394+
}
395+
} else {
396+
only_missing_final = true;
397+
input_analysis.next = PSBTRole::FINALIZER;
398+
}
399+
} else if (!utxo.IsNull()){
400+
input_analysis.is_final = true;
401+
}
402+
}
403+
404+
if (all_final) {
405+
only_missing_sigs = false;
406+
result.next = PSBTRole::EXTRACTOR;
407+
}
408+
if (calc_fee) {
409+
// Get the output amount
410+
CAmount out_amt = std::accumulate(psbtx.tx->vout.begin(), psbtx.tx->vout.end(), CAmount(0),
411+
[](CAmount a, const CTxOut& b) {
412+
return a += b.nValue;
413+
}
414+
);
415+
416+
// Get the fee
417+
CAmount fee = in_amt - out_amt;
418+
result.fee = fee;
419+
420+
// Estimate the size
421+
CMutableTransaction mtx(*psbtx.tx);
422+
CCoinsView view_dummy;
423+
CCoinsViewCache view(&view_dummy);
424+
bool success = true;
425+
426+
for (unsigned int i = 0; i < psbtx.tx->vin.size(); ++i) {
427+
PSBTInput& input = psbtx.inputs[i];
428+
Coin newcoin;
429+
430+
if (!SignPSBTInput(DUMMY_SIGNING_PROVIDER, psbtx, i, 1, nullptr, true) || !psbtx.GetInputUTXO(newcoin.out, i)) {
431+
success = false;
432+
break;
433+
} else {
434+
mtx.vin[i].scriptSig = input.final_script_sig;
435+
mtx.vin[i].scriptWitness = input.final_script_witness;
436+
newcoin.nHeight = 1;
437+
view.AddCoin(psbtx.tx->vin[i].prevout, std::move(newcoin), true);
438+
}
439+
}
440+
441+
if (success) {
442+
CTransaction ctx = CTransaction(mtx);
443+
size_t size = GetVirtualTransactionSize(ctx, GetTransactionSigOpCost(ctx, view, STANDARD_SCRIPT_VERIFY_FLAGS));
444+
result.estimated_vsize = size;
445+
// Estimate fee rate
446+
CFeeRate feerate(fee, size);
447+
result.estimated_feerate = feerate;
448+
}
449+
450+
if (only_missing_sigs) {
451+
result.next = PSBTRole::SIGNER;
452+
} else if (only_missing_final) {
453+
result.next = PSBTRole::FINALIZER;
454+
} else if (all_final) {
455+
result.next = PSBTRole::EXTRACTOR;
456+
} else {
457+
result.next = PSBTRole::UPDATER;
458+
}
459+
} else {
460+
result.next = PSBTRole::UPDATER;
461+
}
462+
463+
return result;
464+
}
465+
329466
bool DecodeBase64PSBT(PartiallySignedTransaction& psbt, const std::string& base64_tx, std::string& error)
330467
{
331468
bool invalid;

src/psbt.h

Lines changed: 39 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,8 @@
77

88
#include <attributes.h>
99
#include <node/transaction.h>
10+
#include <optional.h>
11+
#include <policy/feerate.h>
1012
#include <primitives/transaction.h>
1113
#include <pubkey.h>
1214
#include <script/sign.h>
@@ -548,8 +550,36 @@ struct PartiallySignedTransaction
548550
}
549551
};
550552

553+
enum class PSBTRole {
554+
UPDATER,
555+
SIGNER,
556+
FINALIZER,
557+
EXTRACTOR
558+
};
559+
560+
struct PSBTInputAnalysis {
561+
bool has_utxo;
562+
bool is_final;
563+
PSBTRole next;
564+
565+
std::vector<CKeyID> missing_pubkeys;
566+
std::vector<CKeyID> missing_sigs;
567+
uint160 missing_redeem_script;
568+
uint256 missing_witness_script;
569+
};
570+
571+
struct PSBTAnalysis {
572+
Optional<size_t> estimated_vsize;
573+
Optional<CFeeRate> estimated_feerate;
574+
Optional<CAmount> fee;
575+
std::vector<PSBTInputAnalysis> inputs;
576+
PSBTRole next;
577+
};
578+
579+
std::string PSBTRoleName(PSBTRole role);
580+
551581
/** Checks whether a PSBTInput is already signed. */
552-
bool PSBTInputSigned(PSBTInput& input);
582+
bool PSBTInputSigned(const PSBTInput& input);
553583

554584
/** Signs a PSBTInput, verifying that all provided data matches what is being signed. */
555585
bool SignPSBTInput(const SigningProvider& provider, PartiallySignedTransaction& psbt, int index, int sighash = SIGHASH_ALL, SignatureData* out_sigdata = nullptr, bool use_dummy = false);
@@ -580,6 +610,14 @@ bool FinalizeAndExtractPSBT(PartiallySignedTransaction& psbtx, CMutableTransacti
580610
*/
581611
NODISCARD TransactionError CombinePSBTs(PartiallySignedTransaction& out, const std::vector<PartiallySignedTransaction>& psbtxs);
582612

613+
/**
614+
* Provides helpful miscellaneous information about where a PSBT is in the signing workflow.
615+
*
616+
* @param[in] psbtx the PSBT to analyze
617+
* @return A PSBTAnalysis with information about the provided PSBT.
618+
*/
619+
PSBTAnalysis AnalyzePSBT(PartiallySignedTransaction psbtx);
620+
583621
//! Decode a base64ed PSBT into a PartiallySignedTransaction
584622
NODISCARD bool DecodeBase64PSBT(PartiallySignedTransaction& decoded_psbt, const std::string& base64_psbt, std::string& error);
585623
//! Decode a raw (binary blob) PSBT into a PartiallySignedTransaction

0 commit comments

Comments
 (0)