Skip to content

Commit 02e62c6

Browse files
committed
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.
1 parent 0d44c44 commit 02e62c6

25 files changed

+156
-97
lines changed

src/Makefile.am

+1
Original file line numberDiff line numberDiff line change
@@ -137,6 +137,7 @@ BITCOIN_CORE_H = \
137137
common/bloom.h \
138138
common/init.h \
139139
common/run_command.h \
140+
common/types.h \
140141
common/url.h \
141142
compat/assumptions.h \
142143
compat/byteswap.h \

src/common/types.h

+26
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
// Copyright (c) 2010-2021 The Bitcoin Core developers
2+
// Distributed under the MIT software license, see the accompanying
3+
// file COPYING or http://www.opensource.org/licenses/mit-license.php.
4+
5+
//! @file common/types.h is a home for simple enum and struct type definitions
6+
//! that can be used internally by functions in the libbitcoin_common library,
7+
//! but also used externally by node, wallet, and GUI code.
8+
//!
9+
//! This file is intended to define only simple types that do not have external
10+
//! dependencies. More complicated types should be defined in dedicated header
11+
//! files.
12+
13+
#ifndef BITCOIN_COMMON_TYPES_H
14+
#define BITCOIN_COMMON_TYPES_H
15+
16+
namespace common {
17+
enum class PSBTError {
18+
MISSING_INPUTS,
19+
SIGHASH_MISMATCH,
20+
EXTERNAL_SIGNER_NOT_FOUND,
21+
EXTERNAL_SIGNER_FAILED,
22+
UNSUPPORTED,
23+
};
24+
} // namespace common
25+
26+
#endif // BITCOIN_COMMON_TYPES_H

src/interfaces/wallet.h

+4-2
Original file line numberDiff line numberDiff line change
@@ -30,9 +30,11 @@ class CFeeRate;
3030
class CKey;
3131
enum class FeeReason;
3232
enum class OutputType;
33-
enum class TransactionError;
3433
struct PartiallySignedTransaction;
3534
struct bilingual_str;
35+
namespace common {
36+
enum class PSBTError;
37+
} // namespace common
3638
namespace wallet {
3739
class CCoinControl;
3840
class CWallet;
@@ -202,7 +204,7 @@ class Wallet
202204
int& num_blocks) = 0;
203205

