Skip to content

Commit bd0dbe8

Browse files
committed
Switch away from exceptions in refactored tx code
After refactoring general-purpose PSBT and transaction code out of RPC code, for use in the GUI, it's no longer appropriate to throw exceptions. Instead we now return bools for success, and take an output parameter for an error object. We still use JSONRPCError() for the error objects, since only RPC callers actually care about the error codes.
1 parent c6c3d42 commit bd0dbe8

File tree

10 files changed

+159
-24
lines changed

10 files changed

+159
-24
lines changed

src/node/transaction.cpp

Lines changed: 45 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -5,17 +5,43 @@
55

66
#include <consensus/validation.h>
77
#include <net.h>
8-
#include <rpc/server.h>
98
#include <txmempool.h>
109
#include <validation.h>
1110
#include <validationinterface.h>
1211
#include <node/transaction.h>
1312

1413
#include <future>
1514

16-
uint256 BroadcastTransaction(const CTransactionRef tx, const bool allowhighfees) {
15+
const char* TransactionErrorString(const TransactionError err)
16+
{
17+
switch (err) {
18+
case TransactionError::OK:
19+
return "No error";
20+
case TransactionError::MISSING_INPUTS:
21+
return "Missing inputs";
22+
case TransactionError::ALREADY_IN_CHAIN:
23+
return "Transaction already in block chain";
24+
case TransactionError::P2P_DISABLED:
25+
return "Peer-to-peer functionality missing or disabled";
26+
case TransactionError::MEMPOOL_REJECTED:
27+
return "Transaction rejected by AcceptToMemoryPool";
28+
case TransactionError::MEMPOOL_ERROR:
29+
return "AcceptToMemoryPool failed";
30+
case TransactionError::INVALID_PSBT:
31+
return "PSBT is not sane";
32+
case TransactionError::SIGHASH_MISMATCH:
33+
return "Specified sighash value does not match existing value";
34+
35+
case TransactionError::UNKNOWN_ERROR:
36+
default: break;
37+
}
38+
return "Unknown error";
39+
}
40+
41+
bool BroadcastTransaction(const CTransactionRef tx, uint256& hashTx, TransactionError& error, std::string& err_string, const bool allowhighfees)
42+
{
1743
std::promise<void> promise;
18-
const uint256& hashTx = tx->GetHash();
44+
hashTx = tx->GetHash();
1945

2046
CAmount nMaxRawTxFee = maxTxFee;
2147
if (allowhighfees)
@@ -37,12 +63,17 @@ uint256 BroadcastTransaction(const CTransactionRef tx, const bool allowhighfees)
3763
if (!AcceptToMemoryPool(mempool, state, std::move(tx), &fMissingInputs,
3864
nullptr /* plTxnReplaced */, false /* bypass_limits */, nMaxRawTxFee)) {
3965
if (state.IsInvalid()) {
40-
throw JSONRPCError(RPC_TRANSACTION_REJECTED, FormatStateMessage(state));
66+
err_string = FormatStateMessage(state);
67+
error = TransactionError::MEMPOOL_REJECTED;
68+
return false;
4169
} else {
4270
if (fMissingInputs) {
43-
throw JSONRPCError(RPC_TRANSACTION_ERROR, "Missing inputs");
71+
error = TransactionError::MISSING_INPUTS;
72+
return false;
4473
}
45-
throw JSONRPCError(RPC_TRANSACTION_ERROR, FormatStateMessage(state));
74+
err_string = FormatStateMessage(state);
75+
error = TransactionError::MEMPOOL_ERROR;
76+
return false;
4677
}
4778
} else {
4879
// If wallet is enabled, ensure that the wallet has been made aware
@@ -55,7 +86,8 @@ uint256 BroadcastTransaction(const CTransactionRef tx, const bool allowhighfees)
5586
});
5687
}
5788
} else if (fHaveChain) {
58-
throw JSONRPCError(RPC_TRANSACTION_ALREADY_IN_CHAIN, "transaction already in block chain");
89+
error = TransactionError::ALREADY_IN_CHAIN;
90+
return false;
5991
} else {
6092
// Make sure we don't block forever if re-sending
6193
// a transaction already in mempool.
@@ -66,14 +98,16 @@ uint256 BroadcastTransaction(const CTransactionRef tx, const bool allowhighfees)
6698

6799
promise.get_future().wait();
68100

69-
if(!g_connman)
70-
throw JSONRPCError(RPC_CLIENT_P2P_DISABLED, "Error: Peer-to-peer functionality missing or disabled");
101+
if(!g_connman) {
102+
error = TransactionError::P2P_DISABLED;
103+
return false;
104+
}
71105

72106
CInv inv(MSG_TX, hashTx);
73107
g_connman->ForEachNode([&inv](CNode* pnode)
74108
{
75109
pnode->PushInventory(inv);
76110
});
77111

78-
return hashTx;
79-
}
112+
return true;
113+
}

