Browse Source

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
0.15
Wladimir J. van der Laan 8 years ago
parent
commit
ce8176d038
No known key found for this signature in database
GPG Key ID: 1E4AED62986CD25D
  1. 3
      src/Makefile.qttest.include
  2. 10
      src/qt/test/wallettests.cpp
  3. 56
      src/qt/walletmodel.cpp
  4. 101
      src/wallet/test/wallet_tests.cpp
  5. 101
      src/wallet/wallet.cpp
  6. 18
      src/wallet/wallet.h

3
src/Makefile.qttest.include

@ -46,7 +46,8 @@ qt_test_test_bitcoin_qt_SOURCES = \
if ENABLE_WALLET if ENABLE_WALLET
qt_test_test_bitcoin_qt_SOURCES += \ qt_test_test_bitcoin_qt_SOURCES += \
qt/test/paymentservertests.cpp \ qt/test/paymentservertests.cpp \
qt/test/wallettests.cpp qt/test/wallettests.cpp \
wallet/test/wallet_test_fixture.cpp
endif endif
nodist_qt_test_test_bitcoin_qt_SOURCES = $(TEST_QT_MOC_CPP) nodist_qt_test_test_bitcoin_qt_SOURCES = $(TEST_QT_MOC_CPP)

10
src/qt/test/wallettests.cpp

@ -65,7 +65,6 @@ QModelIndex FindTx(const QAbstractItemModel& model, const uint256& txid)
} }
return {}; return {};
} }
}
//! Simple qt wallet tests. //! Simple qt wallet tests.
// //
@ -80,7 +79,7 @@ QModelIndex FindTx(const QAbstractItemModel& model, const uint256& txid)
// src/qt/test/test_bitcoin-qt -platform xcb # Linux // src/qt/test/test_bitcoin-qt -platform xcb # Linux
// src/qt/test/test_bitcoin-qt -platform windows # Windows // src/qt/test/test_bitcoin-qt -platform windows # Windows
// src/qt/test/test_bitcoin-qt -platform cocoa # macOS // src/qt/test/test_bitcoin-qt -platform cocoa # macOS
void WalletTests::walletTests() void TestSendCoins()
{ {
// Set up wallet and chain with 101 blocks (1 mature block for spending). // Set up wallet and chain with 101 blocks (1 mature block for spending).
TestChain100Setup test; TestChain100Setup test;
@ -117,3 +116,10 @@ void WalletTests::walletTests()
bitdb.Flush(true); bitdb.Flush(true);
bitdb.Reset(); bitdb.Reset();
} }
}
void WalletTests::walletTests()
{
TestSendCoins();
}

56
src/qt/walletmodel.cpp

