common: Add PSBTError enum

Add separate PSBTError enum instead of reusing TransactionError enum for PSBT
operations, and drop unused error codes. The error codes returned by PSBT
operations and transaction broadcast functions mostly do not overlap, so using
an unified enum makes it harder to call any of these functions and know which
errors actually need to be handled.

Define PSBTError in the common library because PSBT functionality is
implemented in the common library and used by both the node (for rawtransaction
RPCs) and the wallet.
This commit is contained in:
Ryan Ofsky 2023-12-13 11:43:16 -05:00
parent 0d44c44ae3
commit 02e62c6c9a
25 changed files with 156 additions and 97 deletions

View file

@ -137,6 +137,7 @@ BITCOIN_CORE_H = \
common/bloom.h \ common/bloom.h \
common/init.h \ common/init.h \
common/run_command.h \ common/run_command.h \
common/types.h \
common/url.h \ common/url.h \
compat/assumptions.h \ compat/assumptions.h \
compat/byteswap.h \ compat/byteswap.h \

26
src/common/types.h Normal file
View file

@ -0,0 +1,26 @@
// Copyright (c) 2010-2021 The Bitcoin Core developers
// Distributed under the MIT software license, see the accompanying
// file COPYING or http://www.opensource.org/licenses/mit-license.php.
//! @file common/types.h is a home for simple enum and struct type definitions
//! that can be used internally by functions in the libbitcoin_common library,
//! but also used externally by node, wallet, and GUI code.
//!
//! This file is intended to define only simple types that do not have external
//! dependencies. More complicated types should be defined in dedicated header
//! files.
#ifndef BITCOIN_COMMON_TYPES_H
#define BITCOIN_COMMON_TYPES_H
namespace common {
enum class PSBTError {
MISSING_INPUTS,
SIGHASH_MISMATCH,
EXTERNAL_SIGNER_NOT_FOUND,
EXTERNAL_SIGNER_FAILED,
UNSUPPORTED,
};
} // namespace common
#endif // BITCOIN_COMMON_TYPES_H

View file

