Skip to content

Commit ce8176d

Browse files
committed
Merge #10295: [qt] Move some WalletModel functions into CWallet
108f04f Add missing LOCK2 in CWallet::GetAvailableBalance (Russell Yanofsky) 429aa9e [test] Move some tests from qt -> wallet (Russell Yanofsky) d944bd7 [qt] Move some WalletModel functions into CWallet (Russell Yanofsky) ef8ca17 [test] Add tests for some walletmodel functions (Russell Yanofsky) Tree-SHA512: f6384d9f2ff3f7fb173d414588c3e7dc8c311b8ed2ce2b0979fb824a0ed83a7302890ccd3d83197f07f6fdcb6b1ca151584d90ea1961d88dfe8956c87087cde8
2 parents 4677151 + 108f04f commit ce8176d

File tree

6 files changed

+235
-54
lines changed

6 files changed

+235
-54
lines changed

src/Makefile.qttest.include

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -46,7 +46,8 @@ qt_test_test_bitcoin_qt_SOURCES = \
4646
if ENABLE_WALLET
4747
qt_test_test_bitcoin_qt_SOURCES += \
4848
qt/test/paymentservertests.cpp \
49-
qt/test/wallettests.cpp
49+
qt/test/wallettests.cpp \
50+
wallet/test/wallet_test_fixture.cpp
5051
endif
5152

5253
nodist_qt_test_test_bitcoin_qt_SOURCES = $(TEST_QT_MOC_CPP)

src/qt/test/wallettests.cpp

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -65,7 +65,6 @@ QModelIndex FindTx(const QAbstractItemModel& model, const uint256& txid)
6565
}
6666
return {};
6767
}
68-
}
6968

7069
//! Simple qt wallet tests.
7170
//
@@ -80,7 +79,7 @@ QModelIndex FindTx(const QAbstractItemModel& model, const uint256& txid)
8079
// src/qt/test/test_bitcoin-qt -platform xcb # Linux
8180
// src/qt/test/test_bitcoin-qt -platform windows # Windows
8281
// src/qt/test/test_bitcoin-qt -platform cocoa # macOS
83-
void WalletTests::walletTests()
82+
void TestSendCoins()
8483
{
8584
// Set up wallet and chain with 101 blocks (1 mature block for spending).
8685
TestChain100Setup test;
@@ -117,3 +116,10 @@ void WalletTests::walletTests()
117116
bitdb.Flush(true);
118117
bitdb.Reset();
119118
}
119+
120+
}
121+
122+
void WalletTests::walletTests()
123+
{
124+
TestSendCoins();
125+
}

src/qt/walletmodel.cpp