@ -68,14 +68,7 @@ CAmount WalletModel::getBalance(const CCoinControl *coinControl) const
{ {
if (coinControl) if (coinControl)
{ {
CAmount nBalance = 0; return wallet->GetAvailableBalance(coinControl);
std::vector<COutput> vCoins;
wallet->AvailableCoins(vCoins, true, coinControl);
BOOST_FOREACH(const COutput& out, vCoins)
if(out.fSpendable)
nBalance += out.tx->tx->vout[out.i].nValue;
return nBalance;
} }
return wallet->GetBalance(); return wallet->GetBalance();
@ -600,38 +593,11 @@ bool WalletModel::isSpent(const COutPoint& outpoint) const
// AvailableCoins + LockedCoins grouped by wallet address (put change in one group with wallet address) // AvailableCoins + LockedCoins grouped by wallet address (put change in one group with wallet address)
void WalletModel::listCoins(std::map<QString, std::vector<COutput> >& mapCoins) const void WalletModel::listCoins(std::map<QString, std::vector<COutput> >& mapCoins) const
{ {
std::vector<COutput> vCoins; for (auto& group : wallet->ListCoins()) {
wallet->AvailableCoins(vCoins); auto& resultGroup = mapCoins[QString::fromStdString(CBitcoinAddress(group.first).ToString())];
for (auto& coin : group.second) {
LOCK2(cs_main, wallet->cs_wallet); // ListLockedCoins, mapWallet resultGroup.emplace_back(std::move(coin));
std::vector<COutPoint> vLockedCoins;
wallet->ListLockedCoins(vLockedCoins);
// add locked coins
BOOST_FOREACH(const COutPoint& outpoint, vLockedCoins)
{
if (!wallet->mapWallet.count(outpoint.hash)) continue;
int nDepth = wallet->mapWallet[outpoint.hash].GetDepthInMainChain();
if (nDepth < 0) continue;
COutput out(&wallet->mapWallet[outpoint.hash], outpoint.n, nDepth, true /* spendable */, true /* solvable */, true /* safe */);
if (outpoint.n < out.tx->tx->vout.size() && wallet->IsMine(out.tx->tx->vout[outpoint.n]) == ISMINE_SPENDABLE)
vCoins.push_back(out);
} }
BOOST_FOREACH(const COutput& out, vCoins)
{
COutput cout = out;
while (wallet->IsChange(cout.tx->tx->vout[cout.i]) && cout.tx->tx->vin.size() > 0 && wallet->IsMine(cout.tx->tx->vin[0]))
{
if (!wallet->mapWallet.count(cout.tx->tx->vin[0].prevout.hash)) break;
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 */);
}
CTxDestination address;
if(!out.fSpendable || !ExtractDestination(cout.tx->tx->vout[cout.i].scriptPubKey, address))
continue;
mapCoins[QString::fromStdString(CBitcoinAddress(address).ToString())].push_back(out);
} }
} }
@ -661,11 +627,7 @@ void WalletModel::listLockedCoins(std::vector<COutPoint>& vOutpts)
void WalletModel::loadReceiveRequests(std::vector<std::string>& vReceiveRequests) void WalletModel::loadReceiveRequests(std::vector<std::string>& vReceiveRequests)
{ {
LOCK(wallet->cs_wallet); vReceiveRequests = wallet->GetDestValues("rr"); // receive request
BOOST_FOREACH(const PAIRTYPE(CTxDestination, CAddressBookData)& item, wallet->mapAddressBook)
BOOST_FOREACH(const PAIRTYPE(std::string, std::string)& item2, item.second.destdata)
if (item2.first.size() > 2 && item2.first.substr(0,2) == "rr") // receive request
vReceiveRequests.push_back(item2.second);
} }
bool WalletModel::saveReceiveRequest(const std::string &sAddress, const int64_t nId, const std::string &sRequest) 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
bool WalletModel::transactionCanBeAbandoned(uint256 hash) const bool WalletModel::transactionCanBeAbandoned(uint256 hash) const
{ {
LOCK2(cs_main, wallet->cs_wallet); return wallet->TransactionCanBeAbandoned(hash);
const CWalletTx *wtx = wallet->GetWalletTx(hash);
if (!wtx || wtx->isAbandoned() || wtx->GetDepthInMainChain() > 0 || wtx->InMempool())
return false;
return true;
} }
bool WalletModel::abandonTransaction(uint256 hash) const bool WalletModel::abandonTransaction(uint256 hash) const

101
src/wallet/test/wallet_tests.cpp

@ -9,6 +9,7 @@
#include <utility> #include <utility>
#include <vector> #include <vector>
#include "consensus/validation.h"
#include "rpc/server.h" #include "rpc/server.h"
#include "test/test_bitcoin.h" #include "test/test_bitcoin.h"
#include "validation.h" #include "validation.h"
@ -577,4 +578,104 @@ BOOST_AUTO_TEST_CASE(ComputeTimeSmart)
SetMockTime(0); SetMockTime(0);
} }
BOOST_AUTO_TEST_CASE(LoadReceiveRequests)
{
CTxDestination dest = CKeyID();
pwalletMain->AddDestData(dest, "misc", "val_misc");
pwalletMain->AddDestData(dest, "rr0", "val_rr0");
pwalletMain->AddDestData(dest, "rr1", "val_rr1");
auto values = pwalletMain->GetDestValues("rr");
BOOST_CHECK_EQUAL(values.size(), 2);
BOOST_CHECK_EQUAL(values[0], "val_rr0");
BOOST_CHECK_EQUAL(values[1], "val_rr1");
}
class ListCoinsTestingSetup : public TestChain100Setup
{
public:
ListCoinsTestingSetup()
{
CreateAndProcessBlock({}, GetScriptForRawPubKey(coinbaseKey.GetPubKey()));
::bitdb.MakeMock();
wallet.reset(new CWallet(std::unique_ptr<CWalletDBWrapper>(new CWalletDBWrapper(&bitdb, "wallet_test.dat"))));
bool firstRun;
wallet->LoadWallet(firstRun);
LOCK(wallet->cs_wallet);
wallet->AddKeyPubKey(coinbaseKey, coinbaseKey.GetPubKey());
wallet->ScanForWalletTransactions(chainActive.Genesis());
}
~ListCoinsTestingSetup()
{
wallet.reset();
::bitdb.Flush(true);
::bitdb.Reset();
}
CWalletTx& AddTx(CRecipient recipient)
{
CWalletTx wtx;
CReserveKey reservekey(wallet.get());
CAmount fee;
int changePos = -1;
std::string error;
BOOST_CHECK(wallet->CreateTransaction({recipient}, wtx, reservekey, fee, changePos, error));
CValidationState state;
BOOST_CHECK(wallet->CommitTransaction(wtx, reservekey, nullptr, state));
auto it = wallet->mapWallet.find(wtx.GetHash());
BOOST_CHECK(it != wallet->mapWallet.end());
CreateAndProcessBlock({CMutableTransaction(*it->second.tx)}, GetScriptForRawPubKey(coinbaseKey.GetPubKey()));
it->second.SetMerkleBranch(chainActive.Tip(), 1);
return it->second;
}
std::unique_ptr<CWallet> wallet;
};
BOOST_FIXTURE_TEST_CASE(ListCoins, ListCoinsTestingSetup)
{
std::string coinbaseAddress = coinbaseKey.GetPubKey().GetID().ToString();
LOCK(wallet->cs_wallet);
// Confirm ListCoins initially returns 1 coin grouped under coinbaseKey
// address.
auto list = wallet->ListCoins();
BOOST_CHECK_EQUAL(list.size(), 1);
BOOST_CHECK_EQUAL(boost::get<CKeyID>(list.begin()->first).ToString(), coinbaseAddress);
BOOST_CHECK_EQUAL(list.begin()->second.size(), 1);
// Check initial balance from one mature coinbase transaction.
BOOST_CHECK_EQUAL(50 * COIN, wallet->GetAvailableBalance());
// Add a transaction creating a change address, and confirm ListCoins still
// returns the coin associated with the change address underneath the
// coinbaseKey pubkey, even though the change address has a different
// pubkey.
AddTx(CRecipient{GetScriptForRawPubKey({}), 1 * COIN, false /* subtract fee */});
list = wallet->ListCoins();
BOOST_CHECK_EQUAL(list.size(), 1);
BOOST_CHECK_EQUAL(boost::get<CKeyID>(list.begin()->first).ToString(), coinbaseAddress);
BOOST_CHECK_EQUAL(list.begin()->second.size(), 2);
// Lock both coins. Confirm number of available coins drops to 0.
std::vector<COutput> available;
wallet->AvailableCoins(available);
BOOST_CHECK_EQUAL(available.size(), 2);
for (const auto& group : list) {
for (const auto& coin : group.second) {
wallet->LockCoin(COutPoint(coin.tx->GetHash(), coin.i));
}
}
wallet->AvailableCoins(available);
BOOST_CHECK_EQUAL(available.size(), 0);
// Confirm ListCoins still returns same result as before, despite coins
// being locked.
list = wallet->ListCoins();
BOOST_CHECK_EQUAL(list.size(), 1);
BOOST_CHECK_EQUAL(boost::get<CKeyID>(list.begin()->first).ToString(), coinbaseAddress);
BOOST_CHECK_EQUAL(list.begin()->second.size(), 2);
}
BOOST_AUTO_TEST_SUITE_END() BOOST_AUTO_TEST_SUITE_END()