src/node/transaction.h

Lines changed: 30 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,35 @@
88
#include <primitives/transaction.h>
99
#include <uint256.h>
1010

11-
/** Broadcast a transaction */
12-
uint256 BroadcastTransaction(CTransactionRef tx, bool allowhighfees = false);
11+
enum class TransactionError {
12+
OK = 0,
13+
UNKNOWN_ERROR,
14+
15+
MISSING_INPUTS,
16+
ALREADY_IN_CHAIN,
17+
P2P_DISABLED,
18+
MEMPOOL_REJECTED,
19+
MEMPOOL_ERROR,
20+
INVALID_PSBT,
21+
SIGHASH_MISMATCH,
22+
23+
ERROR_COUNT
24+
};
25+
26+
#define TRANSACTION_ERR_LAST TransactionError::ERROR_COUNT
27+
28+
const char* TransactionErrorString(const TransactionError error);
29+
30+
/**
31+
* Broadcast a transaction
32+
*
33+
* @param[in] tx the transaction to broadcast
34+
* @param[out] &txid the txid of the transaction, if successfully broadcast
35+
* @param[out] &error reference to UniValue to fill with error info on failure
36+
* @param[out] &err_string reference to std::string to fill with error string if available
37+
* @param[in] allowhighfees whether to allow fees exceeding maxTxFee
38+
* return true on success, false on error (and fills in `error`)
39+
*/
40+
bool BroadcastTransaction(CTransactionRef tx, uint256& txid, TransactionError& error, std::string& err_string, bool allowhighfees = false);
1341

1442
#endif // BITCOIN_NODE_TRANSACTION_H

src/psbt.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
#define BITCOIN_PSBT_H
77

88
#include <attributes.h>
9+
#include <node/transaction.h>
910
#include <primitives/transaction.h>
1011
#include <pubkey.h>
1112
#include <script/sign.h>

src/rpc/rawtransaction.cpp

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1050,7 +1050,14 @@ static UniValue sendrawtransaction(const JSONRPCRequest& request)
10501050

10511051
bool allowhighfees = false;
10521052
if (!request.params[1].isNull()) allowhighfees = request.params[1].get_bool();
1053-
return BroadcastTransaction(tx, allowhighfees).GetHex();
1053+
uint256 txid;
1054+
TransactionError err;
1055+
std::string err_string;
1056+
if (!BroadcastTransaction(tx, txid, err, err_string, allowhighfees)) {
1057+
throw JSONRPCTransactionError(err, err_string);
1058+
}
1059+
1060+
return txid.GetHex();
10541061
}
10551062

10561063
static UniValue testmempoolaccept(const JSONRPCRequest& request)

src/rpc/util.cpp

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -141,6 +141,32 @@ unsigned int ParseConfirmTarget(const UniValue& value)
141141
return (unsigned int)target;
142142
}
143143

