Skip to content

Commit b4999d7

Browse files
c0f9225 merge bitcoin#26747: fix confusing error / GUI crash on cross-chain legacy wallet restore (Kittywhiskers Van Gogh) d63ca8a merge bitcoin#24678: Prevent wallet unload on GetWalletForJSONRPCRequest (Kittywhiskers Van Gogh) 2a880d8 merge bitcoin#25512: remove wallet dependency and refactor rpc_signrawtransaction.py (Kittywhiskers Van Gogh) f405790 merge bitcoin#25525: remove wallet dependency from mempool_updatefromblock.py (Kittywhiskers Van Gogh) c24c9ea merge bitcoin#25364: remove wallet dependency from feature_nulldummy.py (Kittywhiskers Van Gogh) a118fe1 test: reduce `num_nodes` in `feature_nulldummy.py` to 1 (Kittywhiskers Van Gogh) c6eabc1 merge bitcoin#25044: Use MiniWallet in rpc_rawtransaction.py (Kittywhiskers Van Gogh) 4e0725e merge bitcoin#21166: Introduce DeferredSignatureChecker and have SignatureExtractorClass subclass it (Kittywhiskers Van Gogh) f883122 merge bitcoin#20562: Test that a fully signed tx given to signrawtx is unchanged (Kittywhiskers Van Gogh) c349dad merge bitcoin#17204: Do not turn OP_1NEGATE in scriptSig into 0x0181 in signing code (Kittywhiskers Van Gogh) c5e8bd6 merge bitcoin#18554: ensure wallet files are not reused across chains (Kittywhiskers Van Gogh) 0fca914 merge bitcoin#24855: Fix `setwalletflag` disabling of flags (Kittywhiskers Van Gogh) f15a1b9 merge bitcoin#26005: Fix error handling (copy_file failure in RestoreWallet, and in general via interfaces) (Kittywhiskers Van Gogh) 240765e merge bitcoin#25616: Return `util::Result` from WalletLoader methods (Kittywhiskers Van Gogh) bd897fa merge bitcoin#25656: return util::Result from `GetReservedDestination` methods (Kittywhiskers Van Gogh) 56accfe merge bitcoin#25721: Replace BResult with util::Result (Kittywhiskers Van Gogh) 03939f2 partial bitcoin#24584: avoid mixing different `OutputTypes` during coin selection (Kittywhiskers Van Gogh) 50ca8cf refactor: remove `CKey` overload for `Create{,AndProcess}Block()` (Kittywhiskers Van Gogh) 9e23b11 merge bitcoin#25218: introduce generic 'Result' class and connect it to CreateTransaction and GetNewDestination (Kittywhiskers Van Gogh) ec764fd partial bitcoin#22154: Add OutputType::BECH32M and related wallet support for fetching bech32m addresses (Kittywhiskers Van Gogh) Pull request description: ## Additional Information * The `CKey` overload for `Create{,AndProcess}Block()` was introduced as a convenience function in [dash#2264](dashpay#2264). To allow for the easier backport of [bitcoin#24584](bitcoin#24584) (by resolving an ambiguous overload error) and because `GetScriptForRawPubKey` makes the same construction, it was opted to realign with upstream and drop the overload. * Even though we don't actively support any address types that utilize Bech32m, [bitcoin#25656](bitcoin#25656) assumes that `GetReservedDestination` modifies a `bilingual_str` for its error case in order to encapsulate it in a `util::Result`. In order to complete that backport, [bitcoin#22154](bitcoin#22154) has been partially backported. * `CKeyHolder` has always assumed that `GetReservedDestination` will succeed without validation of this assumption ([source](https://github.com/dashpay/dash/blob/develop/src/coinjoin/util.cpp#L29-L33)). As [bitcoin#25656](bitcoin#25656) forces us to unwrap the returned value, this assumption has been documented with an `assert` ([source](https://github.com/dashpay/dash/blob/bd897fa7097d3d68d19b8a9af14b23f4007645ca/src/coinjoin/util.cpp#L32-L34)) * When backporting [bitcoin#21166](bitcoin#21166), we don't have the luxury of a `scriptWitness` to include the redemption script, so `rpc_signrawtransaction.py` must import the script in order to spend it. This creates an additional complication as descriptor wallets do not permit wallets to mix descriptors with and without private keys ([source](https://github.com/dashpay/dash/blob/cc9da5d9e28bf9a7b411dc390523ac6d2dd09de6/src/wallet/rpc/backup.cpp#L1758)) and as P2SH descriptors don't involve a private key, those specific subtests need to be skipped for descriptor wallets. * `OP_TRUE` has been appended to the script, this mirrors the `OP_TRUE` in the `scriptWitness` ([source](https://github.com/bitcoin/bitcoin/blob/a97a9298cea085858e1a65a5e9b20d7a9e0f7303/test/functional/rpc_signrawtransaction.py#L270)) * The subtest `test_signing_with_missing_prevtx_info()` introduced in [bitcoin#25044](bitcoin#25044) requires an extra `walletpassphrase` as the preceding tests that unlock the wallet are not called for descriptor wallets, making the call necessary. It is redundant but harmless for legacy wallets. * With the backport of [bitcoin#19937](bitcoin#19937) in [dash#6726](dashpay#6726) (as ae7e4cb), we can remove a workaround in `feature_nulldummy.py` where we used two nodes to satisfy `getblocktemplate`'s requirement of a connected node as that requirement was lifted on test chains. ## Breaking Changes None expected. ## How Has This Been Tested? A full CoinJoin session run on c0f9225 ![CoinJoin session run on build c0f9225](https://github.com/user-attachments/assets/1dd31814-8d4a-47ab-95e5-5684818f3971) ## Checklist - [x] I have performed a self-review of my own code - [x] I have commented my code, particularly in hard-to-understand areas **(note: N/A)** - [x] I have added or updated relevant unit/integration/functional/e2e tests - [x] I have made corresponding changes to the documentation **(note: N/A)** - [x] I have assigned this pull request to a milestone _(for repository code-owners and collaborators only)_ ACKs for top commit: PastaPastaPasta: utACK c0f9225 UdjinM6: utACK c0f9225 Tree-SHA512: 2217281ea75afe3eb6061e1acbd42afb66ff2ab28c15a3ad03bdb528ef4e40ea30bf1adad8d0ba93310a3d561d6c3f2ad95780b0b972af238523c6f2950a39c7
2 parents a93d2bf + c0f9225 commit b4999d7

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

