diff --git a/src/interfaces/wallet.cpp b/src/interfaces/wallet.cpp --- a/src/interfaces/wallet.cpp +++ b/src/interfaces/wallet.cpp @@ -61,8 +61,8 @@ }; //! Construct wallet tx struct. - static WalletTx MakeWalletTx(CWallet &wallet, const CWalletTx &wtx) - EXCLUSIVE_LOCKS_REQUIRED(cs_main) { + static WalletTx MakeWalletTx(interfaces::Chain::Lock &locked_chain, + CWallet &wallet, const CWalletTx &wtx) { WalletTx result; result.tx = wtx.tx; result.txin_is_mine.reserve(wtx.tx->vin.size()); @@ -81,7 +81,7 @@ ? IsMine(wallet, result.txout_address.back()) : ISMINE_NO); } - result.credit = wtx.GetCredit(ISMINE_ALL); + result.credit = wtx.GetCredit(locked_chain, ISMINE_ALL); result.debit = wtx.GetDebit(ISMINE_ALL); result.change = wtx.GetChange(); result.time = wtx.GetTxTime(); @@ -91,33 +91,39 @@ } //! Construct wallet tx status struct. - static WalletTxStatus MakeWalletTxStatus(const CWalletTx &wtx) - EXCLUSIVE_LOCKS_REQUIRED(cs_main) { + static WalletTxStatus + MakeWalletTxStatus(interfaces::Chain::Lock &locked_chain, + const CWalletTx &wtx) { + // Temporary, for CheckFinalTx below. Removed in upcoming commit. + LockAnnotation lock(::cs_main); + WalletTxStatus result; CBlockIndex *block = LookupBlockIndex(wtx.hashBlock); result.block_height = (block ? block->nHeight : std::numeric_limits::max()); - result.blocks_to_maturity = wtx.GetBlocksToMaturity(); - result.depth_in_main_chain = wtx.GetDepthInMainChain(); + result.blocks_to_maturity = wtx.GetBlocksToMaturity(locked_chain); + result.depth_in_main_chain = wtx.GetDepthInMainChain(locked_chain); result.time_received = wtx.nTimeReceived; result.lock_time = wtx.tx->nLockTime; result.is_final = CheckFinalTx(*wtx.tx); - result.is_trusted = wtx.IsTrusted(); + result.is_trusted = wtx.IsTrusted(locked_chain); result.is_abandoned = wtx.isAbandoned(); result.is_coinbase = wtx.IsCoinBase(); - result.is_in_main_chain = wtx.IsInMainChain(); + result.is_in_main_chain = wtx.IsInMainChain(locked_chain); return result; } //! Construct wallet TxOut struct. - static WalletTxOut MakeWalletTxOut(CWallet &wallet, const CWalletTx &wtx, + static WalletTxOut MakeWalletTxOut(interfaces::Chain::Lock &locked_chain, + CWallet &wallet, const CWalletTx &wtx, int n, int depth) - EXCLUSIVE_LOCKS_REQUIRED(cs_main, wallet.cs_wallet) { + EXCLUSIVE_LOCKS_REQUIRED(wallet.cs_wallet) { WalletTxOut result; result.txout = wtx.tx->vout[n]; result.time = wtx.GetTxTime(); result.depth_in_main_chain = depth; - result.is_spent = wallet.IsSpent(COutPoint(wtx.GetId(), n)); + result.is_spent = + wallet.IsSpent(locked_chain, COutPoint(wtx.GetId(), n)); return result; } @@ -245,9 +251,9 @@ auto locked_chain = m_wallet.chain().lock(); LOCK(m_wallet.cs_wallet); auto pending = std::make_unique(m_wallet); - if (!m_wallet.CreateTransaction(recipients, pending->m_tx, - pending->m_key, fee, change_pos, - fail_reason, coin_control, sign)) { + if (!m_wallet.CreateTransaction( + *locked_chain, recipients, pending->m_tx, pending->m_key, + fee, change_pos, fail_reason, coin_control, sign)) { return {}; } return pending; @@ -258,7 +264,7 @@ bool abandonTransaction(const TxId &txid) override { auto locked_chain = m_wallet.chain().lock(); LOCK(m_wallet.cs_wallet); - return m_wallet.AbandonTransaction(txid); + return m_wallet.AbandonTransaction(*locked_chain, txid); } CTransactionRef getTx(const TxId &txid) override { auto locked_chain = m_wallet.chain().lock(); @@ -274,7 +280,7 @@ LOCK(m_wallet.cs_wallet); auto mi = m_wallet.mapWallet.find(txid); if (mi != m_wallet.mapWallet.end()) { - return MakeWalletTx(m_wallet, mi->second); + return MakeWalletTx(*locked_chain, m_wallet, mi->second); } return {}; } @@ -284,7 +290,8 @@ std::vector result; result.reserve(m_wallet.mapWallet.size()); for (const auto &entry : m_wallet.mapWallet) { - result.emplace_back(MakeWalletTx(m_wallet, entry.second)); + result.emplace_back( + MakeWalletTx(*locked_chain, m_wallet, entry.second)); } return result; } @@ -305,7 +312,7 @@ } num_blocks = ::chainActive.Height(); block_time = ::chainActive.Tip()->GetBlockTime(); - tx_status = MakeWalletTxStatus(mi->second); + tx_status = MakeWalletTxStatus(*locked_chain, mi->second); return true; } WalletTx getWalletTxDetails(const TxId &txid, WalletTxStatus &tx_status, @@ -319,8 +326,8 @@ num_blocks = ::chainActive.Height(); in_mempool = mi->second.InMempool(); order_form = mi->second.vOrderForm; - tx_status = MakeWalletTxStatus(mi->second); - return MakeWalletTx(m_wallet, mi->second); + tx_status = MakeWalletTxStatus(*locked_chain, mi->second); + return MakeWalletTx(*locked_chain, m_wallet, mi->second); } return {}; } @@ -382,12 +389,13 @@ auto locked_chain = m_wallet.chain().lock(); LOCK(m_wallet.cs_wallet); CoinsList result; - for (const auto &entry : m_wallet.ListCoins()) { + for (const auto &entry : m_wallet.ListCoins(*locked_chain)) { auto &group = result[entry.first]; for (const auto &coin : entry.second) { group.emplace_back(COutPoint(coin.tx->GetId(), coin.i), - MakeWalletTxOut(m_wallet, *coin.tx, - coin.i, coin.nDepth)); + MakeWalletTxOut(*locked_chain, m_wallet, + *coin.tx, coin.i, + coin.nDepth)); } } return result; @@ -402,10 +410,11 @@ result.emplace_back(); auto it = m_wallet.mapWallet.find(output.GetTxId()); if (it != m_wallet.mapWallet.end()) { - int depth = it->second.GetDepthInMainChain(); + int depth = it->second.GetDepthInMainChain(*locked_chain); if (depth >= 0) { - result.back() = MakeWalletTxOut(m_wallet, it->second, - output.GetN(), depth); + result.back() = + MakeWalletTxOut(*locked_chain, m_wallet, it->second, + output.GetN(), depth); } } } diff --git a/src/threadsafety.h b/src/threadsafety.h --- a/src/threadsafety.h +++ b/src/threadsafety.h @@ -61,4 +61,12 @@ #define ASSERT_EXCLUSIVE_LOCK(...) #endif // __GNUC__ +// Utility class for indicating to compiler thread analysis that a mutex is +// locked (when it couldn't be determined otherwise). +struct SCOPED_LOCKABLE LockAnnotation { + template + explicit LockAnnotation(Mutex &mutex) EXCLUSIVE_LOCK_FUNCTION(mutex) {} + ~LockAnnotation() UNLOCK_FUNCTION() {} +}; + #endif // BITCOIN_THREADSAFETY_H diff --git a/src/wallet/rpcdump.cpp b/src/wallet/rpcdump.cpp --- a/src/wallet/rpcdump.cpp +++ b/src/wallet/rpcdump.cpp @@ -921,7 +921,7 @@ std::map mapKeyBirth; const std::map &mapKeyPool = pwallet->GetAllReserveKeys(); - pwallet->GetKeyBirthTimes(mapKeyBirth); + pwallet->GetKeyBirthTimes(*locked_chain, mapKeyBirth); std::set scripts = pwallet->GetCScripts(); // TODO: include scripts in GetKeyBirthTimes() output instead of separate diff --git a/src/wallet/rpcwallet.cpp b/src/wallet/rpcwallet.cpp --- a/src/wallet/rpcwallet.cpp +++ b/src/wallet/rpcwallet.cpp @@ -116,9 +116,10 @@ } } -static void WalletTxToJSON(interfaces::Chain &chain, const CWalletTx &wtx, - UniValue &entry) EXCLUSIVE_LOCKS_REQUIRED(cs_main) { - int confirms = wtx.GetDepthInMainChain(); +static void WalletTxToJSON(interfaces::Chain &chain, + interfaces::Chain::Lock &locked_chain, + const CWalletTx &wtx, UniValue &entry) { + int confirms = wtx.GetDepthInMainChain(locked_chain); entry.pushKV("confirmations", confirms); if (wtx.IsCoinBase()) { entry.pushKV("generated", true); @@ -129,7 +130,7 @@ entry.pushKV("blocktime", LookupBlockIndex(wtx.hashBlock)->GetBlockTime()); } else { - entry.pushKV("trusted", wtx.IsTrusted()); + entry.pushKV("trusted", wtx.IsTrusted(locked_chain)); } uint256 hash = wtx.GetId(); entry.pushKV("txid", hash.GetHex()); @@ -559,7 +560,8 @@ return ret; } -static CTransactionRef SendMoney(CWallet *const pwallet, +static CTransactionRef SendMoney(interfaces::Chain::Lock &locked_chain, + CWallet *const pwallet, const CTxDestination &address, Amount nValue, bool fSubtractFeeFromAmount, mapValue_t mapValue, std::string fromAccount) { @@ -594,8 +596,9 @@ CCoinControl coinControl; CTransactionRef tx; - if (!pwallet->CreateTransaction(vecSend, tx, reservekey, nFeeRequired, - nChangePosRet, strError, coinControl)) { + if (!pwallet->CreateTransaction(locked_chain, vecSend, tx, reservekey, + nFeeRequired, nChangePosRet, strError, + coinControl)) { if (!fSubtractFeeFromAmount && nValue + nFeeRequired > curBalance) { strError = strprintf("Error: This transaction requires a " "transaction fee of at least %s", @@ -704,7 +707,7 @@ EnsureWalletIsUnlocked(pwallet); CTransactionRef tx = - SendMoney(pwallet, dest, nAmount, fSubtractFeeFromAmount, + SendMoney(*locked_chain, pwallet, dest, nAmount, fSubtractFeeFromAmount, std::move(mapValue), {} /* fromAccount */); return tx->GetId().GetHex(); } @@ -752,7 +755,8 @@ LOCK(pwallet->cs_wallet); UniValue jsonGroupings(UniValue::VARR); - std::map balances = pwallet->GetAddressBalances(); + std::map balances = + pwallet->GetAddressBalances(*locked_chain); for (const std::set &grouping : pwallet->GetAddressGroupings()) { UniValue jsonGrouping(UniValue::VARR); @@ -894,6 +898,9 @@ // the user could have gotten from another RPC command prior to now pwallet->BlockUntilSyncedToCurrentChain(); + // Temporary, for ContextualCheckTransactionForCurrentBlock below. Removed + // in upcoming commit. + LockAnnotation lock(::cs_main); auto locked_chain = pwallet->chain().lock(); LOCK(pwallet->cs_wallet); @@ -929,7 +936,7 @@ for (const CTxOut &txout : wtx.tx->vout) { if (txout.scriptPubKey == scriptPubKey) { - if (wtx.GetDepthInMainChain() >= nMinDepth) { + if (wtx.GetDepthInMainChain(*locked_chain) >= nMinDepth) { nAmount += txout.nValue; } } @@ -994,6 +1001,9 @@ // the user could have gotten from another RPC command prior to now pwallet->BlockUntilSyncedToCurrentChain(); + // Temporary, for ContextualCheckTransactionForCurrentBlock below. Removed + // in upcoming commit. + LockAnnotation lock(::cs_main); auto locked_chain = pwallet->chain().lock(); LOCK(pwallet->cs_wallet); @@ -1022,7 +1032,7 @@ CTxDestination address; if (ExtractDestination(txout.scriptPubKey, address) && IsMine(*pwallet, address) && setAddress.count(address)) { - if (wtx.GetDepthInMainChain() >= nMinDepth) { + if (wtx.GetDepthInMainChain(*locked_chain) >= nMinDepth) { nAmount += txout.nValue; } } @@ -1386,7 +1396,7 @@ "Account has insufficient funds"); } - CTransactionRef tx = SendMoney(pwallet, dest, nAmount, false, + CTransactionRef tx = SendMoney(*locked_chain, pwallet, dest, nAmount, false, std::move(mapValue), std::move(label)); return tx->GetId().GetHex(); } @@ -1648,9 +1658,9 @@ std::string strFailReason; CTransactionRef tx; CCoinControl coinControl; - bool fCreated = - pwallet->CreateTransaction(vecSend, tx, keyChange, nFeeRequired, - nChangePosRet, strFailReason, coinControl); + bool fCreated = pwallet->CreateTransaction( + *locked_chain, vecSend, tx, keyChange, nFeeRequired, nChangePosRet, + strFailReason, coinControl); if (!fCreated) { throw JSONRPCError(RPC_WALLET_INSUFFICIENT_FUNDS, strFailReason); } @@ -1766,9 +1776,14 @@ tallyitem() {} }; -static UniValue ListReceived(const Config &config, CWallet *const pwallet, - const UniValue ¶ms, bool by_label) - EXCLUSIVE_LOCKS_REQUIRED(cs_main, pwallet->cs_wallet) { +static UniValue +ListReceived(const Config &config, interfaces::Chain::Lock &locked_chain, + CWallet *const pwallet, const UniValue ¶ms, bool by_label) + EXCLUSIVE_LOCKS_REQUIRED(pwallet->cs_wallet) { + // Temporary, for ContextualCheckTransactionForCurrentBlock below. Removed + // in upcoming commit. + LockAnnotation lock(::cs_main); + // Minimum confirmations int nMinDepth = 1; if (!params[0].isNull()) { @@ -1811,7 +1826,7 @@ continue; } - int nDepth = wtx.GetDepthInMainChain(); + int nDepth = wtx.GetDepthInMainChain(locked_chain); if (nDepth < nMinDepth) { continue; } @@ -1987,7 +2002,7 @@ auto locked_chain = pwallet->chain().lock(); LOCK(pwallet->cs_wallet); - return ListReceived(config, pwallet, request.params, false); + return ListReceived(config, *locked_chain, pwallet, request.params, false); } static UniValue listreceivedbylabel(const Config &config, @@ -2055,7 +2070,7 @@ auto locked_chain = pwallet->chain().lock(); LOCK(pwallet->cs_wallet); - return ListReceived(config, pwallet, request.params, true); + return ListReceived(config, *locked_chain, pwallet, request.params, true); } static void MaybePushAddress(UniValue &entry, const CTxDestination &dest) { @@ -2075,11 +2090,11 @@ * @param ret The UniValue into which the result is stored. * @param filter The "is mine" filter bool. */ -static void ListTransactions(CWallet *const pwallet, const CWalletTx &wtx, +static void ListTransactions(interfaces::Chain::Lock &locked_chain, + CWallet *const pwallet, const CWalletTx &wtx, const std::string &strAccount, int nMinDepth, bool fLong, UniValue &ret, - const isminefilter &filter) - EXCLUSIVE_LOCKS_REQUIRED(cs_main) { + const isminefilter &filter) { Amount nFee; std::string strSentAccount; std::list listReceived; @@ -2112,7 +2127,7 @@ entry.pushKV("vout", s.vout); entry.pushKV("fee", ValueFromAmount(-1 * nFee)); if (fLong) { - WalletTxToJSON(pwallet->chain(), wtx, entry); + WalletTxToJSON(pwallet->chain(), locked_chain, wtx, entry); } entry.pushKV("abandoned", wtx.isAbandoned()); ret.push_back(entry); @@ -2120,7 +2135,8 @@ } // Received - if (listReceived.size() > 0 && wtx.GetDepthInMainChain() >= nMinDepth) { + if (listReceived.size() > 0 && + wtx.GetDepthInMainChain(locked_chain) >= nMinDepth) { for (const COutputEntry &r : listReceived) { std::string account; if (pwallet->mapAddressBook.count(r.destination)) { @@ -2137,9 +2153,9 @@ } MaybePushAddress(entry, r.destination); if (wtx.IsCoinBase()) { - if (wtx.GetDepthInMainChain() < 1) { + if (wtx.GetDepthInMainChain(locked_chain) < 1) { entry.pushKV("category", "orphan"); - } else if (wtx.IsImmatureCoinBase()) { + } else if (wtx.IsImmatureCoinBase(locked_chain)) { entry.pushKV("category", "immature"); } else { entry.pushKV("category", "generate"); @@ -2153,7 +2169,7 @@ } entry.pushKV("vout", r.vout); if (fLong) { - WalletTxToJSON(pwallet->chain(), wtx, entry); + WalletTxToJSON(pwallet->chain(), locked_chain, wtx, entry); } ret.push_back(entry); } @@ -2412,7 +2428,8 @@ it != txOrdered.rend(); ++it) { CWalletTx *const pwtx = (*it).second.first; if (pwtx != nullptr) { - ListTransactions(pwallet, *pwtx, strAccount, 0, true, ret, filter); + ListTransactions(*locked_chain, pwallet, *pwtx, strAccount, 0, true, + ret, filter); } if (IsDeprecatedRPCEnabled(gArgs, "accounts")) { CAccountingEntry *const pacentry = (*it).second.second; @@ -2540,8 +2557,8 @@ std::string strSentAccount; std::list listReceived; std::list listSent; - int nDepth = wtx.GetDepthInMainChain(); - if (wtx.IsImmatureCoinBase() || nDepth < 0) { + int nDepth = wtx.GetDepthInMainChain(*locked_chain); + if (wtx.IsImmatureCoinBase(*locked_chain) || nDepth < 0) { continue; } wtx.GetAmounts(listReceived, listSent, nFee, strSentAccount, @@ -2744,8 +2761,9 @@ for (const std::pair &pairWtx : pwallet->mapWallet) { CWalletTx tx = pairWtx.second; - if (depth == -1 || tx.GetDepthInMainChain() < depth) { - ListTransactions(pwallet, tx, "*", 0, true, transactions, filter); + if (depth == -1 || tx.GetDepthInMainChain(*locked_chain) < depth) { + ListTransactions(*locked_chain, pwallet, tx, "*", 0, true, + transactions, filter); } } @@ -2766,8 +2784,8 @@ // We want all transactions regardless of confirmation count to // appear here, even negative confirmation ones, hence the big // negative. - ListTransactions(pwallet, it->second, "*", -100000000, true, - removed, filter); + ListTransactions(*locked_chain, pwallet, it->second, "*", + -100000000, true, removed, filter); } } paltindex = paltindex->pprev; @@ -2906,7 +2924,7 @@ } const CWalletTx &wtx = it->second; - Amount nCredit = wtx.GetCredit(filter); + Amount nCredit = wtx.GetCredit(*locked_chain, filter); Amount nDebit = wtx.GetDebit(filter); Amount nNet = nCredit - nDebit; Amount nFee = (wtx.IsFromMe(filter) ? wtx.tx->GetValueOut() - nDebit @@ -2917,10 +2935,11 @@ entry.pushKV("fee", ValueFromAmount(nFee)); } - WalletTxToJSON(pwallet->chain(), wtx, entry); + WalletTxToJSON(pwallet->chain(), *locked_chain, wtx, entry); UniValue details(UniValue::VARR); - ListTransactions(pwallet, wtx, "*", 0, false, details, filter); + ListTransactions(*locked_chain, pwallet, wtx, "*", 0, false, details, + filter); entry.pushKV("details", details); std::string strHex = EncodeHexTx(*wtx.tx, RPCSerializationFlags()); @@ -2976,7 +2995,7 @@ "Invalid or non-wallet transaction id"); } - if (!pwallet->AbandonTransaction(txid)) { + if (!pwallet->AbandonTransaction(*locked_chain, txid)) { throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Transaction not eligible for abandonment"); } @@ -3484,7 +3503,7 @@ "Invalid parameter, vout index out of bounds"); } - if (pwallet->IsSpent(output)) { + if (pwallet->IsSpent(*locked_chain, output)) { throw JSONRPCError(RPC_INVALID_PARAMETER, "Invalid parameter, expected unspent output"); } @@ -3988,8 +4007,8 @@ "-walletbroadcast"); } - std::vector txids = - pwallet->ResendWalletTransactionsBefore(GetTime(), g_connman.get()); + std::vector txids = pwallet->ResendWalletTransactionsBefore( + *locked_chain, GetTime(), g_connman.get()); UniValue result(UniValue::VARR); for (const uint256 &txid : txids) { result.push_back(txid.ToString()); @@ -4173,8 +4192,8 @@ { auto locked_chain = pwallet->chain().lock(); LOCK(pwallet->cs_wallet); - pwallet->AvailableCoins(vecOutputs, !include_unsafe, nullptr, - nMinimumAmount, nMaximumAmount, + pwallet->AvailableCoins(*locked_chain, vecOutputs, !include_unsafe, + nullptr, nMinimumAmount, nMaximumAmount, nMinimumSumAmount, nMaximumCount, nMinDepth, nMaxDepth); } diff --git a/src/wallet/test/wallet_tests.cpp b/src/wallet/test/wallet_tests.cpp --- a/src/wallet/test/wallet_tests.cpp +++ b/src/wallet/test/wallet_tests.cpp @@ -219,13 +219,13 @@ // Call GetImmatureCredit() once before adding the key to the wallet to // cache the current immature credit amount, which is 0. - BOOST_CHECK_EQUAL(wtx.GetImmatureCredit(), Amount::zero()); + BOOST_CHECK_EQUAL(wtx.GetImmatureCredit(*locked_chain), Amount::zero()); // Invalidate the cached value, add the key, and make sure a new immature // credit amount is calculated. wtx.MarkDirty(); wallet.AddKeyPubKey(coinbaseKey, coinbaseKey.GetPubKey()); - BOOST_CHECK_EQUAL(wtx.GetImmatureCredit(), 50 * COIN); + BOOST_CHECK_EQUAL(wtx.GetImmatureCredit(*locked_chain), 50 * COIN); } static int64_t AddTx(CWallet &wallet, uint32_t lockTime, int64_t mockTime, @@ -322,8 +322,9 @@ int changePos = -1; std::string error; CCoinControl dummy; - BOOST_CHECK(wallet->CreateTransaction({recipient}, tx, reservekey, fee, - changePos, error, dummy)); + BOOST_CHECK(wallet->CreateTransaction(*m_locked_chain, {recipient}, tx, + reservekey, fee, changePos, error, + dummy)); CValidationState state; BOOST_CHECK(wallet->CommitTransaction(tx, {}, {}, {}, reservekey, nullptr, state)); @@ -357,7 +358,7 @@ std::map> list; { LOCK2(cs_main, wallet->cs_wallet); - list = wallet->ListCoins(); + list = wallet->ListCoins(*m_locked_chain); } BOOST_CHECK_EQUAL(list.size(), 1U); BOOST_CHECK_EQUAL(boost::get(list.begin()->first).ToString(), @@ -375,7 +376,7 @@ false /* subtract fee */}); { LOCK2(cs_main, wallet->cs_wallet); - list = wallet->ListCoins(); + list = wallet->ListCoins(*m_locked_chain); } BOOST_CHECK_EQUAL(list.size(), 1U); BOOST_CHECK_EQUAL(boost::get(list.begin()->first).ToString(), @@ -386,7 +387,7 @@ { LOCK2(cs_main, wallet->cs_wallet); std::vector available; - wallet->AvailableCoins(available); + wallet->AvailableCoins(*m_locked_chain, available); BOOST_CHECK_EQUAL(available.size(), 2U); } for (const auto &group : list) { @@ -398,14 +399,14 @@ { LOCK2(cs_main, wallet->cs_wallet); std::vector available; - wallet->AvailableCoins(available); + wallet->AvailableCoins(*m_locked_chain, available); BOOST_CHECK_EQUAL(available.size(), 0U); } // Confirm ListCoins still returns same result as before, despite coins // being locked. { LOCK2(cs_main, wallet->cs_wallet); - list = wallet->ListCoins(); + list = wallet->ListCoins(*m_locked_chain); } BOOST_CHECK_EQUAL(list.size(), 1U); BOOST_CHECK_EQUAL(boost::get(list.begin()->first).ToString(), diff --git a/src/wallet/wallet.h b/src/wallet/wallet.h --- a/src/wallet/wallet.h +++ b/src/wallet/wallet.h @@ -8,6 +8,7 @@ #define BITCOIN_WALLET_WALLET_H #include +#include #include #include #include