144+
RPCErrorCode RPCErrorFromTransactionError(TransactionError terr)
145+
{
146+
switch (terr) {
147+
case TransactionError::MEMPOOL_REJECTED:
148+
return RPC_TRANSACTION_REJECTED;
149+
case TransactionError::ALREADY_IN_CHAIN:
150+
return RPC_TRANSACTION_ALREADY_IN_CHAIN;
151+
case TransactionError::P2P_DISABLED:
152+
return RPC_CLIENT_P2P_DISABLED;
153+
case TransactionError::INVALID_PSBT:
154+
case TransactionError::SIGHASH_MISMATCH:
155+
return RPC_DESERIALIZATION_ERROR;
156+
default: break;
157+
}
158+
return RPC_TRANSACTION_ERROR;
159+
}
160+
161+
UniValue JSONRPCTransactionError(TransactionError terr, const std::string& err_string)
162+
{
163+
if (err_string.length() > 0) {
164+
return JSONRPCError(RPCErrorFromTransactionError(terr), err_string);
165+
} else {
166+
return JSONRPCError(RPCErrorFromTransactionError(terr), TransactionErrorString(terr));
167+
}
168+
}
169+
144170
struct Section {
145171
Section(const std::string& left, const std::string& right)
146172
: m_left{left}, m_right{right} {}

src/rpc/util.h

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
#ifndef BITCOIN_RPC_UTIL_H
66
#define BITCOIN_RPC_UTIL_H
77

8+
#include <node/transaction.h>
89
#include <pubkey.h>
910
#include <script/standard.h>
1011
#include <univalue.h>
@@ -31,6 +32,9 @@ UniValue DescribeAddress(const CTxDestination& dest);
3132
//! Parse a confirm target option and raise an RPC error if it is invalid.
3233
unsigned int ParseConfirmTarget(const UniValue& value);
3334

35+
RPCErrorCode RPCErrorFromTransactionError(TransactionError terr);
36+
UniValue JSONRPCTransactionError(TransactionError terr, const std::string& err_string = "");
37+
3438
struct RPCArg {
3539
enum class Type {
3640
OBJ,

src/wallet/psbtwallet.cpp

Lines changed: 8 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -2,14 +2,13 @@
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 <rpc/protocol.h>
65
#include <wallet/psbtwallet.h>
76

8-
bool FillPSBT(const CWallet* pwallet, PartiallySignedTransaction& psbtx, int sighash_type, bool sign, bool bip32derivs)
7+
bool FillPSBT(const CWallet* pwallet, PartiallySignedTransaction& psbtx, TransactionError& error, bool& complete, int sighash_type, bool sign, bool bip32derivs)
98
{
109
LOCK(pwallet->cs_wallet);
1110
// Get all of the previous transactions
12-
bool complete = true;
11+
complete = true;
1312
for (unsigned int i = 0; i < psbtx.tx->vin.size(); ++i) {
1413
const CTxIn& txin = psbtx.tx->vin[i];
1514
PSBTInput& input = psbtx.inputs.at(i);
@@ -20,7 +19,8 @@ bool FillPSBT(const CWallet* pwallet, PartiallySignedTransaction& psbtx, int sig
2019

2120
// Verify input looks sane. This will check that we have at most one uxto, witness or non-witness.
2221
if (!input.IsSane()) {
23-
throw JSONRPCError(RPC_DESERIALIZATION_ERROR, "PSBT input is not sane.");
22+
error = TransactionError::INVALID_PSBT;
23+
return false;
2424
}
2525

2626
// If we have no utxo, grab it from the wallet.
@@ -37,7 +37,8 @@ bool FillPSBT(const CWallet* pwallet, PartiallySignedTransaction& psbtx, int sig
3737

3838
// Get the Sighash type
3939
if (sign && input.sighash_type > 0 && input.sighash_type != sighash_type) {
40-
throw JSONRPCError(RPC_DESERIALIZATION_ERROR, "Specified Sighash and sighash in PSBT do not match.");
40+
error = TransactionError::SIGHASH_MISMATCH;
41+
return false;
4142
}
4243

4344
complete &= SignPSBTInput(HidingSigningProvider(pwallet, !sign, !bip32derivs), psbtx, i, sighash_type);
@@ -56,5 +57,6 @@ bool FillPSBT(const CWallet* pwallet, PartiallySignedTransaction& psbtx, int sig
5657
ProduceSignature(HidingSigningProvider(pwallet, true, !bip32derivs), creator, out.scriptPubKey, sigdata);
5758
psbt_out.FromSignatureData(sigdata);
5859
}
59-
return complete;
60+
61+
return true;
6062
}

src/wallet/psbtwallet.h

Lines changed: 23 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,10 +5,32 @@
55
#ifndef BITCOIN_WALLET_PSBTWALLET_H
66
#define BITCOIN_WALLET_PSBTWALLET_H
77

8+
#include <node/transaction.h>
89
#include <psbt.h>
910
#include <primitives/transaction.h>
1011
#include <wallet/wallet.h>
1112

12-
bool FillPSBT(const CWallet* pwallet, PartiallySignedTransaction& psbtx, int sighash_type = 1 /* SIGHASH_ALL */, bool sign = true, bool bip32derivs = false);
13+
/**
14+
* Fills out a PSBT with information from the wallet. Fills in UTXOs if we have
15+
* them. Tries to sign if sign=true. Sets `complete` if the PSBT is now complete
16+
* (i.e. has all required signatures or signature-parts, and is ready to
17+
* finalize.) Sets `error` and returns false if something goes wrong.
18+
*
19+
* @param[in] pwallet pointer to a wallet
20+
* @param[in] &psbtx reference to PartiallySignedTransaction to fill in
21+
* @param[out] &error reference to UniValue to fill with error info on failure
22+
* @param[out] &complete indicates whether the PSBT is now complete
23+
* @param[in] sighash_type the sighash type to use when signing (if PSBT does not specify)
24+
* @param[in] sign whether to sign or not
25+
* @param[in] bip32derivs whether to fill in bip32 derivation information if available
26+
* return true on success, false on error (and fills in `error`)
27+
*/
28+
bool FillPSBT(const CWallet* pwallet,
29+
PartiallySignedTransaction& psbtx,
30+
TransactionError& error,
31+
bool& complete,
32+
int sighash_type = 1 /* SIGHASH_ALL */,
33+
bool sign = true,
34+
bool bip32derivs = false);
1335

1436
#endif // BITCOIN_WALLET_PSBTWALLET_H

src/wallet/rpcwallet.cpp

Lines changed: 11 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@
1313
#include <validation.h>
1414
#include <key_io.h>
1515
#include <net.h>
16+
#include <node/transaction.h>
1617
#include <outputtype.h>
1718
#include <policy/feerate.h>
1819
#include <policy/fees.h>
@@ -4003,7 +4004,11 @@ UniValue walletprocesspsbt(const JSONRPCRequest& request)
40034004
// Fill transaction with our data and also sign
40044005
bool sign = request.params[1].isNull() ? true : request.params[1].get_bool();
40054006
bool bip32derivs = request.params[3].isNull() ? false : request.params[3].get_bool();
4006-
bool complete = FillPSBT(pwallet, psbtx, nHashType, sign, bip32derivs);
4007+
bool complete = true;
4008+
TransactionError err;
4009+
if (!FillPSBT(pwallet, psbtx, err, complete, nHashType, sign, bip32derivs)) {
4010+
throw JSONRPCTransactionError(err);
4011+
}
40074012

40084013
UniValue result(UniValue::VOBJ);
40094014
CDataStream ssTx(SER_NETWORK, PROTOCOL_VERSION);
@@ -4117,7 +4122,11 @@ UniValue walletcreatefundedpsbt(const JSONRPCRequest& request)
41174122

41184123
// Fill transaction with out data but don't sign
41194124
bool bip32derivs = request.params[4].isNull() ? false : request.params[4].get_bool();
4120-
FillPSBT(pwallet, psbtx, 1, false, bip32derivs);
4125+
bool complete = true;
4126+
TransactionError err;
4127+
if (!FillPSBT(pwallet, psbtx, err, complete, 1, false, bip32derivs)) {
4128+
throw JSONRPCTransactionError(err);
4129+
}
41214130

41224131
// Serialize the PSBT
41234132
CDataStream ssTx(SER_NETWORK, PROTOCOL_VERSION);

src/wallet/test/psbt_wallet_tests.cpp

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -61,7 +61,9 @@ BOOST_AUTO_TEST_CASE(psbt_updater_test)
6161
ssData >> psbtx;
6262

6363
// Fill transaction with our data
64-
FillPSBT(&m_wallet, psbtx, SIGHASH_ALL, false, true);
64+
TransactionError err;
65+
bool complete = true;
66+
FillPSBT(&m_wallet, psbtx, err, complete, SIGHASH_ALL, false, true);
6567

6668
// Get the final tx
6769
CDataStream ssTx(SER_NETWORK, PROTOCOL_VERSION);

0 commit comments

Comments
 (0)