aboutsummaryrefslogtreecommitdiff
path: root/src/wallet
diff options
context:
space:
mode:
Diffstat (limited to 'src/wallet')
-rw-r--r--src/wallet/test/wallet_tests.cpp101
-rw-r--r--src/wallet/wallet.cpp101
-rw-r--r--src/wallet/wallet.h18
3 files changed, 218 insertions, 2 deletions
diff --git a/src/wallet/test/wallet_tests.cpp b/src/wallet/test/wallet_tests.cpp
index b077d152d9..5c7359fdce 100644
--- a/src/wallet/test/wallet_tests.cpp
+++ b/src/wallet/test/wallet_tests.cpp
@@ -9,6 +9,7 @@
#include <utility>
#include <vector>
+#include "consensus/validation.h"
#include "rpc/server.h"
#include "test/test_bitcoin.h"
#include "validation.h"
@@ -577,4 +578,104 @@ BOOST_AUTO_TEST_CASE(ComputeTimeSmart)
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()
diff --git a/src/wallet/wallet.cpp b/src/wallet/wallet.cpp
index 3c3f6b1a23..997515a04b 100644
--- a/src/wallet/wallet.cpp
+++ b/src/wallet/wallet.cpp
@@ -982,6 +982,13 @@ bool CWallet::AddToWalletIfInvolvingMe(const CTransactionRef& ptx, const CBlockI
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)
{
LOCK2(cs_main, cs_wallet);
@@ -1977,6 +1984,21 @@ CAmount CWallet::GetLegacyBalance(const isminefilter& filter, int minDepth, cons
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
{
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,
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);
}
-void CWallet::ListLockedCoins(std::vector<COutPoint>& vOutpts)
+void CWallet::ListLockedCoins(std::vector<COutPoint>& vOutpts) const
{
AssertLockHeld(cs_wallet); // setLockedCoins
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;
}
+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 strUsage = HelpMessageGroup(_("Wallet options:"));
diff --git a/src/wallet/wallet.h b/src/wallet/wallet.h
index 69f51b3f64..11b2f7a663 100644
--- a/src/wallet/wallet.h
+++ b/src/wallet/wallet.h
@@ -821,6 +821,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;
/**
+ * 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
* small change; This method is stochastic for some inputs and upon
* completion the coin set and corresponding actual target value is
@@ -834,7 +844,7 @@ public:
void LockCoin(const COutPoint& output);
void UnlockCoin(const COutPoint& output);
void UnlockAllCoins();
- void ListLockedCoins(std::vector<COutPoint>& vOutpts);
+ void ListLockedCoins(std::vector<COutPoint>& vOutpts) const;
/*
* Rescan abort properties
@@ -873,6 +883,8 @@ public:
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
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.
bool AddWatchOnly(const CScript& dest, int64_t nCreateTime);
@@ -917,6 +929,7 @@ public:
CAmount GetUnconfirmedWatchOnlyBalance() const;
CAmount GetImmatureWatchOnlyBalance() 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
@@ -1066,6 +1079,9 @@ public:
/** Set whether this wallet broadcasts transactions. */
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. */
bool AbandonTransaction(const uint256& hashTx);