101
src/wallet/wallet.cpp

@ -982,6 +982,13 @@ bool CWallet::AddToWalletIfInvolvingMe(const CTransactionRef& ptx, const CBlockI
return false; return false;
} }
bool CWallet::TransactionCanBeAbandoned(const uint256& hashTx) const
{
LOCK2(cs_main, cs_wallet);
const CWalletTx* wtx = GetWalletTx(hashTx);
return wtx && !wtx->isAbandoned() && wtx->GetDepthInMainChain() <= 0 && !wtx->InMempool();
}
bool CWallet::AbandonTransaction(const uint256& hashTx) bool CWallet::AbandonTransaction(const uint256& hashTx)
{ {
LOCK2(cs_main, cs_wallet); LOCK2(cs_main, cs_wallet);
@ -1977,6 +1984,21 @@ CAmount CWallet::GetLegacyBalance(const isminefilter& filter, int minDepth, cons
return balance; return balance;
} }
CAmount CWallet::GetAvailableBalance(const CCoinControl* coinControl) const
{
LOCK2(cs_main, cs_wallet);
CAmount balance = 0;
std::vector<COutput> vCoins;
AvailableCoins(vCoins, true, coinControl);
for (const COutput& out : vCoins) {
if (out.fSpendable) {
balance += out.tx->tx->vout[out.i].nValue;
}
}
return balance;
}
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 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
{ {
vCoins.clear(); vCoins.clear();
@ -2088,6 +2110,69 @@ void CWallet::AvailableCoins(std::vector<COutput> &vCoins, bool fOnlySafe, const
} }
} }
std::map<CTxDestination, std::vector<COutput>> CWallet::ListCoins() const
{
// TODO: Add AssertLockHeld(cs_wallet) here.
//
// Because the return value from this function contains pointers to
// CWalletTx objects, callers to this function really should acquire the
// cs_wallet lock before calling it. However, the current caller doesn't
// acquire this lock yet. There was an attempt to add the missing lock in
// https://github.com/bitcoin/bitcoin/pull/10340, but that change has been
// postponed until after https://github.com/bitcoin/bitcoin/pull/10244 to
// avoid adding some extra complexity to the Qt code.
std::map<CTxDestination, std::vector<COutput>> result;
std::vector<COutput> availableCoins;
AvailableCoins(availableCoins);
LOCK2(cs_main, cs_wallet);
for (auto& coin : availableCoins) {
CTxDestination address;
if (coin.fSpendable &&
ExtractDestination(FindNonChangeParentOutput(*coin.tx->tx, coin.i).scriptPubKey, address)) {
result[address].emplace_back(std::move(coin));
}
}
std::vector<COutPoint> lockedCoins;
ListLockedCoins(lockedCoins);
for (const auto& output : lockedCoins) {
auto it = mapWallet.find(output.hash);
if (it != mapWallet.end()) {
int depth = it->second.GetDepthInMainChain();
if (depth >= 0 && output.n < it->second.tx->vout.size() &&
IsMine(it->second.tx->vout[output.n]) == ISMINE_SPENDABLE) {
CTxDestination address;
if (ExtractDestination(FindNonChangeParentOutput(*it->second.tx, output.n).scriptPubKey, address)) {
result[address].emplace_back(
&it->second, output.n, depth, true /* spendable */, true /* solvable */, false /* safe */);
}
}
}
}
return result;
}
const CTxOut& CWallet::FindNonChangeParentOutput(const CTransaction& tx, int output) const
{
const CTransaction* ptx = &tx;
int n = output;
while (IsChange(ptx->vout[n]) && ptx->vin.size() > 0) {
const COutPoint& prevout = ptx->vin[0].prevout;
auto it = mapWallet.find(prevout.hash);
if (it == mapWallet.end() || it->second.tx->vout.size() <= prevout.n ||
!IsMine(it->second.tx->vout[prevout.n])) {
break;
}
ptx = it->second.tx.get();
n = prevout.n;
}
return ptx->vout[n];
}
static void ApproximateBestSubset(const std::vector<CInputCoin>& vValue, const CAmount& nTotalLower, const CAmount& nTargetValue, static void ApproximateBestSubset(const std::vector<CInputCoin>& vValue, const CAmount& nTotalLower, const CAmount& nTargetValue,
std::vector<char>& vfBest, CAmount& nBest, int iterations = 1000) std::vector<char>& vfBest, CAmount& nBest, int iterations = 1000)
{ {
@ -3407,7 +3492,7 @@ bool CWallet::IsLockedCoin(uint256 hash, unsigned int n) const
return (setLockedCoins.count(outpt) > 0); return (setLockedCoins.count(outpt) > 0);
} }
void CWallet::ListLockedCoins(std::vector<COutPoint>& vOutpts) void CWallet::ListLockedCoins(std::vector<COutPoint>& vOutpts) const
{ {
AssertLockHeld(cs_wallet); // setLockedCoins AssertLockHeld(cs_wallet); // setLockedCoins
for (std::set<COutPoint>::iterator it = setLockedCoins.begin(); for (std::set<COutPoint>::iterator it = setLockedCoins.begin();
@ -3608,6 +3693,20 @@ bool CWallet::GetDestData(const CTxDestination &dest, const std::string &key, st
return false; return false;
} }
std::vector<std::string> CWallet::GetDestValues(const std::string& prefix) const
{
LOCK(cs_wallet);
std::vector<std::string> values;
for (const auto& address : mapAddressBook) {
for (const auto& data : address.second.destdata) {
if (!data.first.compare(0, prefix.size(), prefix)) {
values.emplace_back(data.second);
}
}
}
return values;
}
std::string CWallet::GetWalletHelpString(bool showDebug) std::string CWallet::GetWalletHelpString(bool showDebug)
{ {
std::string strUsage = HelpMessageGroup(_("Wallet options:")); std::string strUsage = HelpMessageGroup(_("Wallet options:"));

18
src/wallet/wallet.h

@ -820,6 +820,16 @@ public:
*/ */
void AvailableCoins(std::vector<COutput>& vCoins, bool fOnlySafe=true, const CCoinControl *coinControl = NULL, const CAmount& nMinimumAmount = 1, const CAmount& nMaximumAmount = MAX_MONEY, const CAmount& nMinimumSumAmount = MAX_MONEY, const uint64_t& nMaximumCount = 0, const int& nMinDepth = 0, const int& nMaxDepth = 9999999) const; void AvailableCoins(std::vector<COutput>& vCoins, bool fOnlySafe=true, const CCoinControl *coinControl = NULL, const CAmount& nMinimumAmount = 1, const CAmount& nMaximumAmount = MAX_MONEY, const CAmount& nMinimumSumAmount = MAX_MONEY, const uint64_t& nMaximumCount = 0, const int& nMinDepth = 0, const int& nMaxDepth = 9999999) const;
/**
* Return list of available coins and locked coins grouped by non-change output address.
*/
std::map<CTxDestination, std::vector<COutput>> ListCoins() const;
/**
* Find non-change parent output.
*/
const CTxOut& FindNonChangeParentOutput(const CTransaction& tx, int output) const;
/** /**
* Shuffle and select coins until nTargetValue is reached while avoiding * Shuffle and select coins until nTargetValue is reached while avoiding
* small change; This method is stochastic for some inputs and upon * small change; This method is stochastic for some inputs and upon
@ -834,7 +844,7 @@ public:
void LockCoin(const COutPoint& output); void LockCoin(const COutPoint& output);
void UnlockCoin(const COutPoint& output); void UnlockCoin(const COutPoint& output);
void UnlockAllCoins(); void UnlockAllCoins();
void ListLockedCoins(std::vector<COutPoint>& vOutpts); void ListLockedCoins(std::vector<COutPoint>& vOutpts) const;
/* /*
* Rescan abort properties * Rescan abort properties
@ -873,6 +883,8 @@ public:
bool LoadDestData(const CTxDestination &dest, const std::string &key, const std::string &value); bool LoadDestData(const CTxDestination &dest, const std::string &key, const std::string &value);
//! Look up a destination data tuple in the store, return true if found false otherwise //! Look up a destination data tuple in the store, return true if found false otherwise
bool GetDestData(const CTxDestination &dest, const std::string &key, std::string *value) const; bool GetDestData(const CTxDestination &dest, const std::string &key, std::string *value) const;
//! Get all destination values matching a prefix.
std::vector<std::string> GetDestValues(const std::string& prefix) const;
//! Adds a watch-only address to the store, and saves it to disk. //! Adds a watch-only address to the store, and saves it to disk.
bool AddWatchOnly(const CScript& dest, int64_t nCreateTime); bool AddWatchOnly(const CScript& dest, int64_t nCreateTime);
@ -917,6 +929,7 @@ public:
CAmount GetUnconfirmedWatchOnlyBalance() const; CAmount GetUnconfirmedWatchOnlyBalance() const;
CAmount GetImmatureWatchOnlyBalance() const; CAmount GetImmatureWatchOnlyBalance() const;
CAmount GetLegacyBalance(const isminefilter& filter, int minDepth, const std::string* account) const; CAmount GetLegacyBalance(const isminefilter& filter, int minDepth, const std::string* account) const;
CAmount GetAvailableBalance(const CCoinControl* coinControl = nullptr) const;
/** /**
* Insert additional inputs into the transaction by * Insert additional inputs into the transaction by
@ -1066,6 +1079,9 @@ public:
/** Set whether this wallet broadcasts transactions. */ /** Set whether this wallet broadcasts transactions. */
void SetBroadcastTransactions(bool broadcast) { fBroadcastTransactions = broadcast; } void SetBroadcastTransactions(bool broadcast) { fBroadcastTransactions = broadcast; }
/** Return whether transaction can be abandoned */
bool TransactionCanBeAbandoned(const uint256& hashTx) const;
/* Mark a transaction (and it in-wallet descendants) as abandoned so its inputs may be respent. */ /* Mark a transaction (and it in-wallet descendants) as abandoned so its inputs may be respent. */
bool AbandonTransaction(const uint256& hashTx); bool AbandonTransaction(const uint256& hashTx);

Loading…
Cancel
Save