Skip to content

Commit e439aeb

Browse files
committed
Merge #15508: Refactor analyzepsbt for use outside RPC code
892eff0 Add documentation of struct PSBTAnalysis et al (Glenn Willen) ef22fe8 Refactor analyzepsbt for use outside RPC code (Glenn Willen) afd20a2 Move PSBT decoding functions from core_io to psbt.cpp (Glenn Willen) Pull request description: 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. ---- As with my previous refactoring PR, I need this because I am creating a dependency on this code from the GUI. Per discussion in #bitcoin-core-dev on IRC, since we don't want to create a dependency on UniValue in anything outside RPC, I introduced some new structs to hold the info we get when analyzing a PSBT. For the field types, I used whatever types are already used internally for this data (e.g. CAmount, CFeeRate, CKeyID), and only convert to int/string etc. in the wrapper. @achow101, maybe take the first look? :-) ACKs for commit 892eff: sipa: utACK 892eff0 achow101: utACK 892eff0 ryanofsky: utACK 892eff0. Just small cleanups since the last review: removing unneeded include, forward decl, adding const ref Tree-SHA512: eb278b0a82717ebc3eb0c08dc5bb4eefb996a317a6a3a8ecf51cd88110ddbb188ad3482cdd9563e557995e73aca5a282c1f6e352bc598155f1203b7b46fe5dee
2 parents 35991b1 + 892eff0 commit e439aeb

File tree

6 files changed

+252
-165
lines changed

6 files changed

+252
-165
lines changed

src/Makefile.am

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -434,8 +434,8 @@ libbitcoin_common_a_SOURCES = \
434434
netaddress.cpp \
435435
netbase.cpp \
436436
policy/feerate.cpp \
437-
psbt.cpp \
438437
protocol.cpp \
438+
psbt.cpp \
439439
scheduler.cpp \
440440
script/descriptor.cpp \
441441
script/ismine.cpp \

src/core_io.h

Lines changed: 0 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,6 @@ class CBlockHeader;
1616
class CScript;
1717
class CTransaction;
1818
struct CMutableTransaction;
19-
struct PartiallySignedTransaction;
2019
class uint256;
2120
class UniValue;
2221

@@ -37,11 +36,6 @@ bool DecodeHexBlockHeader(CBlockHeader&, const std::string& hex_header);
3736
*/
3837
bool ParseHashStr(const std::string& strHex, uint256& result);
3938
std::vector<unsigned char> ParseHexUV(const UniValue& v, const std::string& strName);
40-
41-
//! Decode a base64ed PSBT into a PartiallySignedTransaction
42-
NODISCARD bool DecodeBase64PSBT(PartiallySignedTransaction& decoded_psbt, const std::string& base64_psbt, std::string& error);
43-
//! Decode a raw (binary blob) PSBT into a PartiallySignedTransaction
44-
NODISCARD bool DecodeRawPSBT(PartiallySignedTransaction& decoded_psbt, const std::string& raw_psbt, std::string& error);
4539
int ParseSighashString(const UniValue& sighash);
4640

4741
// core_write.cpp

src/core_read.cpp

Lines changed: 0 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,6 @@
44

55
#include <core_io.h>
66

7-
#include <psbt.h>
87
#include <primitives/block.h>
98
#include <primitives/transaction.h>
109
#include <script/script.h>
@@ -177,33 +176,6 @@ bool DecodeHexBlk(CBlock& block, const std::string& strHexBlk)
177176
return true;
178177
}
179178

180-
bool DecodeBase64PSBT(PartiallySignedTransaction& psbt, const std::string& base64_tx, std::string& error)
181-
{
182-
bool invalid;
183-
std::string tx_data = DecodeBase64(base64_tx, &invalid);
184-
if (invalid) {
185-
error = "invalid base64";
186-
return false;
187-
}
188-
return DecodeRawPSBT(psbt, tx_data, error);
189-
}
190-
191-
bool DecodeRawPSBT(PartiallySignedTransaction& psbt, const std::string& tx_data, std::string& error)
192-
{
193-
CDataStream ss_data(tx_data.data(), tx_data.data() + tx_data.size(), SER_NETWORK, PROTOCOL_VERSION);
194-
try {
195-
ss_data >> psbt;
196-
if (!ss_data.empty()) {
197-
error = "extra data after PSBT";
198-
return false;
199-
}
200-
} catch (const std::exception& e) {
201-
error = e.what();
202-
return false;
203-
}
204-
return true;
205-
}
206-
207179
bool ParseHashStr(const std::string& strHex, uint256& result)
208180
{
209181
if ((strHex.size() != 64) || !IsHex(strHex))

src/psbt.cpp

Lines changed: 165 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
}
@@ -325,3 +330,162 @@ TransactionError CombinePSBTs(PartiallySignedTransaction& out, const std::vector
325330

326331
return TransactionError::OK;
327332
}
333+
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+
466+
bool DecodeBase64PSBT(PartiallySignedTransaction& psbt, const std::string& base64_tx, std::string& error)
467+
{
468+
bool invalid;
469+
std::string tx_data = DecodeBase64(base64_tx, &invalid);
470+
if (invalid) {
471+
error = "invalid base64";
472+
return false;
473+
}
474+
return DecodeRawPSBT(psbt, tx_data, error);
475+
}
476+
477+
bool DecodeRawPSBT(PartiallySignedTransaction& psbt, const std::string& tx_data, std::string& error)
478+
{
479+
CDataStream ss_data(tx_data.data(), tx_data.data() + tx_data.size(), SER_NETWORK, PROTOCOL_VERSION);
480+
try {
481+
ss_data >> psbt;
482+
if (!ss_data.empty()) {
483+
error = "extra data after PSBT";
484+
return false;
485+
}
486+
} catch (const std::exception& e) {
487+
error = e.what();
488+
return false;
489+
}
490+
return true;
491+
}