55 files changed

+1431
-933
lines changed

src/Makefile.test.include

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -162,6 +162,7 @@ BITCOIN_TESTS =\
162162
test/random_tests.cpp \
163163
test/ratecheck_tests.cpp \
164164
test/rest_tests.cpp \
165+
test/result_tests.cpp \
165166
test/reverselock_tests.cpp \
166167
test/rpc_tests.cpp \
167168
test/sanity_tests.cpp \
@@ -214,6 +215,7 @@ BITCOIN_TESTS += \
214215
wallet/test/wallet_crypto_tests.cpp \
215216
wallet/test/wallet_transaction_tests.cpp \
216217
wallet/test/coinselector_tests.cpp \
218+
wallet/test/availablecoins_tests.cpp \
217219
wallet/test/init_tests.cpp \
218220
wallet/test/ismine_tests.cpp \
219221
wallet/test/scriptpubkeyman_tests.cpp

src/bench/coin_selection.cpp

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -54,10 +54,10 @@ static void CoinSelection(benchmark::Bench& bench)
5454
addCoin(3 * COIN, wallet, wtxs);
5555

5656
// Create coins
57-
std::vector<COutput> coins;
57+
wallet::CoinsResult available_coins;
5858
for (const auto& wtx : wtxs) {
5959
const auto txout = wtx->tx->vout.at(0);
60-
coins.emplace_back(COutPoint(wtx->GetHash(), 0), txout, /*depth=*/6 * 24, CalculateMaximumSignedInputSize(txout, &wallet, /*coin_control=*/nullptr), /*spendable=*/true, /*solvable=*/true, /*safe=*/true, wtx->GetTxTime(), /*from_me=*/true, /*fees=*/ 0);
60+
available_coins.legacy.emplace_back(COutPoint(wtx->GetHash(), 0), txout, /*depth=*/6 * 24, CalculateMaximumSignedInputSize(txout, &wallet, /*coin_control=*/nullptr), /*spendable=*/true, /*solvable=*/true, /*safe=*/true, wtx->GetTxTime(), /*from_me=*/true, /*fees=*/ 0);
6161
}
6262
const CoinEligibilityFilter filter_standard(1, 6, 0);
6363
FastRandomContext rand{};
@@ -73,7 +73,7 @@ static void CoinSelection(benchmark::Bench& bench)
7373
/*avoid_partial=*/ false,
7474
};
7575
bench.run([&] {
76-
auto result = AttemptSelection(wallet, 1003 * COIN, filter_standard, coins, coin_selection_params);
76+
auto result = AttemptSelection(wallet, 1003 * COIN, filter_standard, available_coins, coin_selection_params, /*allow_mixed_output_types=*/true);
7777
assert(result);
7878
assert(result->GetSelectedValue() == 1003 * COIN);
7979
assert(result->GetInputSet().size() == 2);

src/bench/wallet_loading.cpp

Lines changed: 1 addition & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -47,12 +47,8 @@ static void BenchUnloadWallet(std::shared_ptr<CWallet>&& wallet)
4747

4848
static void AddTx(CWallet& wallet)
4949
{
50-
bilingual_str error;
51-
CTxDestination dest;
52-
wallet.GetNewDestination("", dest, error);
53-
5450
CMutableTransaction mtx;
55-
mtx.vout.push_back({COIN, GetScriptForDestination(dest)});
51+
mtx.vout.push_back({COIN, GetScriptForDestination(*Assert(wallet.GetNewDestination("")))});
5652
mtx.vin.push_back(CTxIn());
5753

5854
wallet.AddToWallet(MakeTransactionRef(mtx), TxStateInactive{});

src/coinjoin/client.cpp

Lines changed: 4 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1552,7 +1552,7 @@ bool CCoinJoinClientSession::CreateCollateralTransaction(CMutableTransaction& tx
15521552
AssertLockHeld(m_wallet->cs_wallet);
15531553

15541554
CCoinControl coin_control(CoinType::ONLY_COINJOIN_COLLATERAL);
1555-
std::vector<COutput> vCoins{AvailableCoinsListUnspent(*m_wallet, &coin_control).coins};
1555+
std::vector<COutput> vCoins{AvailableCoinsListUnspent(*m_wallet, &coin_control).all()};
15561556
if (vCoins.empty()) {
15571557
strReason = strprintf("%s requires a collateral transaction and could not locate an acceptable input!", gCoinJoinName);
15581558
return false;
@@ -1571,11 +1571,10 @@ bool CCoinJoinClientSession::CreateCollateralTransaction(CMutableTransaction& tx
15711571
if (txout.nValue >= CoinJoin::GetCollateralAmount() * 2) {
15721572
// make our change address
15731573
CScript scriptChange;
1574-
CTxDestination dest;
15751574
ReserveDestination reserveDest(m_wallet.get());
1576-
bool success = reserveDest.GetReservedDestination(dest, true);
1577-
assert(success); // should never fail, as we just unlocked
1578-
scriptChange = GetScriptForDestination(dest);
1575+
auto dest_opt = reserveDest.GetReservedDestination(true);
1576+
assert(dest_opt); // should never fail, as we just unlocked
1577+
scriptChange = GetScriptForDestination(*dest_opt);
15791578
reserveDest.KeepDestination();
15801579
// return change
15811580
txCollateral.vout.emplace_back(txout.nValue - CoinJoin::GetCollateralAmount(), scriptChange);

src/coinjoin/util.cpp

Lines changed: 12 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,9 @@ inline unsigned int GetSizeOfCompactSizeDiff(uint64_t nSizePrev, uint64_t nSizeN
2929
CKeyHolder::CKeyHolder(CWallet* pwallet) :
3030
reserveDestination(pwallet)
3131
{
32-
reserveDestination.GetReservedDestination(dest, false);
32+
auto dest_opt = reserveDestination.GetReservedDestination(false);
33+
assert(dest_opt);
34+
dest = *dest_opt;
3335
}
3436

3537
void CKeyHolder::KeepKey()
@@ -99,10 +101,10 @@ CTransactionBuilderOutput::CTransactionBuilderOutput(CTransactionBuilder* pTxBui
99101
nAmount(nAmountIn)
100102
{
101103
assert(pTxBuilder);
102-
CTxDestination txdest;
103104
LOCK(wallet.cs_wallet);
104-
dest.GetReservedDestination(txdest, false);
105-
script = ::GetScriptForDestination(txdest);
105+
auto dest_opt = dest.GetReservedDestination(false);
106+
assert(dest_opt);
107+
script = ::GetScriptForDestination(*dest_opt);
106108
}
107109

108110
bool CTransactionBuilderOutput::UpdateAmount(const CAmount nNewAmount)
@@ -280,12 +282,13 @@ bool CTransactionBuilder::Commit(bilingual_str& strResult)
280282
CTransactionRef tx;
281283
{
282284
LOCK2(m_wallet.cs_wallet, ::cs_main);
283-
FeeCalculation fee_calc_out;
284-
if (auto txr = wallet::CreateTransaction(m_wallet, vecSend, nChangePosRet, strResult, coinControl, fee_calc_out)) {
285-
tx = txr->tx;
286-
nFeeRet = txr->fee;
287-
nChangePosRet = txr->change_pos;
285+
auto ret = wallet::CreateTransaction(m_wallet, vecSend, nChangePosRet, coinControl);
286+
if (ret) {
287+
tx = ret->tx;
288+
nFeeRet = ret->fee;
289+
nChangePosRet = ret->change_pos;
288290
} else {
291+
strResult = util::ErrorString(ret);
289292
return false;
290293
}
291294
}

src/dummywallet.cpp

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -78,7 +78,8 @@ void DummyWalletInit::AddWalletOptions(ArgsManager& argsman) const
7878
"-flushwallet",
7979
"-privdb",
8080
"-walletrejectlongchains",
81-
"-unsafesqlitesync"
81+
"-walletcrosschain",
82+
"-unsafesqlitesync",
8283
});
8384
}
8485

src/interfaces/wallet.h

Lines changed: 24 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -112,7 +112,7 @@ class Wallet
112112
virtual std::string getWalletName() = 0;
113113

114114
// Get a new address.
115-
virtual bool getNewDestination(const std::string label, CTxDestination& dest) = 0;
115+
virtual util::Result<CTxDestination> getNewDestination(const std::string& label) = 0;
116116

117117
//! Get public key.
118118
virtual bool getPubKey(const CScript& script, const CKeyID& address, CPubKey& pub_key) = 0;
@@ -167,12 +167,11 @@ class Wallet
167167
virtual std::vector<COutPoint> listProTxCoins() = 0;
168168

169169
//! Create transaction.
170-
virtual CTransactionRef createTransaction(const std::vector<wallet::CRecipient>& recipients,
170+
virtual util::Result<CTransactionRef> createTransaction(const std::vector<wallet::CRecipient>& recipients,
171171
const wallet::CCoinControl& coin_control,
172172
bool sign,
173173
int& change_pos,
174-
CAmount& fee,
175-
bilingual_str& fail_reason) = 0;
174+
CAmount& fee) = 0;
176175

177176
//! Commit transaction.
178177
virtual void commitTransaction(CTransactionRef tx,
@@ -348,35 +347,35 @@ class Wallet
348347
class WalletLoader : public ChainClient
349348
{
350349
public:
351-
//! Register non-core wallet RPCs
352-
virtual void registerOtherRpcs(const Span<const CRPCCommand>& commands) = 0;
350+
//! Register non-core wallet RPCs
351+
virtual void registerOtherRpcs(const Span<const CRPCCommand>& commands) = 0;
353352

354-
//! Create new wallet.
355-
virtual std::unique_ptr<Wallet> createWallet(const std::string& name, const SecureString& passphrase, uint64_t wallet_creation_flags, bilingual_str& error, std::vector<bilingual_str>& warnings) = 0;
353+
//! Create new wallet.
354+
virtual util::Result<std::unique_ptr<Wallet>> createWallet(const std::string& name, const SecureString& passphrase, uint64_t wallet_creation_flags, std::vector<bilingual_str>& warnings) = 0;
356355

357-
//! Load existing wallet.
358-
virtual std::unique_ptr<Wallet> loadWallet(const std::string& name, bilingual_str& error, std::vector<bilingual_str>& warnings) = 0;
356+
//! Load existing wallet.
357+
virtual util::Result<std::unique_ptr<Wallet>> loadWallet(const std::string& name, std::vector<bilingual_str>& warnings) = 0;
359358

360-
//! Return default wallet directory.
361-
virtual std::string getWalletDir() = 0;
359+
//! Return default wallet directory.
360+
virtual std::string getWalletDir() = 0;
362361

363-
//! Restore backup wallet
364-
virtual BResult<std::unique_ptr<Wallet>> restoreWallet(const fs::path& backup_file, const std::string& wallet_name, std::vector<bilingual_str>& warnings) = 0;
362+
//! Restore backup wallet
363+
virtual util::Result<std::unique_ptr<Wallet>> restoreWallet(const fs::path& backup_file, const std::string& wallet_name, std::vector<bilingual_str>& warnings) = 0;
365364

366-
//! Return available wallets in wallet directory.
367-
virtual std::vector<std::string> listWalletDir() = 0;
365+
//! Return available wallets in wallet directory.
366+
virtual std::vector<std::string> listWalletDir() = 0;
368367

369-
//! Return interfaces for accessing wallets (if any).
370-
virtual std::vector<std::unique_ptr<Wallet>> getWallets() = 0;
368+
//! Return interfaces for accessing wallets (if any).
369+
virtual std::vector<std::unique_ptr<Wallet>> getWallets() = 0;
371370

372-
//! Register handler for load wallet messages. This callback is triggered by
373-
//! createWallet and loadWallet above, and also triggered when wallets are
374-
//! loaded at startup or by RPC.
375-
using LoadWalletFn = std::function<void(std::unique_ptr<Wallet> wallet)>;
376-
virtual std::unique_ptr<Handler> handleLoadWallet(LoadWalletFn fn) = 0;
371+
//! Register handler for load wallet messages. This callback is triggered by
372+
//! createWallet and loadWallet above, and also triggered when wallets are
373+
//! loaded at startup or by RPC.
374+
using LoadWalletFn = std::function<void(std::unique_ptr<Wallet> wallet)>;
375+
virtual std::unique_ptr<Handler> handleLoadWallet(LoadWalletFn fn) = 0;
377376

378-
//! Return pointer to internal context, useful for testing.
379-
virtual wallet::WalletContext* context() { return nullptr; }
377+
//! Return pointer to internal context, useful for testing.
378+
virtual wallet::WalletContext* context() { return nullptr; }
380379
};
381380

382381
//! Information about one wallet address.

src/qt/addresstablemodel.cpp

Lines changed: 6 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -366,23 +366,21 @@ QString AddressTableModel::addRow(const QString &type, const QString &label, con
366366
else if(type == Receive)
367367
{
368368
// Generate a new address to associate with given label
369-
CTxDestination dest;
370-
if(!walletModel->wallet().getNewDestination(strLabel, dest))
371-
{
369+
auto op_dest = walletModel->wallet().getNewDestination(strLabel);
370+
if (!op_dest) {
372371
WalletModel::UnlockContext ctx(walletModel->requestUnlock());
373-
if(!ctx.isValid())
374-
{
372+
if (!ctx.isValid()) {
375373
// Unlock wallet failed or was cancelled
376374
editStatus = WALLET_UNLOCK_FAILURE;
377375
return QString();
378376
}
379-
if(!walletModel->wallet().getNewDestination(strLabel, dest))
380-
{
377+
op_dest = walletModel->wallet().getNewDestination(strLabel);
378+
if (!op_dest) {
381379
editStatus = KEY_GENERATION_FAILURE;
382380
return QString();
383381
}
384382
}
385-
strAddress = EncodeDestination(dest);
383+
strAddress = EncodeDestination(*op_dest);
386384
}
387385
else
388386
{

src/qt/walletcontroller.cpp

Lines changed: 17 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -258,9 +258,13 @@ void CreateWalletActivity::createWallet()
258258
}
259259

260260
QTimer::singleShot(500ms, worker(), [this, name, flags] {
261-
std::unique_ptr<interfaces::Wallet> wallet = node().walletLoader().createWallet(name, m_passphrase, flags, m_error_message, m_warning_message);
261+
auto wallet{node().walletLoader().createWallet(name, m_passphrase, flags, m_warning_message)};
262262

263-
if (wallet) m_wallet_model = m_wallet_controller->getOrCreateWallet(std::move(wallet));
263+
if (wallet) {
264+
m_wallet_model = m_wallet_controller->getOrCreateWallet(std::move(*wallet));
265+
} else {
266+
m_error_message = util::ErrorString(wallet);
267+
}
264268

265269
QTimer::singleShot(500ms, this, &CreateWalletActivity::finish);
266270
});
@@ -330,9 +334,13 @@ void OpenWalletActivity::open(const std::string& path)
330334
tr("Opening Wallet <b>%1</b>…").arg(name.toHtmlEscaped()));
331335

332336
QTimer::singleShot(0, worker(), [this, path] {
333-
std::unique_ptr<interfaces::Wallet> wallet = node().walletLoader().loadWallet(path, m_error_message, m_warning_message);
337+
auto wallet{node().walletLoader().loadWallet(path, m_warning_message)};
334338

335-
if (wallet) m_wallet_model = m_wallet_controller->getOrCreateWallet(std::move(wallet));
339+
if (wallet) {
340+
m_wallet_model = m_wallet_controller->getOrCreateWallet(std::move(*wallet));
341+
} else {
342+
m_error_message = util::ErrorString(wallet);
343+
}
336344

337345
QTimer::singleShot(0, this, &OpenWalletActivity::finish);
338346
});
@@ -375,8 +383,11 @@ void RestoreWalletActivity::restore(const fs::path& backup_file, const std::stri
375383
QTimer::singleShot(0, worker(), [this, backup_file, wallet_name] {
376384
auto wallet{node().walletLoader().restoreWallet(backup_file, wallet_name, m_warning_message)};
377385

378-
m_error_message = wallet ? bilingual_str{} : wallet.GetError();
379-
if (wallet) m_wallet_model = m_wallet_controller->getOrCreateWallet(wallet.ReleaseObj());
386+
if (wallet) {
387+
m_wallet_model = m_wallet_controller->getOrCreateWallet(std::move(*wallet));
388+
} else {
389+
m_error_message = util::ErrorString(wallet);
390+
}
380391

381392
QTimer::singleShot(0, this, &RestoreWalletActivity::finish);
382393
});

src/qt/walletmodel.cpp

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -251,11 +251,11 @@ WalletModel::SendCoinsReturn WalletModel::prepareTransaction(WalletModelTransact
251251
}
252252

253253
CAmount nFeeRequired = 0;
254-
bilingual_str error;
255254
int nChangePosRet = -1;
256255

257256
auto& newTx = transaction.getWtx();
258-
newTx = m_wallet->createTransaction(vecSend, coinControl, !wallet().privateKeysDisabled() /* sign */, nChangePosRet, nFeeRequired, error);
257+
const auto& res = m_wallet->createTransaction(vecSend, coinControl, !wallet().privateKeysDisabled() /* sign */, nChangePosRet, nFeeRequired);
258+
newTx = res ? *res : nullptr;
259259
transaction.setTransactionFee(nFeeRequired);
260260
if (fSubtractFeeFromAmount && newTx)
261261
transaction.reassignAmounts(nChangePosRet);
@@ -266,7 +266,7 @@ WalletModel::SendCoinsReturn WalletModel::prepareTransaction(WalletModelTransact
266266
{
267267
return SendCoinsReturn(AmountWithFeeExceedsBalance);
268268
}
269-
Q_EMIT message(tr("Send Coins"), QString::fromStdString(error.translated),
269+
Q_EMIT message(tr("Send Coins"), QString::fromStdString(util::ErrorString(res).translated),
270270
CClientUIInterface::MSG_ERROR);
271271
return TransactionCreationFailed;
272272
}

0 commit comments

Comments
 (0)