@ -30,9 +30,11 @@ class CFeeRate;
class CKey; class CKey;
enum class FeeReason; enum class FeeReason;
enum class OutputType; enum class OutputType;
enum class TransactionError;
struct PartiallySignedTransaction; struct PartiallySignedTransaction;
struct bilingual_str; struct bilingual_str;
namespace common {
enum class PSBTError;
} // namespace common
namespace wallet { namespace wallet {
class CCoinControl; class CCoinControl;
class CWallet; class CWallet;
@ -202,7 +204,7 @@ public:
int& num_blocks) = 0; int& num_blocks) = 0;
//! Fill PSBT. //! Fill PSBT.
virtual TransactionError fillPSBT(int sighash_type, virtual std::optional<common::PSBTError> fillPSBT(int sighash_type,
bool sign, bool sign,
bool bip32derivs, bool bip32derivs,
size_t* n_signed, size_t* n_signed,

View file

@ -17,16 +17,10 @@ enum class TransactionError {
OK, //!< No error OK, //!< No error
MISSING_INPUTS, MISSING_INPUTS,
ALREADY_IN_CHAIN, ALREADY_IN_CHAIN,
P2P_DISABLED,
MEMPOOL_REJECTED, MEMPOOL_REJECTED,
MEMPOOL_ERROR, MEMPOOL_ERROR,
INVALID_PSBT,
PSBT_MISMATCH,
SIGHASH_MISMATCH,
MAX_FEE_EXCEEDED, MAX_FEE_EXCEEDED,
MAX_BURN_EXCEEDED, MAX_BURN_EXCEEDED,
EXTERNAL_SIGNER_NOT_FOUND,
EXTERNAL_SIGNER_FAILED,
INVALID_PACKAGE, INVALID_PACKAGE,
}; };

View file

@ -508,17 +508,17 @@ bool FinalizeAndExtractPSBT(PartiallySignedTransaction& psbtx, CMutableTransacti
return true; return true;
} }
TransactionError CombinePSBTs(PartiallySignedTransaction& out, const std::vector<PartiallySignedTransaction>& psbtxs) bool CombinePSBTs(PartiallySignedTransaction& out, const std::vector<PartiallySignedTransaction>& psbtxs)
{ {
out = psbtxs[0]; // Copy the first one out = psbtxs[0]; // Copy the first one
// Merge // Merge
for (auto it = std::next(psbtxs.begin()); it != psbtxs.end(); ++it) { for (auto it = std::next(psbtxs.begin()); it != psbtxs.end(); ++it) {
if (!out.Merge(*it)) { if (!out.Merge(*it)) {
return TransactionError::PSBT_MISMATCH; return false;
} }
} }
return TransactionError::OK; return true;
} }
std::string PSBTRoleName(PSBTRole role) { std::string PSBTRoleName(PSBTRole role) {

View file

@ -1263,9 +1263,9 @@ bool FinalizeAndExtractPSBT(PartiallySignedTransaction& psbtx, CMutableTransacti
* *
* @param[out] out the combined PSBT, if successful * @param[out] out the combined PSBT, if successful
* @param[in] psbtxs the PSBTs to combine * @param[in] psbtxs the PSBTs to combine
* @return error (OK if we successfully combined the transactions, other error if they were not compatible) * @return True if we successfully combined the transactions, false if they were not compatible
*/ */
[[nodiscard]] TransactionError CombinePSBTs(PartiallySignedTransaction& out, const std::vector<PartiallySignedTransaction>& psbtxs); [[nodiscard]] bool CombinePSBTs(PartiallySignedTransaction& out, const std::vector<PartiallySignedTransaction>& psbtxs);
//! Decode a base64ed PSBT into a PartiallySignedTransaction //! Decode a base64ed PSBT into a PartiallySignedTransaction
[[nodiscard]] bool DecodeBase64PSBT(PartiallySignedTransaction& decoded_psbt, const std::string& base64_psbt, std::string& error); [[nodiscard]] bool DecodeBase64PSBT(PartiallySignedTransaction& decoded_psbt, const std::string& base64_psbt, std::string& error);

View file

@ -13,6 +13,7 @@
#include <qt/forms/ui_psbtoperationsdialog.h> #include <qt/forms/ui_psbtoperationsdialog.h>
#include <qt/guiutil.h> #include <qt/guiutil.h>
#include <qt/optionsmodel.h> #include <qt/optionsmodel.h>
#include <util/error.h>
#include <util/fs.h> #include <util/fs.h>
#include <util/strencodings.h> #include <util/strencodings.h>
@ -55,10 +56,10 @@ void PSBTOperationsDialog::openWithPSBT(PartiallySignedTransaction psbtx)
bool complete = FinalizePSBT(psbtx); // Make sure all existing signatures are fully combined before checking for completeness. bool complete = FinalizePSBT(psbtx); // Make sure all existing signatures are fully combined before checking for completeness.
if (m_wallet_model) { if (m_wallet_model) {
size_t n_could_sign; size_t n_could_sign;
TransactionError err = m_wallet_model->wallet().fillPSBT(SIGHASH_ALL, /*sign=*/false, /*bip32derivs=*/true, &n_could_sign, m_transaction_data, complete); const auto err{m_wallet_model->wallet().fillPSBT(SIGHASH_ALL, /*sign=*/false, /*bip32derivs=*/true, &n_could_sign, m_transaction_data, complete)};
if (err != TransactionError::OK) { if (err) {
showStatus(tr("Failed to load transaction: %1") showStatus(tr("Failed to load transaction: %1")
.arg(QString::fromStdString(TransactionErrorString(err).translated)), .arg(QString::fromStdString(PSBTErrorString(*err).translated)),
StatusLevel::ERR); StatusLevel::ERR);
return; return;
} }
@ -79,11 +80,11 @@ void PSBTOperationsDialog::signTransaction()
WalletModel::UnlockContext ctx(m_wallet_model->requestUnlock()); WalletModel::UnlockContext ctx(m_wallet_model->requestUnlock());
TransactionError err = m_wallet_model->wallet().fillPSBT(SIGHASH_ALL, /*sign=*/true, /*bip32derivs=*/true, &n_signed, m_transaction_data, complete); const auto err{m_wallet_model->wallet().fillPSBT(SIGHASH_ALL, /*sign=*/true, /*bip32derivs=*/true, &n_signed, m_transaction_data, complete)};
if (err != TransactionError::OK) { if (err) {
showStatus(tr("Failed to sign transaction: %1") showStatus(tr("Failed to sign transaction: %1")
.arg(QString::fromStdString(TransactionErrorString(err).translated)), StatusLevel::ERR); .arg(QString::fromStdString(PSBTErrorString(*err).translated)), StatusLevel::ERR);
return; return;
} }
@ -247,9 +248,9 @@ size_t PSBTOperationsDialog::couldSignInputs(const PartiallySignedTransaction &p
size_t n_signed; size_t n_signed;
bool complete; bool complete;
TransactionError err = m_wallet_model->wallet().fillPSBT(SIGHASH_ALL, /*sign=*/false, /*bip32derivs=*/false, &n_signed, m_transaction_data, complete); const auto err{m_wallet_model->wallet().fillPSBT(SIGHASH_ALL, /*sign=*/false, /*bip32derivs=*/false, &n_signed, m_transaction_data, complete)};
if (err != TransactionError::OK) { if (err) {
return 0; return 0;
} }
return n_signed; return n_signed;

View file

@ -37,6 +37,7 @@
#include <QSettings> #include <QSettings>
#include <QTextDocument> #include <QTextDocument>
using common::PSBTError;
using wallet::CCoinControl; using wallet::CCoinControl;
using wallet::DEFAULT_PAY_TX_FEE; using wallet::DEFAULT_PAY_TX_FEE;
@ -442,26 +443,26 @@ void SendCoinsDialog::presentPSBT(PartiallySignedTransaction& psbtx)
} }
bool SendCoinsDialog::signWithExternalSigner(PartiallySignedTransaction& psbtx, CMutableTransaction& mtx, bool& complete) { bool SendCoinsDialog::signWithExternalSigner(PartiallySignedTransaction& psbtx, CMutableTransaction& mtx, bool& complete) {
TransactionError err; std::optional<PSBTError> err;
try { try {
err = model->wallet().fillPSBT(SIGHASH_ALL, /*sign=*/true, /*bip32derivs=*/true, /*n_signed=*/nullptr, psbtx, complete); err = model->wallet().fillPSBT(SIGHASH_ALL, /*sign=*/true, /*bip32derivs=*/true, /*n_signed=*/nullptr, psbtx, complete);
} catch (const std::runtime_error& e) { } catch (const std::runtime_error& e) {
QMessageBox::critical(nullptr, tr("Sign failed"), e.what()); QMessageBox::critical(nullptr, tr("Sign failed"), e.what());
return false; return false;
} }
if (err == TransactionError::EXTERNAL_SIGNER_NOT_FOUND) { if (err == PSBTError::EXTERNAL_SIGNER_NOT_FOUND) {
//: "External signer" means using devices such as hardware wallets. //: "External signer" means using devices such as hardware wallets.
const QString msg = tr("External signer not found"); const QString msg = tr("External signer not found");
QMessageBox::critical(nullptr, msg, msg); QMessageBox::critical(nullptr, msg, msg);
return false; return false;
} }
if (err == TransactionError::EXTERNAL_SIGNER_FAILED) { if (err == PSBTError::EXTERNAL_SIGNER_FAILED) {
//: "External signer" means using devices such as hardware wallets. //: "External signer" means using devices such as hardware wallets.
const QString msg = tr("External signer failure"); const QString msg = tr("External signer failure");
QMessageBox::critical(nullptr, msg, msg); QMessageBox::critical(nullptr, msg, msg);
return false; return false;
} }
if (err != TransactionError::OK) { if (err) {
tfm::format(std::cerr, "Failed to sign PSBT"); tfm::format(std::cerr, "Failed to sign PSBT");
processSendCoinsReturn(WalletModel::TransactionCreationFailed); processSendCoinsReturn(WalletModel::TransactionCreationFailed);
return false; return false;
@ -501,9 +502,9 @@ void SendCoinsDialog::sendButtonClicked([[maybe_unused]] bool checked)
PartiallySignedTransaction psbtx(mtx); PartiallySignedTransaction psbtx(mtx);
bool complete = false; bool complete = false;
// Fill without signing // Fill without signing
TransactionError err = model->wallet().fillPSBT(SIGHASH_ALL, /*sign=*/false, /*bip32derivs=*/true, /*n_signed=*/nullptr, psbtx, complete); const auto err{model->wallet().fillPSBT(SIGHASH_ALL, /*sign=*/false, /*bip32derivs=*/true, /*n_signed=*/nullptr, psbtx, complete)};
assert(!complete); assert(!complete);
assert(err == TransactionError::OK); assert(!err);
// Copy PSBT to clipboard and offer to save // Copy PSBT to clipboard and offer to save
presentPSBT(psbtx); presentPSBT(psbtx);
@ -517,9 +518,9 @@ void SendCoinsDialog::sendButtonClicked([[maybe_unused]] bool checked)
bool complete = false; bool complete = false;
// Always fill without signing first. This prevents an external signer // Always fill without signing first. This prevents an external signer
// from being called prematurely and is not expensive. // from being called prematurely and is not expensive.
TransactionError err = model->wallet().fillPSBT(SIGHASH_ALL, /*sign=*/false, /*bip32derivs=*/true, /*n_signed=*/nullptr, psbtx, complete); const auto err{model->wallet().fillPSBT(SIGHASH_ALL, /*sign=*/false, /*bip32derivs=*/true, /*n_signed=*/nullptr, psbtx, complete)};
assert(!complete); assert(!complete);
assert(err == TransactionError::OK); assert(!err);
send_failure = !signWithExternalSigner(psbtx, mtx, complete); send_failure = !signWithExternalSigner(psbtx, mtx, complete);
// Don't broadcast when user rejects it on the device or there's a failure: // Don't broadcast when user rejects it on the device or there's a failure:
broadcast = complete && !send_failure; broadcast = complete && !send_failure;

View file

@ -534,8 +534,8 @@ bool WalletModel::bumpFee(uint256 hash, uint256& new_hash)
// "Create Unsigned" clicked // "Create Unsigned" clicked
PartiallySignedTransaction psbtx(mtx); PartiallySignedTransaction psbtx(mtx);
bool complete = false; bool complete = false;
const TransactionError err = wallet().fillPSBT(SIGHASH_ALL, /*sign=*/false, /*bip32derivs=*/true, nullptr, psbtx, complete); const auto err{wallet().fillPSBT(SIGHASH_ALL, /*sign=*/false, /*bip32derivs=*/true, nullptr, psbtx, complete)};
if (err != TransactionError::OK || complete) { if (err || complete) {
QMessageBox::critical(nullptr, tr("Fee bump error"), tr("Can't draft transaction.")); QMessageBox::critical(nullptr, tr("Fee bump error"), tr("Can't draft transaction."));
return false; return false;
} }

View file

@ -1485,9 +1485,8 @@ static RPCHelpMan combinepsbt()
} }
PartiallySignedTransaction merged_psbt; PartiallySignedTransaction merged_psbt;
const TransactionError error = CombinePSBTs(merged_psbt, psbtxs); if (!CombinePSBTs(merged_psbt, psbtxs)) {
if (error != TransactionError::OK) { throw JSONRPCError(RPC_INVALID_PARAMETER, "PSBTs not compatible (different transactions)");
throw JSONRPCTransactionError(error);
} }
DataStream ssTx{}; DataStream ssTx{};

View file

@ -7,6 +7,7 @@
#include <clientversion.h> #include <clientversion.h>
#include <core_io.h> #include <core_io.h>
#include <common/args.h> #include <common/args.h>
#include <common/types.h>
#include <consensus/amount.h> #include <consensus/amount.h>
#include <script/interpreter.h> #include <script/interpreter.h>
#include <key_io.h> #include <key_io.h>
@ -30,6 +31,8 @@
#include <tuple> #include <tuple>
#include <utility> #include <utility>
using common::PSBTError;
const std::string UNIX_EPOCH_TIME = "UNIX epoch time"; const std::string UNIX_EPOCH_TIME = "UNIX epoch time";
const std::string EXAMPLE_ADDRESS[2] = {"bc1q09vm5lfy0j5reeulh4x5752q25uqqvz34hufdl", "bc1q02ad21edsxd23d32dfgqqsz4vv4nmtfzuklhy3"}; const std::string EXAMPLE_ADDRESS[2] = {"bc1q09vm5lfy0j5reeulh4x5752q25uqqvz34hufdl", "bc1q02ad21edsxd23d32dfgqqsz4vv4nmtfzuklhy3"};
@ -364,6 +367,18 @@ unsigned int ParseConfirmTarget(const UniValue& value, unsigned int max_target)
return unsigned_target; return unsigned_target;
} }
RPCErrorCode RPCErrorFromPSBTError(PSBTError err)
{
switch (err) {
case PSBTError::UNSUPPORTED:
return RPC_INVALID_PARAMETER;
case PSBTError::SIGHASH_MISMATCH:
return RPC_DESERIALIZATION_ERROR;
default: break;
}
return RPC_TRANSACTION_ERROR;
}
RPCErrorCode RPCErrorFromTransactionError(TransactionError terr) RPCErrorCode RPCErrorFromTransactionError(TransactionError terr)
{ {
switch (terr) { switch (terr) {
@ -371,18 +386,16 @@ RPCErrorCode RPCErrorFromTransactionError(TransactionError terr)
return RPC_TRANSACTION_REJECTED; return RPC_TRANSACTION_REJECTED;
case TransactionError::ALREADY_IN_CHAIN: case TransactionError::ALREADY_IN_CHAIN:
return RPC_TRANSACTION_ALREADY_IN_CHAIN; return RPC_TRANSACTION_ALREADY_IN_CHAIN;
case TransactionError::P2P_DISABLED:
return RPC_CLIENT_P2P_DISABLED;
case TransactionError::INVALID_PSBT:
case TransactionError::PSBT_MISMATCH:
return RPC_INVALID_PARAMETER;
case TransactionError::SIGHASH_MISMATCH:
return RPC_DESERIALIZATION_ERROR;
default: break; default: break;
} }
return RPC_TRANSACTION_ERROR; return RPC_TRANSACTION_ERROR;
} }
UniValue JSONRPCPSBTError(PSBTError err)
{
return JSONRPCError(RPCErrorFromPSBTError(err), PSBTErrorString(err).original);
}
UniValue JSONRPCTransactionError(TransactionError terr, const std::string& err_string) UniValue JSONRPCTransactionError(TransactionError terr, const std::string& err_string)
{ {
if (err_string.length() > 0) { if (err_string.length() > 0) {

View file

@ -37,6 +37,9 @@ enum class OutputType;
enum class TransactionError; enum class TransactionError;
struct FlatSigningProvider; struct FlatSigningProvider;
struct bilingual_str; struct bilingual_str;
namespace common {
enum class PSBTError;
} // namespace common
static constexpr bool DEFAULT_RPC_DOC_CHECK{ static constexpr bool DEFAULT_RPC_DOC_CHECK{
#ifdef RPC_DOC_CHECK #ifdef RPC_DOC_CHECK
@ -128,6 +131,7 @@ int ParseSighashString(const UniValue& sighash);
unsigned int ParseConfirmTarget(const UniValue& value, unsigned int max_target); unsigned int ParseConfirmTarget(const UniValue& value, unsigned int max_target);
RPCErrorCode RPCErrorFromTransactionError(TransactionError terr); RPCErrorCode RPCErrorFromTransactionError(TransactionError terr);
UniValue JSONRPCPSBTError(common::PSBTError err);
UniValue JSONRPCTransactionError(TransactionError terr, const std::string& err_string = ""); UniValue JSONRPCTransactionError(TransactionError terr, const std::string& err_string = "");
//! Parse a JSON range specified as int64, or [int64, int64] //! Parse a JSON range specified as int64, or [int64, int64]

View file

@ -18,15 +18,10 @@
namespace { namespace {
constexpr TransactionError ALL_TRANSACTION_ERROR[] = { constexpr TransactionError ALL_TRANSACTION_ERROR[] = {
TransactionError::OK,
TransactionError::MISSING_INPUTS, TransactionError::MISSING_INPUTS,
TransactionError::ALREADY_IN_CHAIN, TransactionError::ALREADY_IN_CHAIN,
TransactionError::P2P_DISABLED,
TransactionError::MEMPOOL_REJECTED, TransactionError::MEMPOOL_REJECTED,
TransactionError::MEMPOOL_ERROR, TransactionError::MEMPOOL_ERROR,
TransactionError::INVALID_PSBT,
TransactionError::PSBT_MISMATCH,
TransactionError::SIGHASH_MISMATCH,
TransactionError::MAX_FEE_EXCEEDED, TransactionError::MAX_FEE_EXCEEDED,
}; };
}; // namespace }; // namespace

View file

@ -4,12 +4,33 @@
#include <util/error.h> #include <util/error.h>
#include <common/types.h>
#include <tinyformat.h> #include <tinyformat.h>
#include <util/translation.h> #include <util/translation.h>
#include <cassert> #include <cassert>
#include <string> #include <string>
using common::PSBTError;
bilingual_str PSBTErrorString(PSBTError err)
{
switch (err) {
case PSBTError::MISSING_INPUTS:
return Untranslated("Inputs missing or spent");
case PSBTError::SIGHASH_MISMATCH:
return Untranslated("Specified sighash value does not match value stored in PSBT");
case PSBTError::EXTERNAL_SIGNER_NOT_FOUND:
return Untranslated("External signer not found");
case PSBTError::EXTERNAL_SIGNER_FAILED:
return Untranslated("External signer failed to sign");
case PSBTError::UNSUPPORTED:
return Untranslated("Signer does not support PSBT");
// no default case, so the compiler can warn about missing cases
}
assert(false);
}
bilingual_str TransactionErrorString(const TransactionError err) bilingual_str TransactionErrorString(const TransactionError err)
{ {
switch (err) { switch (err) {
@ -19,26 +40,14 @@ bilingual_str TransactionErrorString(const TransactionError err)
return Untranslated("Inputs missing or spent"); return Untranslated("Inputs missing or spent");
case TransactionError::ALREADY_IN_CHAIN: case TransactionError::ALREADY_IN_CHAIN:
return Untranslated("Transaction already in block chain"); return Untranslated("Transaction already in block chain");
case TransactionError::P2P_DISABLED:
return Untranslated("Peer-to-peer functionality missing or disabled");
case TransactionError::MEMPOOL_REJECTED: case TransactionError::MEMPOOL_REJECTED:
return Untranslated("Transaction rejected by mempool"); return Untranslated("Transaction rejected by mempool");
case TransactionError::MEMPOOL_ERROR: case TransactionError::MEMPOOL_ERROR:
return Untranslated("Mempool internal error"); return Untranslated("Mempool internal error");
case TransactionError::INVALID_PSBT:
return Untranslated("PSBT is not well-formed");
case TransactionError::PSBT_MISMATCH:
return Untranslated("PSBTs not compatible (different transactions)");
case TransactionError::SIGHASH_MISMATCH:
return Untranslated("Specified sighash value does not match value stored in PSBT");
case TransactionError::MAX_FEE_EXCEEDED: case TransactionError::MAX_FEE_EXCEEDED:
return Untranslated("Fee exceeds maximum configured by user (e.g. -maxtxfee, maxfeerate)"); return Untranslated("Fee exceeds maximum configured by user (e.g. -maxtxfee, maxfeerate)");
case TransactionError::MAX_BURN_EXCEEDED: case TransactionError::MAX_BURN_EXCEEDED:
return Untranslated("Unspendable output exceeds maximum configured by user (maxburnamount)"); return Untranslated("Unspendable output exceeds maximum configured by user (maxburnamount)");
case TransactionError::EXTERNAL_SIGNER_NOT_FOUND:
return Untranslated("External signer not found");
case TransactionError::EXTERNAL_SIGNER_FAILED:
return Untranslated("External signer failed to sign");
case TransactionError::INVALID_PACKAGE: case TransactionError::INVALID_PACKAGE:
return Untranslated("Transaction rejected due to invalid package"); return Untranslated("Transaction rejected due to invalid package");
// no default case, so the compiler can warn about missing cases // no default case, so the compiler can warn about missing cases

View file

@ -19,6 +19,11 @@
#include <string> #include <string>
struct bilingual_str; struct bilingual_str;
namespace common {
enum class PSBTError;
} // namespace common
bilingual_str PSBTErrorString(common::PSBTError err);
bilingual_str TransactionErrorString(const TransactionError error); bilingual_str TransactionErrorString(const TransactionError error);

View file

@ -17,6 +17,8 @@
#include <utility> #include <utility>
#include <vector> #include <vector>
using common::PSBTError;
namespace wallet { namespace wallet {
bool ExternalSignerScriptPubKeyMan::SetupDescriptor(WalletBatch& batch, std::unique_ptr<Descriptor> desc) bool ExternalSignerScriptPubKeyMan::SetupDescriptor(WalletBatch& batch, std::unique_ptr<Descriptor> desc)
{ {
@ -76,7 +78,7 @@ util::Result<void> ExternalSignerScriptPubKeyMan::DisplayAddress(const CTxDestin
} }
// If sign is true, transaction must previously have been filled // If sign is true, transaction must previously have been filled
TransactionError ExternalSignerScriptPubKeyMan::FillPSBT(PartiallySignedTransaction& psbt, const PrecomputedTransactionData& txdata, int sighash_type, bool sign, bool bip32derivs, int* n_signed, bool finalize) const std::optional<PSBTError> ExternalSignerScriptPubKeyMan::FillPSBT(PartiallySignedTransaction& psbt, const PrecomputedTransactionData& txdata, int sighash_type, bool sign, bool bip32derivs, int* n_signed, bool finalize) const
{ {
if (!sign) { if (!sign) {
return DescriptorScriptPubKeyMan::FillPSBT(psbt, txdata, sighash_type, false, bip32derivs, n_signed, finalize); return DescriptorScriptPubKeyMan::FillPSBT(psbt, txdata, sighash_type, false, bip32derivs, n_signed, finalize);
@ -88,14 +90,14 @@ TransactionError ExternalSignerScriptPubKeyMan::FillPSBT(PartiallySignedTransact
// TODO: for multisig wallets, we should only care if all _our_ inputs are signed // TODO: for multisig wallets, we should only care if all _our_ inputs are signed
complete &= PSBTInputSigned(input); complete &= PSBTInputSigned(input);
} }
if (complete) return TransactionError::OK; if (complete) return {};
std::string strFailReason; std::string strFailReason;
if(!GetExternalSigner().SignTransaction(psbt, strFailReason)) { if(!GetExternalSigner().SignTransaction(psbt, strFailReason)) {
tfm::format(std::cerr, "Failed to sign: %s\n", strFailReason); tfm::format(std::cerr, "Failed to sign: %s\n", strFailReason);
return TransactionError::EXTERNAL_SIGNER_FAILED; return PSBTError::EXTERNAL_SIGNER_FAILED;
} }
if (finalize) FinalizePSBT(psbt); // This won't work in a multisig setup if (finalize) FinalizePSBT(psbt); // This won't work in a multisig setup
return TransactionError::OK; return {};
} }
} // namespace wallet } // namespace wallet

View file

@ -35,7 +35,7 @@ class ExternalSignerScriptPubKeyMan : public DescriptorScriptPubKeyMan
*/ */
util::Result<void> DisplayAddress(const CTxDestination& dest, const ExternalSigner& signer) const; util::Result<void> DisplayAddress(const CTxDestination& dest, const ExternalSigner& signer) const;
TransactionError FillPSBT(PartiallySignedTransaction& psbt, const PrecomputedTransactionData& txdata, int sighash_type = 1 /* SIGHASH_ALL */, bool sign = true, bool bip32derivs = false, int* n_signed = nullptr, bool finalize = true) const override; std::optional<common::PSBTError> FillPSBT(PartiallySignedTransaction& psbt, const PrecomputedTransactionData& txdata, int sighash_type = 1 /* SIGHASH_ALL */, bool sign = true, bool bip32derivs = false, int* n_signed = nullptr, bool finalize = true) const override;
}; };
} // namespace wallet } // namespace wallet
#endif // BITCOIN_WALLET_EXTERNAL_SIGNER_SCRIPTPUBKEYMAN_H #endif // BITCOIN_WALLET_EXTERNAL_SIGNER_SCRIPTPUBKEYMAN_H

View file

@ -343,8 +343,8 @@ bool SignTransaction(CWallet& wallet, CMutableTransaction& mtx) {
// so external signers are not asked to sign more than once. // so external signers are not asked to sign more than once.
bool complete; bool complete;
wallet.FillPSBT(psbtx, complete, SIGHASH_ALL, false /* sign */, true /* bip32derivs */); wallet.FillPSBT(psbtx, complete, SIGHASH_ALL, false /* sign */, true /* bip32derivs */);
const TransactionError err = wallet.FillPSBT(psbtx, complete, SIGHASH_ALL, true /* sign */, false /* bip32derivs */); auto err{wallet.FillPSBT(psbtx, complete, SIGHASH_ALL, true /* sign */, false /* bip32derivs */)};
if (err != TransactionError::OK) return false; if (err) return false;
complete = FinalizeAndExtractPSBT(psbtx, mtx); complete = FinalizeAndExtractPSBT(psbtx, mtx);
return complete; return complete;
} else { } else {

View file

@ -34,6 +34,7 @@
#include <utility> #include <utility>
#include <vector> #include <vector>
using common::PSBTError;
using interfaces::Chain; using interfaces::Chain;
using interfaces::FoundBlock; using interfaces::FoundBlock;
using interfaces::Handler; using interfaces::Handler;
@ -389,7 +390,7 @@ public:
} }
return {}; return {};
} }
TransactionError fillPSBT(int sighash_type, std::optional<PSBTError> fillPSBT(int sighash_type,
bool sign, bool sign,
bool bip32derivs, bool bip32derivs,
size_t* n_signed, size_t* n_signed,

View file

@ -97,9 +97,9 @@ static UniValue FinishTransaction(const std::shared_ptr<CWallet> pwallet, const
// so external signers are not asked to sign more than once. // so external signers are not asked to sign more than once.
bool complete; bool complete;
pwallet->FillPSBT(psbtx, complete, SIGHASH_DEFAULT, /*sign=*/false, /*bip32derivs=*/true); pwallet->FillPSBT(psbtx, complete, SIGHASH_DEFAULT, /*sign=*/false, /*bip32derivs=*/true);
const TransactionError err{pwallet->FillPSBT(psbtx, complete, SIGHASH_DEFAULT, /*sign=*/true, /*bip32derivs=*/false)}; const auto err{pwallet->FillPSBT(psbtx, complete, SIGHASH_DEFAULT, /*sign=*/true, /*bip32derivs=*/false)};
if (err != TransactionError::OK) { if (err) {
throw JSONRPCTransactionError(err); throw JSONRPCPSBTError(*err);
} }
CMutableTransaction mtx; CMutableTransaction mtx;
@ -1153,8 +1153,8 @@ static RPCHelpMan bumpfee_helper(std::string method_name)
} else { } else {
PartiallySignedTransaction psbtx(mtx); PartiallySignedTransaction psbtx(mtx);
bool complete = false; bool complete = false;
const TransactionError err = pwallet->FillPSBT(psbtx, complete, SIGHASH_DEFAULT, /*sign=*/false, /*bip32derivs=*/true); const auto err{pwallet->FillPSBT(psbtx, complete, SIGHASH_DEFAULT, /*sign=*/false, /*bip32derivs=*/true)};
CHECK_NONFATAL(err == TransactionError::OK); CHECK_NONFATAL(!err);
CHECK_NONFATAL(!complete); CHECK_NONFATAL(!complete);
DataStream ssTx{}; DataStream ssTx{};
ssTx << psbtx; ssTx << psbtx;
@ -1602,9 +1602,9 @@ RPCHelpMan walletprocesspsbt()
if (sign) EnsureWalletIsUnlocked(*pwallet); if (sign) EnsureWalletIsUnlocked(*pwallet);
const TransactionError err{wallet.FillPSBT(psbtx, complete, nHashType, sign, bip32derivs, nullptr, finalize)}; const auto err{wallet.FillPSBT(psbtx, complete, nHashType, sign, bip32derivs, nullptr, finalize)};
if (err != TransactionError::OK) { if (err) {
throw JSONRPCTransactionError(err); throw JSONRPCPSBTError(*err);
} }
UniValue result(UniValue::VOBJ); UniValue result(UniValue::VOBJ);
@ -1736,9 +1736,9 @@ RPCHelpMan walletcreatefundedpsbt()
// Fill transaction with out data but don't sign // Fill transaction with out data but don't sign
bool bip32derivs = request.params[4].isNull() ? true : request.params[4].get_bool(); bool bip32derivs = request.params[4].isNull() ? true : request.params[4].get_bool();
bool complete = true; bool complete = true;
const TransactionError err{wallet.FillPSBT(psbtx, complete, 1, /*sign=*/false, /*bip32derivs=*/bip32derivs)}; const auto err{wallet.FillPSBT(psbtx, complete, 1, /*sign=*/false, /*bip32derivs=*/bip32derivs)};
if (err != TransactionError::OK) { if (err) {
throw JSONRPCTransactionError(err); throw JSONRPCPSBTError(*err);
} }
// Serialize the PSBT // Serialize the PSBT

View file

@ -20,6 +20,8 @@
#include <optional> #include <optional>
using common::PSBTError;
namespace wallet { namespace wallet {
//! Value for the first BIP 32 hardened derivation. Can be used as a bit mask and as a value. See BIP 32 for more details. //! Value for the first BIP 32 hardened derivation. Can be used as a bit mask and as a value. See BIP 32 for more details.
const uint32_t BIP32_HARDENED_KEY_LIMIT = 0x80000000; const uint32_t BIP32_HARDENED_KEY_LIMIT = 0x80000000;
@ -627,7 +629,7 @@ SigningResult LegacyScriptPubKeyMan::SignMessage(const std::string& message, con
return SigningResult::SIGNING_FAILED; return SigningResult::SIGNING_FAILED;
} }
TransactionError LegacyScriptPubKeyMan::FillPSBT(PartiallySignedTransaction& psbtx, const PrecomputedTransactionData& txdata, int sighash_type, bool sign, bool bip32derivs, int* n_signed, bool finalize) const std::optional<PSBTError> LegacyScriptPubKeyMan::FillPSBT(PartiallySignedTransaction& psbtx, const PrecomputedTransactionData& txdata, int sighash_type, bool sign, bool bip32derivs, int* n_signed, bool finalize) const
{ {
if (n_signed) { if (n_signed) {
*n_signed = 0; *n_signed = 0;
@ -642,13 +644,13 @@ TransactionError LegacyScriptPubKeyMan::FillPSBT(PartiallySignedTransaction& psb
// Get the Sighash type // Get the Sighash type
if (sign && input.sighash_type != std::nullopt && *input.sighash_type != sighash_type) { if (sign && input.sighash_type != std::nullopt && *input.sighash_type != sighash_type) {
return TransactionError::SIGHASH_MISMATCH; return PSBTError::SIGHASH_MISMATCH;
} }
// Check non_witness_utxo has specified prevout // Check non_witness_utxo has specified prevout
if (input.non_witness_utxo) { if (input.non_witness_utxo) {
if (txin.prevout.n >= input.non_witness_utxo->vout.size()) { if (txin.prevout.n >= input.non_witness_utxo->vout.size()) {
return TransactionError::MISSING_INPUTS; return PSBTError::MISSING_INPUTS;
} }
} else if (input.witness_utxo.IsNull()) { } else if (input.witness_utxo.IsNull()) {
// There's no UTXO so we can just skip this now // There's no UTXO so we can just skip this now
@ -670,7 +672,7 @@ TransactionError LegacyScriptPubKeyMan::FillPSBT(PartiallySignedTransaction& psb
UpdatePSBTOutput(HidingSigningProvider(this, true, !bip32derivs), psbtx, i); UpdatePSBTOutput(HidingSigningProvider(this, true, !bip32derivs), psbtx, i);
} }
return TransactionError::OK; return {};
} }
std::unique_ptr<CKeyMetadata> LegacyScriptPubKeyMan::GetMetadata(const CTxDestination& dest) const std::unique_ptr<CKeyMetadata> LegacyScriptPubKeyMan::GetMetadata(const CTxDestination& dest) const
@ -2485,7 +2487,7 @@ SigningResult DescriptorScriptPubKeyMan::SignMessage(const std::string& message,
return SigningResult::OK; return SigningResult::OK;
} }
TransactionError DescriptorScriptPubKeyMan::FillPSBT(PartiallySignedTransaction& psbtx, const PrecomputedTransactionData& txdata, int sighash_type, bool sign, bool bip32derivs, int* n_signed, bool finalize) const std::optional<PSBTError> DescriptorScriptPubKeyMan::FillPSBT(PartiallySignedTransaction& psbtx, const PrecomputedTransactionData& txdata, int sighash_type, bool sign, bool bip32derivs, int* n_signed, bool finalize) const
{ {
if (n_signed) { if (n_signed) {
*n_signed = 0; *n_signed = 0;
@ -2500,7 +2502,7 @@ TransactionError DescriptorScriptPubKeyMan::FillPSBT(PartiallySignedTransaction&
// Get the Sighash type // Get the Sighash type
if (sign && input.sighash_type != std::nullopt && *input.sighash_type != sighash_type) { if (sign && input.sighash_type != std::nullopt && *input.sighash_type != sighash_type) {
return TransactionError::SIGHASH_MISMATCH; return PSBTError::SIGHASH_MISMATCH;
} }
// Get the scriptPubKey to know which SigningProvider to use // Get the scriptPubKey to know which SigningProvider to use
@ -2509,7 +2511,7 @@ TransactionError DescriptorScriptPubKeyMan::FillPSBT(PartiallySignedTransaction&
script = input.witness_utxo.scriptPubKey; script = input.witness_utxo.scriptPubKey;
} else if (input.non_witness_utxo) { } else if (input.non_witness_utxo) {
if (txin.prevout.n >= input.non_witness_utxo->vout.size()) { if (txin.prevout.n >= input.non_witness_utxo->vout.size()) {
return TransactionError::MISSING_INPUTS; return PSBTError::MISSING_INPUTS;
} }
script = input.non_witness_utxo->vout[txin.prevout.n].scriptPubKey; script = input.non_witness_utxo->vout[txin.prevout.n].scriptPubKey;
} else { } else {
@ -2580,7 +2582,7 @@ TransactionError DescriptorScriptPubKeyMan::FillPSBT(PartiallySignedTransaction&
UpdatePSBTOutput(HidingSigningProvider(keys.get(), /*hide_secret=*/true, /*hide_origin=*/!bip32derivs), psbtx, i); UpdatePSBTOutput(HidingSigningProvider(keys.get(), /*hide_secret=*/true, /*hide_origin=*/!bip32derivs), psbtx, i);
} }
return TransactionError::OK; return {};
} }
std::unique_ptr<CKeyMetadata> DescriptorScriptPubKeyMan::GetMetadata(const CTxDestination& dest) const std::unique_ptr<CKeyMetadata> DescriptorScriptPubKeyMan::GetMetadata(const CTxDestination& dest) const

View file

@ -7,6 +7,7 @@
#include <addresstype.h> #include <addresstype.h>
#include <common/signmessage.h> #include <common/signmessage.h>
#include <common/types.h>
#include <logging.h> #include <logging.h>
#include <psbt.h> #include <psbt.h>
#include <script/descriptor.h> #include <script/descriptor.h>
@ -243,7 +244,7 @@ public:
/** Sign a message with the given script */ /** Sign a message with the given script */
virtual SigningResult SignMessage(const std::string& message, const PKHash& pkhash, std::string& str_sig) const { return SigningResult::SIGNING_FAILED; }; virtual SigningResult SignMessage(const std::string& message, const PKHash& pkhash, std::string& str_sig) const { return SigningResult::SIGNING_FAILED; };
/** Adds script and derivation path information to a PSBT, and optionally signs it. */ /** Adds script and derivation path information to a PSBT, and optionally signs it. */
virtual TransactionError FillPSBT(PartiallySignedTransaction& psbt, const PrecomputedTransactionData& txdata, int sighash_type = SIGHASH_DEFAULT, bool sign = true, bool bip32derivs = false, int* n_signed = nullptr, bool finalize = true) const { return TransactionError::INVALID_PSBT; } virtual std::optional<common::PSBTError> FillPSBT(PartiallySignedTransaction& psbt, const PrecomputedTransactionData& txdata, int sighash_type = SIGHASH_DEFAULT, bool sign = true, bool bip32derivs = false, int* n_signed = nullptr, bool finalize = true) const { return common::PSBTError::UNSUPPORTED; }
virtual uint256 GetID() const { return uint256(); } virtual uint256 GetID() const { return uint256(); }
@ -421,7 +422,7 @@ public:
bool SignTransaction(CMutableTransaction& tx, const std::map<COutPoint, Coin>& coins, int sighash, std::map<int, bilingual_str>& input_errors) const override; bool SignTransaction(CMutableTransaction& tx, const std::map<COutPoint, Coin>& coins, int sighash, std::map<int, bilingual_str>& input_errors) const override;
SigningResult SignMessage(const std::string& message, const PKHash& pkhash, std::string& str_sig) const override; SigningResult SignMessage(const std::string& message, const PKHash& pkhash, std::string& str_sig) const override;
TransactionError FillPSBT(PartiallySignedTransaction& psbt, const PrecomputedTransactionData& txdata, int sighash_type = SIGHASH_DEFAULT, bool sign = true, bool bip32derivs = false, int* n_signed = nullptr, bool finalize = true) const override; std::optional<common::PSBTError> FillPSBT(PartiallySignedTransaction& psbt, const PrecomputedTransactionData& txdata, int sighash_type = SIGHASH_DEFAULT, bool sign = true, bool bip32derivs = false, int* n_signed = nullptr, bool finalize = true) const override;
uint256 GetID() const override; uint256 GetID() const override;
@ -651,7 +652,7 @@ public:
bool SignTransaction(CMutableTransaction& tx, const std::map<COutPoint, Coin>& coins, int sighash, std::map<int, bilingual_str>& input_errors) const override; bool SignTransaction(CMutableTransaction& tx, const std::map<COutPoint, Coin>& coins, int sighash, std::map<int, bilingual_str>& input_errors) const override;
SigningResult SignMessage(const std::string& message, const PKHash& pkhash, std::string& str_sig) const override; SigningResult SignMessage(const std::string& message, const PKHash& pkhash, std::string& str_sig) const override;
TransactionError FillPSBT(PartiallySignedTransaction& psbt, const PrecomputedTransactionData& txdata, int sighash_type = SIGHASH_DEFAULT, bool sign = true, bool bip32derivs = false, int* n_signed = nullptr, bool finalize = true) const override; std::optional<common::PSBTError> FillPSBT(PartiallySignedTransaction& psbt, const PrecomputedTransactionData& txdata, int sighash_type = SIGHASH_DEFAULT, bool sign = true, bool bip32derivs = false, int* n_signed = nullptr, bool finalize = true) const override;
uint256 GetID() const override; uint256 GetID() const override;

View file

@ -60,7 +60,7 @@ BOOST_AUTO_TEST_CASE(psbt_updater_test)
// Fill transaction with our data // Fill transaction with our data
bool complete = true; bool complete = true;
BOOST_REQUIRE_EQUAL(TransactionError::OK, m_wallet.FillPSBT(psbtx, complete, SIGHASH_ALL, false, true)); BOOST_REQUIRE(!m_wallet.FillPSBT(psbtx, complete, SIGHASH_ALL, false, true));
// Get the final tx // Get the final tx
DataStream ssTx{}; DataStream ssTx{};
@ -73,7 +73,7 @@ BOOST_AUTO_TEST_CASE(psbt_updater_test)
// Try to sign the mutated input // Try to sign the mutated input
SignatureData sigdata; SignatureData sigdata;
BOOST_CHECK(m_wallet.FillPSBT(psbtx, complete, SIGHASH_ALL, true, true) != TransactionError::OK); BOOST_CHECK(m_wallet.FillPSBT(psbtx, complete, SIGHASH_ALL, true, true));
} }
BOOST_AUTO_TEST_CASE(parse_hd_keypath) BOOST_AUTO_TEST_CASE(parse_hd_keypath)

View file

@ -81,6 +81,7 @@
struct KeyOriginInfo; struct KeyOriginInfo;
using common::PSBTError;
using interfaces::FoundBlock; using interfaces::FoundBlock;
namespace wallet { namespace wallet {
@ -2167,7 +2168,7 @@ bool CWallet::SignTransaction(CMutableTransaction& tx, const std::map<COutPoint,
return false; return false;
} }
TransactionError CWallet::FillPSBT(PartiallySignedTransaction& psbtx, bool& complete, int sighash_type, bool sign, bool bip32derivs, size_t * n_signed, bool finalize) const std::optional<PSBTError> CWallet::FillPSBT(PartiallySignedTransaction& psbtx, bool& complete, int sighash_type, bool sign, bool bip32derivs, size_t * n_signed, bool finalize) const
{ {
if (n_signed) { if (n_signed) {
*n_signed = 0; *n_signed = 0;
@ -2200,9 +2201,9 @@ TransactionError CWallet::FillPSBT(PartiallySignedTransaction& psbtx, bool& comp
// Fill in information from ScriptPubKeyMans // Fill in information from ScriptPubKeyMans
for (ScriptPubKeyMan* spk_man : GetAllScriptPubKeyMans()) { for (ScriptPubKeyMan* spk_man : GetAllScriptPubKeyMans()) {
int n_signed_this_spkm = 0; int n_signed_this_spkm = 0;
TransactionError res = spk_man->FillPSBT(psbtx, txdata, sighash_type, sign, bip32derivs, &n_signed_this_spkm, finalize); const auto error{spk_man->FillPSBT(psbtx, txdata, sighash_type, sign, bip32derivs, &n_signed_this_spkm, finalize)};
if (res != TransactionError::OK) { if (error) {
return res; return error;
} }
if (n_signed) { if (n_signed) {
@ -2218,7 +2219,7 @@ TransactionError CWallet::FillPSBT(PartiallySignedTransaction& psbtx, bool& comp
complete &= PSBTInputSigned(input); complete &= PSBTInputSigned(input);
} }
return TransactionError::OK; return {};
} }
SigningResult CWallet::SignMessage(const std::string& message, const PKHash& pkhash, std::string& str_sig) const SigningResult CWallet::SignMessage(const std::string& message, const PKHash& pkhash, std::string& str_sig) const

View file

@ -58,7 +58,9 @@ class Coin;
class SigningProvider; class SigningProvider;
enum class MemPoolRemovalReason; enum class MemPoolRemovalReason;
enum class SigningResult; enum class SigningResult;
enum class TransactionError; namespace common {
enum class PSBTError;
} // namespace common
namespace interfaces { namespace interfaces {
class Wallet; class Wallet;
} }
@ -659,7 +661,7 @@ public:
* @param[in] finalize whether to create the final scriptSig or scriptWitness if possible * @param[in] finalize whether to create the final scriptSig or scriptWitness if possible
* return error * return error
*/ */
TransactionError FillPSBT(PartiallySignedTransaction& psbtx, std::optional<common::PSBTError> FillPSBT(PartiallySignedTransaction& psbtx,
bool& complete, bool& complete,
int sighash_type = SIGHASH_DEFAULT, int sighash_type = SIGHASH_DEFAULT,
bool sign = true, bool sign = true,