src/psbt.h

Lines changed: 50 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,42 @@ struct PartiallySignedTransaction
548550
}
549551
};
550552

553+
enum class PSBTRole {
554+
UPDATER,
555+
SIGNER,
556+
FINALIZER,
557+
EXTRACTOR
558+
};
559+
560+
/**
561+
* Holds an analysis of one input from a PSBT
562+
*/
563+
struct PSBTInputAnalysis {
564+
bool has_utxo; //!< Whether we have UTXO information for this input
565+
bool is_final; //!< Whether the input has all required information including signatures
566+
PSBTRole next; //!< Which of the BIP 174 roles needs to handle this input next
567+
568+
std::vector<CKeyID> missing_pubkeys; //!< Pubkeys whose BIP32 derivation path is missing
569+
std::vector<CKeyID> missing_sigs; //!< Pubkeys whose signatures are missing
570+
uint160 missing_redeem_script; //!< Hash160 of redeem script, if missing
571+
uint256 missing_witness_script; //!< SHA256 of witness script, if missing
572+
};
573+
574+
/**
575+
* Holds the results of AnalyzePSBT (miscellaneous information about a PSBT)
576+
*/
577+
struct PSBTAnalysis {
578+
Optional<size_t> estimated_vsize; //!< Estimated weight of the transaction
579+
Optional<CFeeRate> estimated_feerate; //!< Estimated feerate (fee / weight) of the transaction
580+
Optional<CAmount> fee; //!< Amount of fee being paid by the transaction
581+
std::vector<PSBTInputAnalysis> inputs; //!< More information about the individual inputs of the transaction
582+
PSBTRole next; //!< Which of the BIP 174 roles needs to handle the transaction next
583+
};
584+
585+
std::string PSBTRoleName(PSBTRole role);
586+
551587
/** Checks whether a PSBTInput is already signed. */
552-
bool PSBTInputSigned(PSBTInput& input);
588+
bool PSBTInputSigned(const PSBTInput& input);
553589

554590
/** Signs a PSBTInput, verifying that all provided data matches what is being signed. */
555591
bool SignPSBTInput(const SigningProvider& provider, PartiallySignedTransaction& psbt, int index, int sighash = SIGHASH_ALL, SignatureData* out_sigdata = nullptr, bool use_dummy = false);
@@ -580,4 +616,17 @@ bool FinalizeAndExtractPSBT(PartiallySignedTransaction& psbtx, CMutableTransacti
580616
*/
581617
NODISCARD TransactionError CombinePSBTs(PartiallySignedTransaction& out, const std::vector<PartiallySignedTransaction>& psbtxs);
582618

619+
/**
620+
* Provides helpful miscellaneous information about where a PSBT is in the signing workflow.
621+
*
622+
* @param[in] psbtx the PSBT to analyze
623+
* @return A PSBTAnalysis with information about the provided PSBT.
624+
*/
625+
PSBTAnalysis AnalyzePSBT(PartiallySignedTransaction psbtx);
626+
627+
//! Decode a base64ed PSBT into a PartiallySignedTransaction
628+
NODISCARD bool DecodeBase64PSBT(PartiallySignedTransaction& decoded_psbt, const std::string& base64_psbt, std::string& error);
629+
//! Decode a raw (binary blob) PSBT into a PartiallySignedTransaction
630+
NODISCARD bool DecodeRawPSBT(PartiallySignedTransaction& decoded_psbt, const std::string& raw_psbt, std::string& error);
631+
583632
#endif // BITCOIN_PSBT_H

0 commit comments

Comments
 (0)