204206
//! Fill PSBT.
205-
virtual TransactionError fillPSBT(int sighash_type,
207+
virtual std::optional<common::PSBTError> fillPSBT(int sighash_type,
206208
bool sign,
207209
bool bip32derivs,
208210
size_t* n_signed,

src/node/types.h

-6
Original file line numberDiff line numberDiff line change
@@ -17,16 +17,10 @@ enum class TransactionError {
1717
OK, //!< No error
1818
MISSING_INPUTS,
1919
ALREADY_IN_CHAIN,
20-
P2P_DISABLED,
2120
MEMPOOL_REJECTED,
2221
MEMPOOL_ERROR,
23-
INVALID_PSBT,
24-
PSBT_MISMATCH,
25-
SIGHASH_MISMATCH,
2622
MAX_FEE_EXCEEDED,
2723
MAX_BURN_EXCEEDED,
28-
EXTERNAL_SIGNER_NOT_FOUND,
29-
EXTERNAL_SIGNER_FAILED,
3024
INVALID_PACKAGE,
3125
};
3226

src/psbt.cpp

+3-3
Original file line numberDiff line numberDiff line change
@@ -508,17 +508,17 @@ bool FinalizeAndExtractPSBT(PartiallySignedTransaction& psbtx, CMutableTransacti
508508
return true;
509509
}
510510

511-
TransactionError CombinePSBTs(PartiallySignedTransaction& out, const std::vector<PartiallySignedTransaction>& psbtxs)
511+
bool CombinePSBTs(PartiallySignedTransaction& out, const std::vector<PartiallySignedTransaction>& psbtxs)
512512
{
513513
out = psbtxs[0]; // Copy the first one
514514

515515
// Merge
516516
for (auto it = std::next(psbtxs.begin()); it != psbtxs.end(); ++it) {
517517
if (!out.Merge(*it)) {
518-
return TransactionError::PSBT_MISMATCH;
518+
return false;
519519
}
520520
}
521-
return TransactionError::OK;
521+
return true;
522522
}
523523

524524
std::string PSBTRoleName(PSBTRole role) {

src/psbt.h

+2-2
Original file line numberDiff line numberDiff line change
@@ -1263,9 +1263,9 @@ bool FinalizeAndExtractPSBT(PartiallySignedTransaction& psbtx, CMutableTransacti
12631263
*
12641264
* @param[out] out the combined PSBT, if successful
12651265
* @param[in] psbtxs the PSBTs to combine
1266-
* @return error (OK if we successfully combined the transactions, other error if they were not compatible)
1266+
* @return True if we successfully combined the transactions, false if they were not compatible
12671267
*/
1268-
[[nodiscard]] TransactionError CombinePSBTs(PartiallySignedTransaction& out, const std::vector<PartiallySignedTransaction>& psbtxs);
1268+
[[nodiscard]] bool CombinePSBTs(PartiallySignedTransaction& out, const std::vector<PartiallySignedTransaction>& psbtxs);
12691269

12701270
//! Decode a base64ed PSBT into a PartiallySignedTransaction
12711271
[[nodiscard]] bool DecodeBase64PSBT(PartiallySignedTransaction& decoded_psbt, const std::string& base64_psbt, std::string& error);

src/qt/psbtoperationsdialog.cpp

+9-8
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@
1313
#include <qt/forms/ui_psbtoperationsdialog.h>
1414
#include <qt/guiutil.h>
1515
#include <qt/optionsmodel.h>
16+
#include <util/error.h>
1617
#include <util/fs.h>
1718
#include <util/strencodings.h>
1819

@@ -55,10 +56,10 @@ void PSBTOperationsDialog::openWithPSBT(PartiallySignedTransaction psbtx)
5556
bool complete = FinalizePSBT(psbtx); // Make sure all existing signatures are fully combined before checking for completeness.
5657
if (m_wallet_model) {
5758
size_t n_could_sign;
58-
TransactionError err = m_wallet_model->wallet().fillPSBT(SIGHASH_ALL, /*sign=*/false, /*bip32derivs=*/true, &n_could_sign, m_transaction_data, complete);
59-
if (err != TransactionError::OK) {
59+
const auto err{m_wallet_model->wallet().fillPSBT(SIGHASH_ALL, /*sign=*/false, /*bip32derivs=*/true, &n_could_sign, m_transaction_data, complete)};
60+
if (err) {
6061
showStatus(tr("Failed to load transaction: %1")
61-
.arg(QString::fromStdString(TransactionErrorString(err).translated)),
62+
.arg(QString::fromStdString(PSBTErrorString(*err).translated)),
6263
StatusLevel::ERR);
6364
return;
6465
}
@@ -79,11 +80,11 @@ void PSBTOperationsDialog::signTransaction()
7980

8081
WalletModel::UnlockContext ctx(m_wallet_model->requestUnlock());
8182

82-
TransactionError err = m_wallet_model->wallet().fillPSBT(SIGHASH_ALL, /*sign=*/true, /*bip32derivs=*/true, &n_signed, m_transaction_data, complete);
83+
const auto err{m_wallet_model->wallet().fillPSBT(SIGHASH_ALL, /*sign=*/true, /*bip32derivs=*/true, &n_signed, m_transaction_data, complete)};
8384

84-
if (err != TransactionError::OK) {
85+
if (err) {
8586
showStatus(tr("Failed to sign transaction: %1")
86-
.arg(QString::fromStdString(TransactionErrorString(err).translated)), StatusLevel::ERR);
87+
.arg(QString::fromStdString(PSBTErrorString(*err).translated)), StatusLevel::ERR);
8788
return;
8889
}
8990

@@ -247,9 +248,9 @@ size_t PSBTOperationsDialog::couldSignInputs(const PartiallySignedTransaction &p
247248

248249
size_t n_signed;
249250
bool complete;
250-
TransactionError err = m_wallet_model->wallet().fillPSBT(SIGHASH_ALL, /*sign=*/false, /*bip32derivs=*/false, &n_signed, m_transaction_data, complete);
251+
const auto err{m_wallet_model->wallet().fillPSBT(SIGHASH_ALL, /*sign=*/false, /*bip32derivs=*/false, &n_signed, m_transaction_data, complete)};
251252

252-
if (err != TransactionError::OK) {
253+
if (err) {
253254
return 0;
254255
}
255256
return n_signed;

src/qt/sendcoinsdialog.cpp

+9-8
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,7 @@
3737
#include <QSettings>
3838
#include <QTextDocument>
3939

40+
using common::PSBTError;
4041
using wallet::CCoinControl;
4142
using wallet::DEFAULT_PAY_TX_FEE;
4243

@@ -442,26 +443,26 @@ void SendCoinsDialog::presentPSBT(PartiallySignedTransaction& psbtx)
442443
}
443444

444445
bool SendCoinsDialog::signWithExternalSigner(PartiallySignedTransaction& psbtx, CMutableTransaction& mtx, bool& complete) {
445-
TransactionError err;
446+
std::optional<PSBTError> err;
446447
try {
447448
err = model->wallet().fillPSBT(SIGHASH_ALL, /*sign=*/true, /*bip32derivs=*/true, /*n_signed=*/nullptr, psbtx, complete);
448449
} catch (const std::runtime_error& e) {
449450
QMessageBox::critical(nullptr, tr("Sign failed"), e.what());
450451
return false;
451452
}
452-
if (err == TransactionError::EXTERNAL_SIGNER_NOT_FOUND) {
453+
if (err == PSBTError::EXTERNAL_SIGNER_NOT_FOUND) {
453454
//: "External signer" means using devices such as hardware wallets.
454455
const QString msg = tr("External signer not found");
455456
QMessageBox::critical(nullptr, msg, msg);
456457
return false;
457458
}
458-
if (err == TransactionError::EXTERNAL_SIGNER_FAILED) {
459+
if (err == PSBTError::EXTERNAL_SIGNER_FAILED) {
459460
//: "External signer" means using devices such as hardware wallets.
460461
const QString msg = tr("External signer failure");
461462
QMessageBox::critical(nullptr, msg, msg);
462463
return false;
463464
}
464-
if (err != TransactionError::OK) {
465+
if (err) {
465466
tfm::format(std::cerr, "Failed to sign PSBT");
466467
processSendCoinsReturn(WalletModel::TransactionCreationFailed);
467468
return false;
@@ -501,9 +502,9 @@ void SendCoinsDialog::sendButtonClicked([[maybe_unused]] bool checked)
501502
PartiallySignedTransaction psbtx(mtx);
502503
bool complete = false;
503504
// Fill without signing
504-
TransactionError err = model->wallet().fillPSBT(SIGHASH_ALL, /*sign=*/false, /*bip32derivs=*/true, /*n_signed=*/nullptr, psbtx, complete);
505+
const auto err{model->wallet().fillPSBT(SIGHASH_ALL, /*sign=*/false, /*bip32derivs=*/true, /*n_signed=*/nullptr, psbtx, complete)};
505506
assert(!complete);
506-
assert(err == TransactionError::OK);
507+
assert(!err);
507508

508509
// Copy PSBT to clipboard and offer to save
509510
presentPSBT(psbtx);
@@ -517,9 +518,9 @@ void SendCoinsDialog::sendButtonClicked([[maybe_unused]] bool checked)
517518
bool complete = false;
518519
// Always fill without signing first. This prevents an external signer
519520
// from being called prematurely and is not expensive.
520-
TransactionError err = model->wallet().fillPSBT(SIGHASH_ALL, /*sign=*/false, /*bip32derivs=*/true, /*n_signed=*/nullptr, psbtx, complete);
521+
const auto err{model->wallet().fillPSBT(SIGHASH_ALL, /*sign=*/false, /*bip32derivs=*/true, /*n_signed=*/nullptr, psbtx, complete)};
521522
assert(!complete);
522-
assert(err == TransactionError::OK);
523+
assert(!err);
523524
send_failure = !signWithExternalSigner(psbtx, mtx, complete);
524525
// Don't broadcast when user rejects it on the device or there's a failure:
525526
broadcast = complete && !send_failure;

src/qt/walletmodel.cpp

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

src/rpc/rawtransaction.cpp

+2-3
Original file line numberDiff line numberDiff line change
@@ -1485,9 +1485,8 @@ static RPCHelpMan combinepsbt()
14851485
}
14861486

14871487
PartiallySignedTransaction merged_psbt;
1488-
const TransactionError error = CombinePSBTs(merged_psbt, psbtxs);
1489-
if (error != TransactionError::OK) {
1490-
throw JSONRPCTransactionError(error);
1488+
if (!CombinePSBTs(merged_psbt, psbtxs)) {
1489+
throw JSONRPCError(RPC_INVALID_PARAMETER, "PSBTs not compatible (different transactions)");
14911490
}
14921491

14931492
DataStream ssTx{};

src/rpc/util.cpp

+20-7
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
#include <clientversion.h>
88
#include <core_io.h>
99
#include <common/args.h>
10+
#include <common/types.h>
1011
#include <consensus/amount.h>
1112
#include <script/interpreter.h>
1213
#include <key_io.h>
@@ -30,6 +31,8 @@
3031
#include <tuple>
3132
#include <utility>
3233

34+
using common::PSBTError;
35+
3336
const std::string UNIX_EPOCH_TIME = "UNIX epoch time";
3437
const std::string EXAMPLE_ADDRESS[2] = {"bc1q09vm5lfy0j5reeulh4x5752q25uqqvz34hufdl", "bc1q02ad21edsxd23d32dfgqqsz4vv4nmtfzuklhy3"};
3538

@@ -364,25 +367,35 @@ unsigned int ParseConfirmTarget(const UniValue& value, unsigned int max_target)
364367
return unsigned_target;
365368
}
366369

370+
RPCErrorCode RPCErrorFromPSBTError(PSBTError err)
371+
{
372+
switch (err) {
373+
case PSBTError::UNSUPPORTED:
374+
return RPC_INVALID_PARAMETER;
375+
case PSBTError::SIGHASH_MISMATCH:
376+
return RPC_DESERIALIZATION_ERROR;
377+
default: break;
378+
}
379+
return RPC_TRANSACTION_ERROR;
380+
}
381+
367382
RPCErrorCode RPCErrorFromTransactionError(TransactionError terr)
368383
{
369384
switch (terr) {
370385
case TransactionError::MEMPOOL_REJECTED:
371386
return RPC_TRANSACTION_REJECTED;
372387
case TransactionError::ALREADY_IN_CHAIN:
373388
return RPC_TRANSACTION_ALREADY_IN_CHAIN;
374-
case TransactionError::P2P_DISABLED:
375-
return RPC_CLIENT_P2P_DISABLED;
376-
case TransactionError::INVALID_PSBT:
377-
case TransactionError::PSBT_MISMATCH:
378-
return RPC_INVALID_PARAMETER;
379-
case TransactionError::SIGHASH_MISMATCH:
380-
return RPC_DESERIALIZATION_ERROR;
381389
default: break;
382390
}
383391
return RPC_TRANSACTION_ERROR;
384392
}
385393

394+
UniValue JSONRPCPSBTError(PSBTError err)
395+
{
396+
return JSONRPCError(RPCErrorFromPSBTError(err), PSBTErrorString(err).original);
397+
}
398+
386399
UniValue JSONRPCTransactionError(TransactionError terr, const std::string& err_string)
387400
{
388401
if (err_string.length() > 0) {

src/rpc/util.h

+4
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,9 @@ enum class OutputType;
3737
enum class TransactionError;
3838
struct FlatSigningProvider;
3939
struct bilingual_str;
40+
namespace common {
41+
enum class PSBTError;
42+
} // namespace common
4043

4144
static constexpr bool DEFAULT_RPC_DOC_CHECK{
4245
#ifdef RPC_DOC_CHECK
@@ -128,6 +131,7 @@ int ParseSighashString(const UniValue& sighash);
128131
unsigned int ParseConfirmTarget(const UniValue& value, unsigned int max_target);
129132

130133
RPCErrorCode RPCErrorFromTransactionError(TransactionError terr);
134+
UniValue JSONRPCPSBTError(common::PSBTError err);
131135
UniValue JSONRPCTransactionError(TransactionError terr, const std::string& err_string = "");
132136

133137
//! Parse a JSON range specified as int64, or [int64, int64]

src/test/fuzz/kitchen_sink.cpp

-5
Original file line numberDiff line numberDiff line change
@@ -18,15 +18,10 @@
1818

1919
namespace {
2020
constexpr TransactionError ALL_TRANSACTION_ERROR[] = {
21-
TransactionError::OK,
2221
TransactionError::MISSING_INPUTS,
2322
TransactionError::ALREADY_IN_CHAIN,
24-
TransactionError::P2P_DISABLED,
2523
TransactionError::MEMPOOL_REJECTED,
2624
TransactionError::MEMPOOL_ERROR,
27-
TransactionError::INVALID_PSBT,
28-
TransactionError::PSBT_MISMATCH,
29-
TransactionError::SIGHASH_MISMATCH,
3025
TransactionError::MAX_FEE_EXCEEDED,
3126
};
3227
}; // namespace

src/util/error.cpp

+21-12
Original file line numberDiff line numberDiff line change
@@ -4,12 +4,33 @@
44

55
#include <util/error.h>
66

7+
#include <common/types.h>
78
#include <tinyformat.h>
89
#include <util/translation.h>
910

1011
#include <cassert>
1112
#include <string>
1213

14+
using common::PSBTError;
15+
16+
bilingual_str PSBTErrorString(PSBTError err)
17+
{
18+
switch (err) {
19+
case PSBTError::MISSING_INPUTS:
20+
return Untranslated("Inputs missing or spent");
21+
case PSBTError::SIGHASH_MISMATCH:
22+
return Untranslated("Specified sighash value does not match value stored in PSBT");
23+
case PSBTError::EXTERNAL_SIGNER_NOT_FOUND:
24+
return Untranslated("External signer not found");
25+
case PSBTError::EXTERNAL_SIGNER_FAILED:
26+
return Untranslated("External signer failed to sign");
27+
case PSBTError::UNSUPPORTED:
28+
return Untranslated("Signer does not support PSBT");
29+
// no default case, so the compiler can warn about missing cases
30+
}
31+
assert(false);
32+
}
33+
1334
bilingual_str TransactionErrorString(const TransactionError err)
1435
{
1536
switch (err) {
@@ -19,26 +40,14 @@ bilingual_str TransactionErrorString(const TransactionError err)
1940
return Untranslated("Inputs missing or spent");
2041
case TransactionError::ALREADY_IN_CHAIN:
2142
return Untranslated("Transaction already in block chain");
22-
case TransactionError::P2P_DISABLED:
23-
return Untranslated("Peer-to-peer functionality missing or disabled");
2443
case TransactionError::MEMPOOL_REJECTED:
2544
return Untranslated("Transaction rejected by mempool");
2645
case TransactionError::MEMPOOL_ERROR:
2746
return Untranslated("Mempool internal error");
28-
case TransactionError::INVALID_PSBT:
29-
return Untranslated("PSBT is not well-formed");
30-
case TransactionError::PSBT_MISMATCH:
31-
return Untranslated("PSBTs not compatible (different transactions)");
32-
case TransactionError::SIGHASH_MISMATCH:
33-
return Untranslated("Specified sighash value does not match value stored in PSBT");
3447
case TransactionError::MAX_FEE_EXCEEDED:
3548
return Untranslated("Fee exceeds maximum configured by user (e.g. -maxtxfee, maxfeerate)");
3649
case TransactionError::MAX_BURN_EXCEEDED:
3750
return Untranslated("Unspendable output exceeds maximum configured by user (maxburnamount)");
38-
case TransactionError::EXTERNAL_SIGNER_NOT_FOUND:
39-
return Untranslated("External signer not found");
40-
case TransactionError::EXTERNAL_SIGNER_FAILED:
41-
return Untranslated("External signer failed to sign");
4251
case TransactionError::INVALID_PACKAGE:
4352
return Untranslated("Transaction rejected due to invalid package");
4453
// no default case, so the compiler can warn about missing cases

0 commit comments

Comments
 (0)