Lines changed: 7 additions & 49 deletions
Original file line numberDiff line numberDiff line change
@@ -68,14 +68,7 @@ CAmount WalletModel::getBalance(const CCoinControl *coinControl) const
6868
{
6969
if (coinControl)
7070
{
71-
CAmount nBalance = 0;
72-
std::vector<COutput> vCoins;
73-
wallet->AvailableCoins(vCoins, true, coinControl);
74-
BOOST_FOREACH(const COutput& out, vCoins)
75-
if(out.fSpendable)
76-
nBalance += out.tx->tx->vout[out.i].nValue;
77-
78-
return nBalance;
71+
return wallet->GetAvailableBalance(coinControl);
7972
}
8073

8174
return wallet->GetBalance();
@@ -600,38 +593,11 @@ bool WalletModel::isSpent(const COutPoint& outpoint) const
600593
// AvailableCoins + LockedCoins grouped by wallet address (put change in one group with wallet address)
601594
void WalletModel::listCoins(std::map<QString, std::vector<COutput> >& mapCoins) const
602595
{
603-
std::vector<COutput> vCoins;
604-
wallet->AvailableCoins(vCoins);
605-
606-
LOCK2(cs_main, wallet->cs_wallet); // ListLockedCoins, mapWallet
607-
std::vector<COutPoint> vLockedCoins;
608-
wallet->ListLockedCoins(vLockedCoins);
609-
610-
// add locked coins
611-
BOOST_FOREACH(const COutPoint& outpoint, vLockedCoins)
612-
{
613-
if (!wallet->mapWallet.count(outpoint.hash)) continue;
614-
int nDepth = wallet->mapWallet[outpoint.hash].GetDepthInMainChain();
615-
if (nDepth < 0) continue;
616-
COutput out(&wallet->mapWallet[outpoint.hash], outpoint.n, nDepth, true /* spendable */, true /* solvable */, true /* safe */);
617-
if (outpoint.n < out.tx->tx->vout.size() && wallet->IsMine(out.tx->tx->vout[outpoint.n]) == ISMINE_SPENDABLE)
618-
vCoins.push_back(out);
619-
}
620-
621-
BOOST_FOREACH(const COutput& out, vCoins)
622-
{
623-
COutput cout = out;
624-
625-
while (wallet->IsChange(cout.tx->tx->vout[cout.i]) && cout.tx->tx->vin.size() > 0 && wallet->IsMine(cout.tx->tx->vin[0]))
626-
{
627-
if (!wallet->mapWallet.count(cout.tx->tx->vin[0].prevout.hash)) break;
628-
cout = COutput(&wallet->mapWallet[cout.tx->tx->vin[0].prevout.hash], cout.tx->tx->vin[0].prevout.n, 0 /* depth */, true /* spendable */, true /* solvable */, true /* safe */);
596+
for (auto& group : wallet->ListCoins()) {
597+
auto& resultGroup = mapCoins[QString::fromStdString(CBitcoinAddress(group.first).ToString())];
598+
for (auto& coin : group.second) {
599+
resultGroup.emplace_back(std::move(coin));
629600
}
630-
631-
CTxDestination address;
632-
if(!out.fSpendable || !ExtractDestination(cout.tx->tx->vout[cout.i].scriptPubKey, address))
633-
continue;
634-
mapCoins[QString::fromStdString(CBitcoinAddress(address).ToString())].push_back(out);
635601
}
636602
}
637603

@@ -661,11 +627,7 @@ void WalletModel::listLockedCoins(std::vector<COutPoint>& vOutpts)
661627

662628
void WalletModel::loadReceiveRequests(std::vector<std::string>& vReceiveRequests)
663629
{
664-
LOCK(wallet->cs_wallet);
665-
BOOST_FOREACH(const PAIRTYPE(CTxDestination, CAddressBookData)& item, wallet->mapAddressBook)
666-
BOOST_FOREACH(const PAIRTYPE(std::string, std::string)& item2, item.second.destdata)
667-
if (item2.first.size() > 2 && item2.first.substr(0,2) == "rr") // receive request
668-
vReceiveRequests.push_back(item2.second);
630+
vReceiveRequests = wallet->GetDestValues("rr"); // receive request
669631
}
670632

671633
bool WalletModel::saveReceiveRequest(const std::string &sAddress, const int64_t nId, const std::string &sRequest)
@@ -685,11 +647,7 @@ bool WalletModel::saveReceiveRequest(const std::string &sAddress, const int64_t
685647

686648
bool WalletModel::transactionCanBeAbandoned(uint256 hash) const
687649
{
688-
LOCK2(cs_main, wallet->cs_wallet);
689-
const CWalletTx *wtx = wallet->GetWalletTx(hash);
690-
if (!wtx || wtx->isAbandoned() || wtx->GetDepthInMainChain() > 0 || wtx->InMempool())
691-
return false;
692-
return true;
650+
return wallet->TransactionCanBeAbandoned(hash);
693651
}
694652

695653
bool WalletModel::abandonTransaction(uint256 hash) const

src/wallet/test/wallet_tests.cpp

Lines changed: 101 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99
#include <utility>
1010
#include <vector>
1111

12+
#include "consensus/validation.h"
1213
#include "rpc/server.h"
1314
#include "test/test_bitcoin.h"
1415
#include "validation.h"
@@ -577,4 +578,104 @@ BOOST_AUTO_TEST_CASE(ComputeTimeSmart)
577578
SetMockTime(0);
578579
}
579580

581+
BOOST_AUTO_TEST_CASE(LoadReceiveRequests)
582+
{
583+
CTxDestination dest = CKeyID();
584+
pwalletMain->AddDestData(dest, "misc", "val_misc");
585+
pwalletMain->AddDestData(dest, "rr0", "val_rr0");
586+
pwalletMain->AddDestData(dest, "rr1", "val_rr1");
587+
588+
auto values = pwalletMain->GetDestValues("rr");
589+
BOOST_CHECK_EQUAL(values.size(), 2);
590+
BOOST_CHECK_EQUAL(values[0], "val_rr0");
591+
BOOST_CHECK_EQUAL(values[1], "val_rr1");
592+
}
593+
594+
class ListCoinsTestingSetup : public TestChain100Setup
595+
{
596+
public:
597+
ListCoinsTestingSetup()
598+
{
599+
CreateAndProcessBlock({}, GetScriptForRawPubKey(coinbaseKey.GetPubKey()));
600+
::bitdb.MakeMock();
601+
wallet.reset(new CWallet(std::unique_ptr<CWalletDBWrapper>(new CWalletDBWrapper(&bitdb, "wallet_test.dat"))));
602+
bool firstRun;
603+
wallet->LoadWallet(firstRun);
604+
LOCK(wallet->cs_wallet);
605+
wallet->AddKeyPubKey(coinbaseKey, coinbaseKey.GetPubKey());
606+
wallet->ScanForWalletTransactions(chainActive.Genesis());
607+
}
608+
609+
~ListCoinsTestingSetup()
610+
{
611+
wallet.reset();
612+
::bitdb.Flush(true);
613+
::bitdb.Reset();
614+
}
615+
616+
CWalletTx& AddTx(CRecipient recipient)
617+
{
618+
CWalletTx wtx;
619+
CReserveKey reservekey(wallet.get());
620+
CAmount fee;
621+
int changePos = -1;
622+
std::string error;
623+
BOOST_CHECK(wallet->CreateTransaction({recipient}, wtx, reservekey, fee, changePos, error));
624+
CValidationState state;
625+
BOOST_CHECK(wallet->CommitTransaction(wtx, reservekey, nullptr, state));
626+
auto it = wallet->mapWallet.find(wtx.GetHash());
627+
BOOST_CHECK(it != wallet->mapWallet.end());
628+
CreateAndProcessBlock({CMutableTransaction(*it->second.tx)}, GetScriptForRawPubKey(coinbaseKey.GetPubKey()));
629+
it->second.SetMerkleBranch(chainActive.Tip(), 1);
630+
return it->second;
631+
}
632+
633+
std::unique_ptr<CWallet> wallet;
634+
};
635+
636+
BOOST_FIXTURE_TEST_CASE(ListCoins, ListCoinsTestingSetup)
637+
{
638+
std::string coinbaseAddress = coinbaseKey.GetPubKey().GetID().ToString();
639+
LOCK(wallet->cs_wallet);
640+
641+
// Confirm ListCoins initially returns 1 coin grouped under coinbaseKey
642+
// address.
643+
auto list = wallet->ListCoins();
644+
BOOST_CHECK_EQUAL(list.size(), 1);
645+
BOOST_CHECK_EQUAL(boost::get<CKeyID>(list.begin()->first).ToString(), coinbaseAddress);
646+
BOOST_CHECK_EQUAL(list.begin()->second.size(), 1);
647+
648+
// Check initial balance from one mature coinbase transaction.
649+
BOOST_CHECK_EQUAL(50 * COIN, wallet->GetAvailableBalance());
650+
651+
// Add a transaction creating a change address, and confirm ListCoins still
652+
// returns the coin associated with the change address underneath the
653+
// coinbaseKey pubkey, even though the change address has a different
654+
// pubkey.
655+
AddTx(CRecipient{GetScriptForRawPubKey({}), 1 * COIN, false /* subtract fee */});
656+
list = wallet->ListCoins();
657+
BOOST_CHECK_EQUAL(list.size(), 1);
658+
BOOST_CHECK_EQUAL(boost::get<CKeyID>(list.begin()->first).ToString(), coinbaseAddress);
659+
BOOST_CHECK_EQUAL(list.begin()->second.size(), 2);
660+
661+
// Lock both coins. Confirm number of available coins drops to 0.
662+
std::vector<COutput> available;
663+
wallet->AvailableCoins(available);
664+
BOOST_CHECK_EQUAL(available.size(), 2);
665+
for (const auto& group : list) {
666+
for (const auto& coin : group.second) {
667+
wallet->LockCoin(COutPoint(coin.tx->GetHash(), coin.i));
668+
}
669+
}
670+
wallet->AvailableCoins(available);
671+
BOOST_CHECK_EQUAL(available.size(), 0);
672+
673+
// Confirm ListCoins still returns same result as before, despite coins
674+
// being locked.
675+
list = wallet->ListCoins();
676+
BOOST_CHECK_EQUAL(list.size(), 1);
677+
BOOST_CHECK_EQUAL(boost::get<CKeyID>(list.begin()->first).ToString(), coinbaseAddress);
678+
BOOST_CHECK_EQUAL(list.begin()->second.size(), 2);
679+
}
680+
580681
BOOST_AUTO_TEST_SUITE_END()

src/wallet/wallet.cpp

Lines changed: 100 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -982,6 +982,13 @@ bool CWallet::AddToWalletIfInvolvingMe(const CTransactionRef& ptx, const CBlockI
982982
return false;
983983
}
984984

985+
bool CWallet::TransactionCanBeAbandoned(const uint256& hashTx) const
986+
{
987+
LOCK2(cs_main, cs_wallet);
988+
const CWalletTx* wtx = GetWalletTx(hashTx);
989+
return wtx && !wtx->isAbandoned() && wtx->GetDepthInMainChain() <= 0 && !wtx->InMempool();
990+
}
991+
985992
bool CWallet::AbandonTransaction(const uint256& hashTx)
986993
{
987994
LOCK2(cs_main, cs_wallet);
@@ -1977,6 +1984,21 @@ CAmount CWallet::GetLegacyBalance(const isminefilter& filter, int minDepth, cons
19771984
return balance;
19781985
}
19791986

1987+
CAmount CWallet::GetAvailableBalance(const CCoinControl* coinControl) const
1988+
{
1989+
LOCK2(cs_main, cs_wallet);
1990+
1991+
CAmount balance = 0;
1992+
std::vector<COutput> vCoins;
1993+
AvailableCoins(vCoins, true, coinControl);
1994+
for (const COutput& out : vCoins) {
1995+
if (out.fSpendable) {
1996+
balance += out.tx->tx->vout[out.i].nValue;
1997+
}
1998+
}
1999+
return balance;
2000+
}
2001+
19802002
void CWallet::AvailableCoins(std::vector<COutput> &vCoins, bool fOnlySafe, const CCoinControl *coinControl, const CAmount &nMinimumAmount, const CAmount &nMaximumAmount, const CAmount &nMinimumSumAmount, const uint64_t &nMaximumCount, const int &nMinDepth, const int &nMaxDepth) const
19812003
{
19822004
vCoins.clear();
@@ -2088,6 +2110,69 @@ void CWallet::AvailableCoins(std::vector<COutput> &vCoins, bool fOnlySafe, const
20882110
}
20892111
}
20902112

2113+
std::map<CTxDestination, std::vector<COutput>> CWallet::ListCoins() const
2114+
{
2115+
// TODO: Add AssertLockHeld(cs_wallet) here.
2116+
//
2117+
// Because the return value from this function contains pointers to
2118+
// CWalletTx objects, callers to this function really should acquire the
2119+
// cs_wallet lock before calling it. However, the current caller doesn't
2120+
// acquire this lock yet. There was an attempt to add the missing lock in
2121+
// https://github.com/bitcoin/bitcoin/pull/10340, but that change has been
2122+
// postponed until after https://github.com/bitcoin/bitcoin/pull/10244 to
2123+
// avoid adding some extra complexity to the Qt code.
2124+
2125+
std::map<CTxDestination, std::vector<COutput>> result;
2126+
2127+
std::vector<COutput> availableCoins;
2128+
AvailableCoins(availableCoins);
2129+
2130+
LOCK2(cs_main, cs_wallet);
2131+
for (auto& coin : availableCoins) {
2132+
CTxDestination address;
2133+
if (coin.fSpendable &&
2134+
ExtractDestination(FindNonChangeParentOutput(*coin.tx->tx, coin.i).scriptPubKey, address)) {
2135+
result[address].emplace_back(std::move(coin));
2136+
}
2137+
}
2138+
2139+
std::vector<COutPoint> lockedCoins;
2140+
ListLockedCoins(lockedCoins);
2141+
for (const auto& output : lockedCoins) {
2142+
auto it = mapWallet.find(output.hash);
2143+
if (it != mapWallet.end()) {
2144+
int depth = it->second.GetDepthInMainChain();
2145+
if (depth >= 0 && output.n < it->second.tx->vout.size() &&
2146+
IsMine(it->second.tx->vout[output.n]) == ISMINE_SPENDABLE) {
2147+
CTxDestination address;
2148+
if (ExtractDestination(FindNonChangeParentOutput(*it->second.tx, output.n).scriptPubKey, address)) {
2149+
result[address].emplace_back(
2150+
&it->second, output.n, depth, true /* spendable */, true /* solvable */, false /* safe */);
2151+
}
2152+
}
2153+
}
2154+
}
2155+
2156+
return result;
2157+
}
2158+
2159+
const CTxOut& CWallet::FindNonChangeParentOutput(const CTransaction& tx, int output) const
2160+
{
2161+
const CTransaction* ptx = &tx;
2162+
int n = output;
2163+
while (IsChange(ptx->vout[n]) && ptx->vin.size() > 0) {
2164+
const COutPoint& prevout = ptx->vin[0].prevout;
2165+
auto it = mapWallet.find(prevout.hash);
2166+
if (it == mapWallet.end() || it->second.tx->vout.size() <= prevout.n ||
2167+
!IsMine(it->second.tx->vout[prevout.n])) {
2168+
break;
2169+
}
2170+
ptx = it->second.tx.get();
2171+
n = prevout.n;
2172+
}
2173+
return ptx->vout[n];
2174+
}
2175+
20912176
static void ApproximateBestSubset(const std::vector<CInputCoin>& vValue, const CAmount& nTotalLower, const CAmount& nTargetValue,
20922177
std::vector<char>& vfBest, CAmount& nBest, int iterations = 1000)
20932178
{
@@ -3407,7 +3492,7 @@ bool CWallet::IsLockedCoin(uint256 hash, unsigned int n) const
34073492
return (setLockedCoins.count(outpt) > 0);
34083493
}
34093494

3410-
void CWallet::ListLockedCoins(std::vector<COutPoint>& vOutpts)
3495+
void CWallet::ListLockedCoins(std::vector<COutPoint>& vOutpts) const
34113496
{
34123497
AssertLockHeld(cs_wallet); // setLockedCoins
34133498
for (std::set<COutPoint>::iterator it = setLockedCoins.begin();
@@ -3608,6 +3693,20 @@ bool CWallet::GetDestData(const CTxDestination &dest, const std::string &key, st
36083693
return false;
36093694
}
36103695

3696+
std::vector<std::string> CWallet::GetDestValues(const std::string& prefix) const
3697+
{
3698+
LOCK(cs_wallet);
3699+
std::vector<std::string> values;
3700+
for (const auto& address : mapAddressBook) {
3701+
for (const auto& data : address.second.destdata) {
3702+
if (!data.first.compare(0, prefix.size(), prefix)) {
3703+
values.emplace_back(data.second);
3704+
}
3705+
}
3706+
}
3707+
return values;
3708+
}
3709+
36113710
std::string CWallet::GetWalletHelpString(bool showDebug)
36123711
{
36133712
std::string strUsage = HelpMessageGroup(_("Wallet options:"));

0 commit comments

Comments
 (0)