diff --git a/contrib/signet/miner b/contrib/signet/miner index f46d88b52e1..1d9f87203c1 100755 --- a/contrib/signet/miner +++ b/contrib/signet/miner @@ -66,8 +66,8 @@ def signet_txs(block, challenge): def decode_challenge_psbt(b64psbt): psbt = PSBT.from_base64(b64psbt) - assert len(psbt.tx.vin) == 1 - assert len(psbt.tx.vout) == 1 + assert len(psbt.i) == 1 + assert len(psbt.o) == 1 assert PSBT_SIGNET_BLOCK in psbt.g.map return psbt diff --git a/doc/release-notes-21283.md b/doc/release-notes-21283.md new file mode 100644 index 00000000000..fb5494a1f73 --- /dev/null +++ b/doc/release-notes-21283.md @@ -0,0 +1,6 @@ +Updated RPCs +------------ + +- `createpsbt`, `walletcreatepsbt`, `converttopsbt`, and `psbtbumpfee` + will now default to creating version 2 PSBTs. An optional `psbt_version` + argument is added to these RPCs which allows specifying the version of PSBT to create. diff --git a/src/common/messages.cpp b/src/common/messages.cpp index 12e32cae806..700f4f03cd6 100644 --- a/src/common/messages.cpp +++ b/src/common/messages.cpp @@ -116,6 +116,8 @@ bilingual_str PSBTErrorString(PSBTError err) return Untranslated("Signer does not support PSBT"); case PSBTError::INCOMPLETE: return Untranslated("Input needs additional signatures or other data"); + case PSBTError::INVALID_TX: + return Untranslated("The transaction cannot be valid"); case PSBTError::OK: return Untranslated("No errors"); } // no default case, so the compiler can warn about missing cases diff --git a/src/common/types.h b/src/common/types.h index c366a847562..b9ebca15e84 100644 --- a/src/common/types.h +++ b/src/common/types.h @@ -23,6 +23,7 @@ enum class PSBTError { EXTERNAL_SIGNER_FAILED, UNSUPPORTED, INCOMPLETE, + INVALID_TX, OK, }; /** diff --git a/src/external_signer.cpp b/src/external_signer.cpp index 3790f4d36f9..2da9502669a 100644 --- a/src/external_signer.cpp +++ b/src/external_signer.cpp @@ -112,14 +112,13 @@ bool ExternalSigner::SignTransaction(PartiallySignedTransaction& psbtx, std::str return false; } - PartiallySignedTransaction signer_psbtx; - std::string signer_psbt_error; - if (!DecodeBase64PSBT(signer_psbtx, signer_result.find_value("psbt").get_str(), signer_psbt_error)) { - error = strprintf("TX decode failed %s", signer_psbt_error); + util::Result signer_psbtx = DecodeBase64PSBT(signer_result.find_value("psbt").get_str()); + if (!signer_psbtx) { + error = strprintf("TX decode failed %s", util::ErrorString(signer_psbtx).original); return false; } - psbtx = signer_psbtx; + psbtx = *signer_psbtx; return true; } diff --git a/src/external_signer.h b/src/external_signer.h index 5ba37c0626b..87fbbf0b8c2 100644 --- a/src/external_signer.h +++ b/src/external_signer.h @@ -11,7 +11,7 @@ #include #include -struct PartiallySignedTransaction; +class PartiallySignedTransaction; //! Enables interaction with an external signing device or service, such as //! a hardware wallet. See doc/external-signer.md diff --git a/src/interfaces/wallet.h b/src/interfaces/wallet.h index ab142080fc6..d21163171d5 100644 --- a/src/interfaces/wallet.h +++ b/src/interfaces/wallet.h @@ -32,7 +32,7 @@ class CFeeRate; class CKey; enum class FeeReason; enum class OutputType; -struct PartiallySignedTransaction; +class PartiallySignedTransaction; struct bilingual_str; namespace common { enum class PSBTError; diff --git a/src/node/psbt.cpp b/src/node/psbt.cpp index 62382e5602b..699d1d5f7ce 100644 --- a/src/node/psbt.cpp +++ b/src/node/psbt.cpp @@ -18,15 +18,23 @@ PSBTAnalysis AnalyzePSBT(PartiallySignedTransaction psbtx) // Go through each input and build status PSBTAnalysis result; + std::optional unsigned_tx = psbtx.GetUnsignedTx(); + if (!unsigned_tx) { + result.SetInvalid("PSBT cannot be made into a valid transaction"); + return result; + } + CMutableTransaction& mtx = *unsigned_tx; + bool calc_fee = true; CAmount in_amt = 0; - result.inputs.resize(psbtx.tx->vin.size()); + result.inputs.resize(psbtx.inputs.size()); - const PrecomputedTransactionData txdata = PrecomputePSBTData(psbtx); + // PrecomputePSBTData calls GetUnsignedTx() which we checked already works + const PrecomputedTransactionData txdata = *PrecomputePSBTData(psbtx); - for (unsigned int i = 0; i < psbtx.tx->vin.size(); ++i) { + for (unsigned int i = 0; i < psbtx.inputs.size(); ++i) { PSBTInput& input = psbtx.inputs[i]; PSBTInputAnalysis& input_analysis = result.inputs[i]; @@ -35,7 +43,7 @@ PSBTAnalysis AnalyzePSBT(PartiallySignedTransaction psbtx) // Check for a UTXO CTxOut utxo; - if (psbtx.GetInputUTXO(utxo, i)) { + if (input.GetUTXO(utxo)) { if (!MoneyRange(utxo.nValue) || !MoneyRange(in_amt + utxo.nValue)) { result.SetInvalid(strprintf("PSBT is not valid. Input %u has invalid value", i)); return result; @@ -43,7 +51,7 @@ PSBTAnalysis AnalyzePSBT(PartiallySignedTransaction psbtx) in_amt += utxo.nValue; input_analysis.has_utxo = true; } else { - if (input.non_witness_utxo && psbtx.tx->vin[i].prevout.n >= input.non_witness_utxo->vout.size()) { + if (input.non_witness_utxo && input.prev_out >= input.non_witness_utxo->vout.size()) { result.SetInvalid(strprintf("PSBT is not valid. Input %u specifies invalid prevout", i)); return result; } @@ -89,7 +97,7 @@ PSBTAnalysis AnalyzePSBT(PartiallySignedTransaction psbtx) // Calculate next role for PSBT by grabbing "minimum" PSBTInput next role result.next = PSBTRole::EXTRACTOR; - for (unsigned int i = 0; i < psbtx.tx->vin.size(); ++i) { + for (unsigned int i = 0; i < psbtx.inputs.size(); ++i) { PSBTInputAnalysis& input_analysis = result.inputs[i]; result.next = std::min(result.next, input_analysis.next); } @@ -97,12 +105,12 @@ PSBTAnalysis AnalyzePSBT(PartiallySignedTransaction psbtx) if (calc_fee) { // Get the output amount - CAmount out_amt = std::accumulate(psbtx.tx->vout.begin(), psbtx.tx->vout.end(), CAmount(0), - [](CAmount a, const CTxOut& b) { - if (!MoneyRange(a) || !MoneyRange(b.nValue) || !MoneyRange(a + b.nValue)) { + CAmount out_amt = std::accumulate(psbtx.outputs.begin(), psbtx.outputs.end(), CAmount(0), + [](CAmount a, const PSBTOutput& b) { + if (!MoneyRange(a) || !MoneyRange(b.amount) || !MoneyRange(a + b.amount)) { return CAmount(-1); } - return a += b.nValue; + return a += b.amount; } ); if (!MoneyRange(out_amt)) { @@ -115,22 +123,21 @@ PSBTAnalysis AnalyzePSBT(PartiallySignedTransaction psbtx) result.fee = fee; // Estimate the size - CMutableTransaction mtx(*psbtx.tx); CCoinsViewCache view{&CoinsViewEmpty::Get()}; bool success = true; - for (unsigned int i = 0; i < psbtx.tx->vin.size(); ++i) { + for (unsigned int i = 0; i < psbtx.inputs.size(); ++i) { PSBTInput& input = psbtx.inputs[i]; Coin newcoin; - if (SignPSBTInput(DUMMY_SIGNING_PROVIDER, psbtx, i, nullptr, /*options=*/{}) != PSBTError::OK || !psbtx.GetInputUTXO(newcoin.out, i)) { + if (SignPSBTInput(DUMMY_SIGNING_PROVIDER, psbtx, i, nullptr, /*options=*/{}) != PSBTError::OK || !input.GetUTXO(newcoin.out)) { success = false; break; } else { mtx.vin[i].scriptSig = input.final_script_sig; mtx.vin[i].scriptWitness = input.final_script_witness; newcoin.nHeight = 1; - view.AddCoin(psbtx.tx->vin[i].prevout, std::move(newcoin), true); + view.AddCoin(input.GetOutPoint(), std::move(newcoin), true); } } diff --git a/src/psbt.cpp b/src/psbt.cpp index cfc720b06d9..f01d915f46d 100644 --- a/src/psbt.cpp +++ b/src/psbt.cpp @@ -7,35 +7,56 @@ #include #include #include +#include #include