aboutsummaryrefslogtreecommitdiff
path: root/src/wallet
diff options
context:
space:
mode:
Diffstat (limited to 'src/wallet')
-rw-r--r--src/wallet/coincontrol.cpp23
-rw-r--r--src/wallet/coincontrol.h81
-rw-r--r--src/wallet/coinselection.cpp326
-rw-r--r--src/wallet/coinselection.h101
-rw-r--r--src/wallet/crypter.cpp181
-rw-r--r--src/wallet/crypter.h99
-rw-r--r--src/wallet/db.cpp625
-rw-r--r--src/wallet/db.h237
-rw-r--r--src/wallet/feebumper.cpp269
-rw-r--r--src/wallet/feebumper.h58
-rw-r--r--src/wallet/fees.cpp102
-rw-r--r--src/wallet/fees.h47
-rw-r--r--src/wallet/init.cpp270
-rw-r--r--src/wallet/rpcdump.cpp1248
-rw-r--r--src/wallet/rpcwallet.cpp4355
-rw-r--r--src/wallet/rpcwallet.h26
-rw-r--r--src/wallet/test/accounting_tests.cpp73
-rw-r--r--src/wallet/test/coinselector_tests.cpp584
-rw-r--r--src/wallet/test/crypto_tests.cpp230
-rw-r--r--src/wallet/test/psbt_wallet_tests.cpp150
-rw-r--r--src/wallet/test/wallet_crypto_tests.cpp126
-rw-r--r--src/wallet/test/wallet_test_fixture.cpp25
-rw-r--r--src/wallet/test/wallet_test_fixture.h19
-rw-r--r--src/wallet/test/wallet_tests.cpp641
-rw-r--r--src/wallet/wallet.cpp3929
-rw-r--r--src/wallet/wallet.h992
-rw-r--r--src/wallet/walletdb.cpp721
-rw-r--r--src/wallet/walletdb.h150
-rw-r--r--src/wallet/walletutil.cpp27
-rw-r--r--src/wallet/walletutil.h14
30 files changed, 11048 insertions, 4681 deletions
diff --git a/src/wallet/coincontrol.cpp b/src/wallet/coincontrol.cpp
new file mode 100644
index 0000000000..645981faa4
--- /dev/null
+++ b/src/wallet/coincontrol.cpp
@@ -0,0 +1,23 @@
+// Copyright (c) 2018 The Bitcoin Core developers
+// Distributed under the MIT software license, see the accompanying
+// file COPYING or http://www.opensource.org/licenses/mit-license.php.
+
+#include <wallet/coincontrol.h>
+
+#include <util.h>
+
+void CCoinControl::SetNull()
+{
+ destChange = CNoDestination();
+ m_change_type.reset();
+ fAllowOtherInputs = false;
+ fAllowWatchOnly = false;
+ m_avoid_partial_spends = gArgs.GetBoolArg("-avoidpartialspends", DEFAULT_AVOIDPARTIALSPENDS);
+ setSelected.clear();
+ m_feerate.reset();
+ fOverrideFeeRate = false;
+ m_confirm_target.reset();
+ m_signal_bip125_rbf.reset();
+ m_fee_mode = FeeEstimateMode::UNSET;
+}
+
diff --git a/src/wallet/coincontrol.h b/src/wallet/coincontrol.h
new file mode 100644
index 0000000000..48a924abfb
--- /dev/null
+++ b/src/wallet/coincontrol.h
@@ -0,0 +1,81 @@
+// Copyright (c) 2011-2018 The Bitcoin Core developers
+// Distributed under the MIT software license, see the accompanying
+// file COPYING or http://www.opensource.org/licenses/mit-license.php.
+
+#ifndef BITCOIN_WALLET_COINCONTROL_H
+#define BITCOIN_WALLET_COINCONTROL_H
+
+#include <policy/feerate.h>
+#include <policy/fees.h>
+#include <primitives/transaction.h>
+#include <wallet/wallet.h>
+
+#include <boost/optional.hpp>
+
+/** Coin Control Features. */
+class CCoinControl
+{
+public:
+ //! Custom change destination, if not set an address is generated
+ CTxDestination destChange;
+ //! Override the default change type if set, ignored if destChange is set
+ boost::optional<OutputType> m_change_type;
+ //! If false, allows unselected inputs, but requires all selected inputs be used
+ bool fAllowOtherInputs;
+ //! Includes watch only addresses which are solvable
+ bool fAllowWatchOnly;
+ //! Override automatic min/max checks on fee, m_feerate must be set if true
+ bool fOverrideFeeRate;
+ //! Override the wallet's m_pay_tx_fee if set
+ boost::optional<CFeeRate> m_feerate;
+ //! Override the default confirmation target if set
+ boost::optional<unsigned int> m_confirm_target;
+ //! Override the wallet's m_signal_rbf if set
+ boost::optional<bool> m_signal_bip125_rbf;
+ //! Avoid partial use of funds sent to a given address
+ bool m_avoid_partial_spends;
+ //! Fee estimation mode to control arguments to estimateSmartFee
+ FeeEstimateMode m_fee_mode;
+
+ CCoinControl()
+ {
+ SetNull();
+ }
+
+ void SetNull();
+
+ bool HasSelected() const
+ {
+ return (setSelected.size() > 0);
+ }
+
+ bool IsSelected(const COutPoint& output) const
+ {
+ return (setSelected.count(output) > 0);
+ }
+
+ void Select(const COutPoint& output)
+ {
+ setSelected.insert(output);
+ }
+
+ void UnSelect(const COutPoint& output)
+ {
+ setSelected.erase(output);
+ }
+
+ void UnSelectAll()
+ {
+ setSelected.clear();
+ }
+
+ void ListSelected(std::vector<COutPoint>& vOutpoints) const
+ {
+ vOutpoints.assign(setSelected.begin(), setSelected.end());
+ }
+
+private:
+ std::set<COutPoint> setSelected;
+};
+
+#endif // BITCOIN_WALLET_COINCONTROL_H
diff --git a/src/wallet/coinselection.cpp b/src/wallet/coinselection.cpp
new file mode 100644
index 0000000000..3734ba66b6
--- /dev/null
+++ b/src/wallet/coinselection.cpp
@@ -0,0 +1,326 @@
+// Copyright (c) 2017-2018 The Bitcoin Core developers
+// Distributed under the MIT software license, see the accompanying
+// file COPYING or http://www.opensource.org/licenses/mit-license.php.
+
+#include <wallet/coinselection.h>
+#include <util.h>
+#include <utilmoneystr.h>
+
+// Descending order comparator
+struct {
+ bool operator()(const OutputGroup& a, const OutputGroup& b) const
+ {
+ return a.effective_value > b.effective_value;
+ }
+} descending;
+
+/*
+ * This is the Branch and Bound Coin Selection algorithm designed by Murch. It searches for an input
+ * set that can pay for the spending target and does not exceed the spending target by more than the
+ * cost of creating and spending a change output. The algorithm uses a depth-first search on a binary
+ * tree. In the binary tree, each node corresponds to the inclusion or the omission of a UTXO. UTXOs
+ * are sorted by their effective values and the trees is explored deterministically per the inclusion
+ * branch first. At each node, the algorithm checks whether the selection is within the target range.
+ * While the selection has not reached the target range, more UTXOs are included. When a selection's
+ * value exceeds the target range, the complete subtree deriving from this selection can be omitted.
+ * At that point, the last included UTXO is deselected and the corresponding omission branch explored
+ * instead. The search ends after the complete tree has been searched or after a limited number of tries.
+ *
+ * The search continues to search for better solutions after one solution has been found. The best
+ * solution is chosen by minimizing the waste metric. The waste metric is defined as the cost to
+ * spend the current inputs at the given fee rate minus the long term expected cost to spend the
+ * inputs, plus the amount the selection exceeds the spending target:
+ *
+ * waste = selectionTotal - target + inputs × (currentFeeRate - longTermFeeRate)
+ *
+ * The algorithm uses two additional optimizations. A lookahead keeps track of the total value of
+ * the unexplored UTXOs. A subtree is not explored if the lookahead indicates that the target range
+ * cannot be reached. Further, it is unnecessary to test equivalent combinations. This allows us
+ * to skip testing the inclusion of UTXOs that match the effective value and waste of an omitted
+ * predecessor.
+ *
+ * The Branch and Bound algorithm is described in detail in Murch's Master Thesis:
+ * https://murch.one/wp-content/uploads/2016/11/erhardt2016coinselection.pdf
+ *
+ * @param const std::vector<CInputCoin>& utxo_pool The set of UTXOs that we are choosing from.
+ * These UTXOs will be sorted in descending order by effective value and the CInputCoins'
+ * values are their effective values.
+ * @param const CAmount& target_value This is the value that we want to select. It is the lower
+ * bound of the range.
+ * @param const CAmount& cost_of_change This is the cost of creating and spending a change output.
+ * This plus target_value is the upper bound of the range.
+ * @param std::set<CInputCoin>& out_set -> This is an output parameter for the set of CInputCoins
+ * that have been selected.
+ * @param CAmount& value_ret -> This is an output parameter for the total value of the CInputCoins
+ * that were selected.
+ * @param CAmount not_input_fees -> The fees that need to be paid for the outputs and fixed size
+ * overhead (version, locktime, marker and flag)
+ */
+
+static const size_t TOTAL_TRIES = 100000;
+
+bool SelectCoinsBnB(std::vector<OutputGroup>& utxo_pool, const CAmount& target_value, const CAmount& cost_of_change, std::set<CInputCoin>& out_set, CAmount& value_ret, CAmount not_input_fees)
+{
+ out_set.clear();
+ CAmount curr_value = 0;
+
+ std::vector<bool> curr_selection; // select the utxo at this index
+ curr_selection.reserve(utxo_pool.size());
+ CAmount actual_target = not_input_fees + target_value;
+
+ // Calculate curr_available_value
+ CAmount curr_available_value = 0;
+ for (const OutputGroup& utxo : utxo_pool) {
+ // Assert that this utxo is not negative. It should never be negative, effective value calculation should have removed it
+ assert(utxo.effective_value > 0);
+ curr_available_value += utxo.effective_value;
+ }
+ if (curr_available_value < actual_target) {
+ return false;
+ }
+
+ // Sort the utxo_pool
+ std::sort(utxo_pool.begin(), utxo_pool.end(), descending);
+
+ CAmount curr_waste = 0;
+ std::vector<bool> best_selection;
+ CAmount best_waste = MAX_MONEY;
+
+ // Depth First search loop for choosing the UTXOs
+ for (size_t i = 0; i < TOTAL_TRIES; ++i) {
+ // Conditions for starting a backtrack
+ bool backtrack = false;
+ if (curr_value + curr_available_value < actual_target || // Cannot possibly reach target with the amount remaining in the curr_available_value.
+ curr_value > actual_target + cost_of_change || // Selected value is out of range, go back and try other branch
+ (curr_waste > best_waste && (utxo_pool.at(0).fee - utxo_pool.at(0).long_term_fee) > 0)) { // Don't select things which we know will be more wasteful if the waste is increasing
+ backtrack = true;
+ } else if (curr_value >= actual_target) { // Selected value is within range
+ curr_waste += (curr_value - actual_target); // This is the excess value which is added to the waste for the below comparison
+ // Adding another UTXO after this check could bring the waste down if the long term fee is higher than the current fee.
+ // However we are not going to explore that because this optimization for the waste is only done when we have hit our target
+ // value. Adding any more UTXOs will be just burning the UTXO; it will go entirely to fees. Thus we aren't going to
+ // explore any more UTXOs to avoid burning money like that.
+ if (curr_waste <= best_waste) {
+ best_selection = curr_selection;
+ best_selection.resize(utxo_pool.size());
+ best_waste = curr_waste;
+ }
+ curr_waste -= (curr_value - actual_target); // Remove the excess value as we will be selecting different coins now
+ backtrack = true;
+ }
+
+ // Backtracking, moving backwards
+ if (backtrack) {
+ // Walk backwards to find the last included UTXO that still needs to have its omission branch traversed.
+ while (!curr_selection.empty() && !curr_selection.back()) {
+ curr_selection.pop_back();
+ curr_available_value += utxo_pool.at(curr_selection.size()).effective_value;
+ }
+
+ if (curr_selection.empty()) { // We have walked back to the first utxo and no branch is untraversed. All solutions searched
+ break;
+ }
+
+ // Output was included on previous iterations, try excluding now.
+ curr_selection.back() = false;
+ OutputGroup& utxo = utxo_pool.at(curr_selection.size() - 1);
+ curr_value -= utxo.effective_value;
+ curr_waste -= utxo.fee - utxo.long_term_fee;
+ } else { // Moving forwards, continuing down this branch
+ OutputGroup& utxo = utxo_pool.at(curr_selection.size());
+
+ // Remove this utxo from the curr_available_value utxo amount
+ curr_available_value -= utxo.effective_value;
+
+ // Avoid searching a branch if the previous UTXO has the same value and same waste and was excluded. Since the ratio of fee to
+ // long term fee is the same, we only need to check if one of those values match in order to know that the waste is the same.
+ if (!curr_selection.empty() && !curr_selection.back() &&
+ utxo.effective_value == utxo_pool.at(curr_selection.size() - 1).effective_value &&
+ utxo.fee == utxo_pool.at(curr_selection.size() - 1).fee) {
+ curr_selection.push_back(false);
+ } else {
+ // Inclusion branch first (Largest First Exploration)
+ curr_selection.push_back(true);
+ curr_value += utxo.effective_value;
+ curr_waste += utxo.fee - utxo.long_term_fee;
+ }
+ }
+ }
+
+ // Check for solution
+ if (best_selection.empty()) {
+ return false;
+ }
+
+ // Set output set
+ value_ret = 0;
+ for (size_t i = 0; i < best_selection.size(); ++i) {
+ if (best_selection.at(i)) {
+ util::insert(out_set, utxo_pool.at(i).m_outputs);
+ value_ret += utxo_pool.at(i).m_value;
+ }
+ }
+
+ return true;
+}
+
+static void ApproximateBestSubset(const std::vector<OutputGroup>& groups, const CAmount& nTotalLower, const CAmount& nTargetValue,
+ std::vector<char>& vfBest, CAmount& nBest, int iterations = 1000)
+{
+ std::vector<char> vfIncluded;
+
+ vfBest.assign(groups.size(), true);
+ nBest = nTotalLower;
+
+ FastRandomContext insecure_rand;
+
+ for (int nRep = 0; nRep < iterations && nBest != nTargetValue; nRep++)
+ {
+ vfIncluded.assign(groups.size(), false);
+ CAmount nTotal = 0;
+ bool fReachedTarget = false;
+ for (int nPass = 0; nPass < 2 && !fReachedTarget; nPass++)
+ {
+ for (unsigned int i = 0; i < groups.size(); i++)
+ {
+ //The solver here uses a randomized algorithm,
+ //the randomness serves no real security purpose but is just
+ //needed to prevent degenerate behavior and it is important
+ //that the rng is fast. We do not use a constant random sequence,
+ //because there may be some privacy improvement by making
+ //the selection random.
+ if (nPass == 0 ? insecure_rand.randbool() : !vfIncluded[i])
+ {
+ nTotal += groups[i].m_value;
+ vfIncluded[i] = true;
+ if (nTotal >= nTargetValue)
+ {
+ fReachedTarget = true;
+ if (nTotal < nBest)
+ {
+ nBest = nTotal;
+ vfBest = vfIncluded;
+ }
+ nTotal -= groups[i].m_value;
+ vfIncluded[i] = false;
+ }
+ }
+ }
+ }
+ }
+}
+
+bool KnapsackSolver(const CAmount& nTargetValue, std::vector<OutputGroup>& groups, std::set<CInputCoin>& setCoinsRet, CAmount& nValueRet)
+{
+ setCoinsRet.clear();
+ nValueRet = 0;
+
+ // List of values less than target
+ boost::optional<OutputGroup> lowest_larger;
+ std::vector<OutputGroup> applicable_groups;
+ CAmount nTotalLower = 0;
+
+ random_shuffle(groups.begin(), groups.end(), GetRandInt);
+
+ for (const OutputGroup& group : groups) {
+ if (group.m_value == nTargetValue) {
+ util::insert(setCoinsRet, group.m_outputs);
+ nValueRet += group.m_value;
+ return true;
+ } else if (group.m_value < nTargetValue + MIN_CHANGE) {
+ applicable_groups.push_back(group);
+ nTotalLower += group.m_value;
+ } else if (!lowest_larger || group.m_value < lowest_larger->m_value) {
+ lowest_larger = group;
+ }
+ }
+
+ if (nTotalLower == nTargetValue) {
+ for (const auto& group : applicable_groups) {
+ util::insert(setCoinsRet, group.m_outputs);
+ nValueRet += group.m_value;
+ }
+ return true;
+ }
+
+ if (nTotalLower < nTargetValue) {
+ if (!lowest_larger) return false;
+ util::insert(setCoinsRet, lowest_larger->m_outputs);
+ nValueRet += lowest_larger->m_value;
+ return true;
+ }
+
+ // Solve subset sum by stochastic approximation
+ std::sort(applicable_groups.begin(), applicable_groups.end(), descending);
+ std::vector<char> vfBest;
+ CAmount nBest;
+
+ ApproximateBestSubset(applicable_groups, nTotalLower, nTargetValue, vfBest, nBest);
+ if (nBest != nTargetValue && nTotalLower >= nTargetValue + MIN_CHANGE) {
+ ApproximateBestSubset(applicable_groups, nTotalLower, nTargetValue + MIN_CHANGE, vfBest, nBest);
+ }
+
+ // If we have a bigger coin and (either the stochastic approximation didn't find a good solution,
+ // or the next bigger coin is closer), return the bigger coin
+ if (lowest_larger &&
+ ((nBest != nTargetValue && nBest < nTargetValue + MIN_CHANGE) || lowest_larger->m_value <= nBest)) {
+ util::insert(setCoinsRet, lowest_larger->m_outputs);
+ nValueRet += lowest_larger->m_value;
+ } else {
+ for (unsigned int i = 0; i < applicable_groups.size(); i++) {
+ if (vfBest[i]) {
+ util::insert(setCoinsRet, applicable_groups[i].m_outputs);
+ nValueRet += applicable_groups[i].m_value;
+ }
+ }
+
+ if (LogAcceptCategory(BCLog::SELECTCOINS)) {
+ LogPrint(BCLog::SELECTCOINS, "SelectCoins() best subset: "); /* Continued */
+ for (unsigned int i = 0; i < applicable_groups.size(); i++) {
+ if (vfBest[i]) {
+ LogPrint(BCLog::SELECTCOINS, "%s ", FormatMoney(applicable_groups[i].m_value)); /* Continued */
+ }
+ }
+ LogPrint(BCLog::SELECTCOINS, "total %s\n", FormatMoney(nBest));
+ }
+ }
+
+ return true;
+}
+
+/******************************************************************************
+
+ OutputGroup
+
+ ******************************************************************************/
+
+void OutputGroup::Insert(const CInputCoin& output, int depth, bool from_me, size_t ancestors, size_t descendants) {
+ m_outputs.push_back(output);
+ m_from_me &= from_me;
+ m_value += output.effective_value;
+ m_depth = std::min(m_depth, depth);
+ // ancestors here express the number of ancestors the new coin will end up having, which is
+ // the sum, rather than the max; this will overestimate in the cases where multiple inputs
+ // have common ancestors
+ m_ancestors += ancestors;
+ // descendants is the count as seen from the top ancestor, not the descendants as seen from the
+ // coin itself; thus, this value is counted as the max, not the sum
+ m_descendants = std::max(m_descendants, descendants);
+ effective_value = m_value;
+}
+
+std::vector<CInputCoin>::iterator OutputGroup::Discard(const CInputCoin& output) {
+ auto it = m_outputs.begin();
+ while (it != m_outputs.end() && it->outpoint != output.outpoint) ++it;
+ if (it == m_outputs.end()) return it;
+ m_value -= output.effective_value;
+ effective_value -= output.effective_value;
+ return m_outputs.erase(it);
+}
+
+bool OutputGroup::EligibleForSpending(const CoinEligibilityFilter& eligibility_filter) const
+{
+ return m_depth >= (m_from_me ? eligibility_filter.conf_mine : eligibility_filter.conf_theirs)
+ && m_ancestors <= eligibility_filter.max_ancestors
+ && m_descendants <= eligibility_filter.max_descendants;
+}
diff --git a/src/wallet/coinselection.h b/src/wallet/coinselection.h
new file mode 100644
index 0000000000..6d755d0969
--- /dev/null
+++ b/src/wallet/coinselection.h
@@ -0,0 +1,101 @@
+// Copyright (c) 2017-2018 The Bitcoin Core developers
+// Distributed under the MIT software license, see the accompanying
+// file COPYING or http://www.opensource.org/licenses/mit-license.php.
+
+#ifndef BITCOIN_WALLET_COINSELECTION_H
+#define BITCOIN_WALLET_COINSELECTION_H
+
+#include <amount.h>
+#include <primitives/transaction.h>
+#include <random.h>
+
+//! target minimum change amount
+static const CAmount MIN_CHANGE = CENT;
+//! final minimum change amount after paying for fees
+static const CAmount MIN_FINAL_CHANGE = MIN_CHANGE/2;
+
+class CInputCoin {
+public:
+ CInputCoin(const CTransactionRef& tx, unsigned int i)
+ {
+ if (!tx)
+ throw std::invalid_argument("tx should not be null");
+ if (i >= tx->vout.size())
+ throw std::out_of_range("The output index is out of range");
+
+ outpoint = COutPoint(tx->GetHash(), i);
+ txout = tx->vout[i];
+ effective_value = txout.nValue;
+ }
+
+ CInputCoin(const CTransactionRef& tx, unsigned int i, int input_bytes) : CInputCoin(tx, i)
+ {
+ m_input_bytes = input_bytes;
+ }
+
+ COutPoint outpoint;
+ CTxOut txout;
+ CAmount effective_value;
+
+ /** Pre-computed estimated size of this output as a fully-signed input in a transaction. Can be -1 if it could not be calculated */
+ int m_input_bytes{-1};
+
+ bool operator<(const CInputCoin& rhs) const {
+ return outpoint < rhs.outpoint;
+ }
+
+ bool operator!=(const CInputCoin& rhs) const {
+ return outpoint != rhs.outpoint;
+ }
+
+ bool operator==(const CInputCoin& rhs) const {
+ return outpoint == rhs.outpoint;
+ }
+};
+
+struct CoinEligibilityFilter
+{
+ const int conf_mine;
+ const int conf_theirs;
+ const uint64_t max_ancestors;
+ const uint64_t max_descendants;
+
+ CoinEligibilityFilter(int conf_mine, int conf_theirs, uint64_t max_ancestors) : conf_mine(conf_mine), conf_theirs(conf_theirs), max_ancestors(max_ancestors), max_descendants(max_ancestors) {}
+ CoinEligibilityFilter(int conf_mine, int conf_theirs, uint64_t max_ancestors, uint64_t max_descendants) : conf_mine(conf_mine), conf_theirs(conf_theirs), max_ancestors(max_ancestors), max_descendants(max_descendants) {}
+};
+
+struct OutputGroup
+{
+ std::vector<CInputCoin> m_outputs;
+ bool m_from_me{true};
+ CAmount m_value{0};
+ int m_depth{999};
+ size_t m_ancestors{0};
+ size_t m_descendants{0};
+ CAmount effective_value{0};
+ CAmount fee{0};
+ CAmount long_term_fee{0};
+
+ OutputGroup() {}
+ OutputGroup(std::vector<CInputCoin>&& outputs, bool from_me, CAmount value, int depth, size_t ancestors, size_t descendants)
+ : m_outputs(std::move(outputs))
+ , m_from_me(from_me)
+ , m_value(value)
+ , m_depth(depth)
+ , m_ancestors(ancestors)
+ , m_descendants(descendants)
+ {}
+ OutputGroup(const CInputCoin& output, int depth, bool from_me, size_t ancestors, size_t descendants) : OutputGroup() {
+ Insert(output, depth, from_me, ancestors, descendants);
+ }
+ void Insert(const CInputCoin& output, int depth, bool from_me, size_t ancestors, size_t descendants);
+ std::vector<CInputCoin>::iterator Discard(const CInputCoin& output);
+ bool EligibleForSpending(const CoinEligibilityFilter& eligibility_filter) const;
+};
+
+bool SelectCoinsBnB(std::vector<OutputGroup>& utxo_pool, const CAmount& target_value, const CAmount& cost_of_change, std::set<CInputCoin>& out_set, CAmount& value_ret, CAmount not_input_fees);
+
+// Original coin selection algorithm as a fallback
+bool KnapsackSolver(const CAmount& nTargetValue, std::vector<OutputGroup>& groups, std::set<CInputCoin>& setCoinsRet, CAmount& nValueRet);
+
+#endif // BITCOIN_WALLET_COINSELECTION_H
diff --git a/src/wallet/crypter.cpp b/src/wallet/crypter.cpp
index 190f8ecf2a..4fa9603011 100644
--- a/src/wallet/crypter.cpp
+++ b/src/wallet/crypter.cpp
@@ -1,18 +1,17 @@
-// Copyright (c) 2009-2015 The Bitcoin Core developers
+// Copyright (c) 2009-2018 The Bitcoin Core developers
// Distributed under the MIT software license, see the accompanying
// file COPYING or http://www.opensource.org/licenses/mit-license.php.
-#include "crypter.h"
+#include <wallet/crypter.h>
-#include "crypto/aes.h"
-#include "crypto/sha512.h"
-#include "script/script.h"
-#include "script/standard.h"
-#include "util.h"
+#include <crypto/aes.h>
+#include <crypto/sha512.h>
+#include <script/script.h>
+#include <script/standard.h>
+#include <util.h>
#include <string>
#include <vector>
-#include <boost/foreach.hpp>
int CCrypter::BytesToKeySHA512AES(const std::vector<unsigned char>& chSalt, const SecureString& strKeyData, int count, unsigned char *key,unsigned char *iv) const
{
@@ -28,8 +27,7 @@ int CCrypter::BytesToKeySHA512AES(const std::vector<unsigned char>& chSalt, cons
CSHA512 di;
di.Write((const unsigned char*)strKeyData.c_str(), strKeyData.size());
- if(chSalt.size())
- di.Write(&chSalt[0], chSalt.size());
+ di.Write(chSalt.data(), chSalt.size());
di.Finalize(buf);
for(int i = 0; i != count - 1; i++)
@@ -48,12 +46,12 @@ bool CCrypter::SetKeyFromPassphrase(const SecureString& strKeyData, const std::v
int i = 0;
if (nDerivationMethod == 0)
- i = BytesToKeySHA512AES(chSalt, strKeyData, nRounds, chKey, chIV);
+ i = BytesToKeySHA512AES(chSalt, strKeyData, nRounds, vchKey.data(), vchIV.data());
if (i != (int)WALLET_CRYPTO_KEY_SIZE)
{
- memory_cleanse(chKey, sizeof(chKey));
- memory_cleanse(chIV, sizeof(chIV));
+ memory_cleanse(vchKey.data(), vchKey.size());
+ memory_cleanse(vchIV.data(), vchIV.size());
return false;
}
@@ -66,8 +64,8 @@ bool CCrypter::SetKey(const CKeyingMaterial& chNewKey, const std::vector<unsigne
if (chNewKey.size() != WALLET_CRYPTO_KEY_SIZE || chNewIV.size() != WALLET_CRYPTO_IV_SIZE)
return false;
- memcpy(&chKey[0], &chNewKey[0], sizeof chKey);
- memcpy(&chIV[0], &chNewIV[0], sizeof chIV);
+ memcpy(vchKey.data(), chNewKey.data(), chNewKey.size());
+ memcpy(vchIV.data(), chNewIV.data(), chNewIV.size());
fKeySet = true;
return true;
@@ -82,8 +80,8 @@ bool CCrypter::Encrypt(const CKeyingMaterial& vchPlaintext, std::vector<unsigned
// n + AES_BLOCKSIZE bytes
vchCiphertext.resize(vchPlaintext.size() + AES_BLOCKSIZE);
- AES256CBCEncrypt enc(chKey, chIV, true);
- size_t nLen = enc.Encrypt(&vchPlaintext[0], vchPlaintext.size(), &vchCiphertext[0]);
+ AES256CBCEncrypt enc(vchKey.data(), vchIV.data(), true);
+ size_t nLen = enc.Encrypt(&vchPlaintext[0], vchPlaintext.size(), vchCiphertext.data());
if(nLen < vchPlaintext.size())
return false;
vchCiphertext.resize(nLen);
@@ -101,8 +99,8 @@ bool CCrypter::Decrypt(const std::vector<unsigned char>& vchCiphertext, CKeyingM
vchPlaintext.resize(nLen);
- AES256CBCDecrypt dec(chKey, chIV, true);
- nLen = dec.Decrypt(&vchCiphertext[0], vchCiphertext.size(), &vchPlaintext[0]);
+ AES256CBCDecrypt dec(vchKey.data(), vchIV.data(), true);
+ nLen = dec.Decrypt(vchCiphertext.data(), vchCiphertext.size(), &vchPlaintext[0]);
if(nLen == 0)
return false;
vchPlaintext.resize(nLen);
@@ -114,7 +112,7 @@ static bool EncryptSecret(const CKeyingMaterial& vMasterKey, const CKeyingMateri
{
CCrypter cKeyCrypter;
std::vector<unsigned char> chIV(WALLET_CRYPTO_IV_SIZE);
- memcpy(&chIV[0], &nIV, WALLET_CRYPTO_IV_SIZE);
+ memcpy(chIV.data(), &nIV, WALLET_CRYPTO_IV_SIZE);
if(!cKeyCrypter.SetKey(vMasterKey, chIV))
return false;
return cKeyCrypter.Encrypt(*((const CKeyingMaterial*)&vchPlaintext), vchCiphertext);
@@ -124,7 +122,7 @@ static bool DecryptSecret(const CKeyingMaterial& vMasterKey, const std::vector<u
{
CCrypter cKeyCrypter;
std::vector<unsigned char> chIV(WALLET_CRYPTO_IV_SIZE);
- memcpy(&chIV[0], &nIV, WALLET_CRYPTO_IV_SIZE);
+ memcpy(chIV.data(), &nIV, WALLET_CRYPTO_IV_SIZE);
if(!cKeyCrypter.SetKey(vMasterKey, chIV))
return false;
return cKeyCrypter.Decrypt(vchCiphertext, *((CKeyingMaterial*)&vchPlaintext));
@@ -154,6 +152,15 @@ bool CCryptoKeyStore::SetCrypted()
return true;
}
+bool CCryptoKeyStore::IsLocked() const
+{
+ if (!IsCrypted()) {
+ return false;
+ }
+ LOCK(cs_KeyStore);
+ return vMasterKey.empty();
+}
+
bool CCryptoKeyStore::Lock()
{
if (!SetCrypted())
@@ -208,21 +215,23 @@ bool CCryptoKeyStore::Unlock(const CKeyingMaterial& vMasterKeyIn)
bool CCryptoKeyStore::AddKeyPubKey(const CKey& key, const CPubKey &pubkey)
{
- {
- LOCK(cs_KeyStore);
- if (!IsCrypted())
- return CBasicKeyStore::AddKeyPubKey(key, pubkey);
+ LOCK(cs_KeyStore);
+ if (!IsCrypted()) {
+ return CBasicKeyStore::AddKeyPubKey(key, pubkey);
+ }
- if (IsLocked())
- return false;
+ if (IsLocked()) {
+ return false;
+ }
- std::vector<unsigned char> vchCryptedSecret;
- CKeyingMaterial vchSecret(key.begin(), key.end());
- if (!EncryptSecret(vMasterKey, vchSecret, pubkey.GetHash(), vchCryptedSecret))
- return false;
+ std::vector<unsigned char> vchCryptedSecret;
+ CKeyingMaterial vchSecret(key.begin(), key.end());
+ if (!EncryptSecret(vMasterKey, vchSecret, pubkey.GetHash(), vchCryptedSecret)) {
+ return false;
+ }
- if (!AddCryptedKey(pubkey, vchCryptedSecret))
- return false;
+ if (!AddCryptedKey(pubkey, vchCryptedSecret)) {
+ return false;
}
return true;
}
@@ -230,73 +239,89 @@ bool CCryptoKeyStore::AddKeyPubKey(const CKey& key, const CPubKey &pubkey)
bool CCryptoKeyStore::AddCryptedKey(const CPubKey &vchPubKey, const std::vector<unsigned char> &vchCryptedSecret)
{
- {
- LOCK(cs_KeyStore);
- if (!SetCrypted())
- return false;
-
- mapCryptedKeys[vchPubKey.GetID()] = make_pair(vchPubKey, vchCryptedSecret);
+ LOCK(cs_KeyStore);
+ if (!SetCrypted()) {
+ return false;
}
+
+ mapCryptedKeys[vchPubKey.GetID()] = make_pair(vchPubKey, vchCryptedSecret);
+ ImplicitlyLearnRelatedKeyScripts(vchPubKey);
return true;
}
+bool CCryptoKeyStore::HaveKey(const CKeyID &address) const
+{
+ LOCK(cs_KeyStore);
+ if (!IsCrypted()) {
+ return CBasicKeyStore::HaveKey(address);
+ }
+ return mapCryptedKeys.count(address) > 0;
+}
+
bool CCryptoKeyStore::GetKey(const CKeyID &address, CKey& keyOut) const
{
- {
- LOCK(cs_KeyStore);
- if (!IsCrypted())
- return CBasicKeyStore::GetKey(address, keyOut);
+ LOCK(cs_KeyStore);
+ if (!IsCrypted()) {
+ return CBasicKeyStore::GetKey(address, keyOut);
+ }
- CryptedKeyMap::const_iterator mi = mapCryptedKeys.find(address);
- if (mi != mapCryptedKeys.end())
- {
- const CPubKey &vchPubKey = (*mi).second.first;
- const std::vector<unsigned char> &vchCryptedSecret = (*mi).second.second;
- return DecryptKey(vMasterKey, vchCryptedSecret, vchPubKey, keyOut);
- }
+ CryptedKeyMap::const_iterator mi = mapCryptedKeys.find(address);
+ if (mi != mapCryptedKeys.end())
+ {
+ const CPubKey &vchPubKey = (*mi).second.first;
+ const std::vector<unsigned char> &vchCryptedSecret = (*mi).second.second;
+ return DecryptKey(vMasterKey, vchCryptedSecret, vchPubKey, keyOut);
}
return false;
}
bool CCryptoKeyStore::GetPubKey(const CKeyID &address, CPubKey& vchPubKeyOut) const
{
+ LOCK(cs_KeyStore);
+ if (!IsCrypted())
+ return CBasicKeyStore::GetPubKey(address, vchPubKeyOut);
+
+ CryptedKeyMap::const_iterator mi = mapCryptedKeys.find(address);
+ if (mi != mapCryptedKeys.end())
{
- LOCK(cs_KeyStore);
- if (!IsCrypted())
- return CBasicKeyStore::GetPubKey(address, vchPubKeyOut);
+ vchPubKeyOut = (*mi).second.first;
+ return true;
+ }
+ // Check for watch-only pubkeys
+ return CBasicKeyStore::GetPubKey(address, vchPubKeyOut);
+}
- CryptedKeyMap::const_iterator mi = mapCryptedKeys.find(address);
- if (mi != mapCryptedKeys.end())
- {
- vchPubKeyOut = (*mi).second.first;
- return true;
- }
- // Check for watch-only pubkeys
- return CBasicKeyStore::GetPubKey(address, vchPubKeyOut);
+std::set<CKeyID> CCryptoKeyStore::GetKeys() const
+{
+ LOCK(cs_KeyStore);
+ if (!IsCrypted()) {
+ return CBasicKeyStore::GetKeys();
}
- return false;
+ std::set<CKeyID> set_address;
+ for (const auto& mi : mapCryptedKeys) {
+ set_address.insert(mi.first);
+ }
+ return set_address;
}
bool CCryptoKeyStore::EncryptKeys(CKeyingMaterial& vMasterKeyIn)
{
+ LOCK(cs_KeyStore);
+ if (!mapCryptedKeys.empty() || IsCrypted())
+ return false;
+
+ fUseCrypto = true;
+ for (KeyMap::value_type& mKey : mapKeys)
{
- LOCK(cs_KeyStore);
- if (!mapCryptedKeys.empty() || IsCrypted())
+ const CKey &key = mKey.second;
+ CPubKey vchPubKey = key.GetPubKey();
+ CKeyingMaterial vchSecret(key.begin(), key.end());
+ std::vector<unsigned char> vchCryptedSecret;
+ if (!EncryptSecret(vMasterKeyIn, vchSecret, vchPubKey.GetHash(), vchCryptedSecret))
+ return false;
+ if (!AddCryptedKey(vchPubKey, vchCryptedSecret))
return false;
-
- fUseCrypto = true;
- BOOST_FOREACH(KeyMap::value_type& mKey, mapKeys)
- {
- const CKey &key = mKey.second;
- CPubKey vchPubKey = key.GetPubKey();
- CKeyingMaterial vchSecret(key.begin(), key.end());
- std::vector<unsigned char> vchCryptedSecret;
- if (!EncryptSecret(vMasterKeyIn, vchSecret, vchPubKey.GetHash(), vchCryptedSecret))
- return false;
- if (!AddCryptedKey(vchPubKey, vchCryptedSecret))
- return false;
- }
- mapKeys.clear();
}
+ mapKeys.clear();
return true;
}
diff --git a/src/wallet/crypter.h b/src/wallet/crypter.h
index 5d0a4a3305..418316c398 100644
--- a/src/wallet/crypter.h
+++ b/src/wallet/crypter.h
@@ -1,15 +1,15 @@
-// Copyright (c) 2009-2015 The Bitcoin Core developers
+// Copyright (c) 2009-2018 The Bitcoin Core developers
// Distributed under the MIT software license, see the accompanying
// file COPYING or http://www.opensource.org/licenses/mit-license.php.
#ifndef BITCOIN_WALLET_CRYPTER_H
#define BITCOIN_WALLET_CRYPTER_H
-#include "keystore.h"
-#include "serialize.h"
-#include "support/allocators/secure.h"
+#include <keystore.h>
+#include <serialize.h>
+#include <support/allocators/secure.h>
-class uint256;
+#include <atomic>
const unsigned int WALLET_CRYPTO_KEY_SIZE = 32;
const unsigned int WALLET_CRYPTO_SALT_SIZE = 8;
@@ -18,13 +18,13 @@ const unsigned int WALLET_CRYPTO_IV_SIZE = 16;
/**
* Private key encryption is done based on a CMasterKey,
* which holds a salt and random encryption key.
- *
+ *
* CMasterKeys are encrypted using AES-256-CBC using a key
* derived using derivation method nDerivationMethod
* (0 == EVP_sha512()) and derivation iterations nDeriveIterations.
* vchOtherDerivationParameters is provided for alternative algorithms
* which may require more parameters (such as scrypt).
- *
+ *
* Wallet Private Keys are then encrypted using AES-256-CBC
* with the double-sha256 of the public key as the IV, and the
* master key's key as the encryption key (see keystore.[ch]).
@@ -47,7 +47,7 @@ public:
ADD_SERIALIZE_METHODS;
template <typename Stream, typename Operation>
- inline void SerializationOp(Stream& s, Operation ser_action, int nType, int nVersion) {
+ inline void SerializationOp(Stream& s, Operation ser_action) {
READWRITE(vchCryptedKey);
READWRITE(vchSalt);
READWRITE(nDerivationMethod);
@@ -67,7 +67,7 @@ public:
typedef std::vector<unsigned char, secure_allocator<unsigned char> > CKeyingMaterial;
-namespace wallet_crypto
+namespace wallet_crypto_tests
{
class TestCrypter;
}
@@ -75,10 +75,10 @@ namespace wallet_crypto
/** Encryption/decryption context with key information */
class CCrypter
{
-friend class wallet_crypto::TestCrypter; // for test access to chKey/chIV
+friend class wallet_crypto_tests::TestCrypter; // for test access to chKey/chIV
private:
- unsigned char chKey[WALLET_CRYPTO_KEY_SIZE];
- unsigned char chIV[WALLET_CRYPTO_IV_SIZE];
+ std::vector<unsigned char, secure_allocator<unsigned char>> vchKey;
+ std::vector<unsigned char, secure_allocator<unsigned char>> vchIV;
bool fKeySet;
int BytesToKeySHA512AES(const std::vector<unsigned char>& chSalt, const SecureString& strKeyData, int count, unsigned char *key,unsigned char *iv) const;
@@ -91,28 +91,21 @@ public:
void CleanKey()
{
- memory_cleanse(chKey, sizeof(chKey));
- memory_cleanse(chIV, sizeof(chIV));
+ memory_cleanse(vchKey.data(), vchKey.size());
+ memory_cleanse(vchIV.data(), vchIV.size());
fKeySet = false;
}
CCrypter()
{
fKeySet = false;
-
- // Try to keep the key data out of swap (and be a bit over-careful to keep the IV that we don't even use out of swap)
- // Note that this does nothing about suspend-to-disk (which will put all our key data on disk)
- // Note as well that at no point in this program is any attempt made to prevent stealing of keys by reading the memory of the running process.
- LockedPageManager::Instance().LockRange(&chKey[0], sizeof chKey);
- LockedPageManager::Instance().LockRange(&chIV[0], sizeof chIV);
+ vchKey.resize(WALLET_CRYPTO_KEY_SIZE);
+ vchIV.resize(WALLET_CRYPTO_IV_SIZE);
}
~CCrypter()
{
CleanKey();
-
- LockedPageManager::Instance().UnlockRange(&chKey[0], sizeof chKey);
- LockedPageManager::Instance().UnlockRange(&chIV[0], sizeof chIV);
}
};
@@ -122,78 +115,42 @@ public:
class CCryptoKeyStore : public CBasicKeyStore
{
private:
- CryptedKeyMap mapCryptedKeys;
- CKeyingMaterial vMasterKey;
+ CKeyingMaterial vMasterKey GUARDED_BY(cs_KeyStore);
//! if fUseCrypto is true, mapKeys must be empty
//! if fUseCrypto is false, vMasterKey must be empty
- bool fUseCrypto;
+ std::atomic<bool> fUseCrypto;
//! keeps track of whether Unlock has run a thorough check before
bool fDecryptionThoroughlyChecked;
protected:
+ using CryptedKeyMap = std::map<CKeyID, std::pair<CPubKey, std::vector<unsigned char>>>;
+
bool SetCrypted();
//! will encrypt previously unencrypted keys
bool EncryptKeys(CKeyingMaterial& vMasterKeyIn);
bool Unlock(const CKeyingMaterial& vMasterKeyIn);
+ CryptedKeyMap mapCryptedKeys GUARDED_BY(cs_KeyStore);
public:
CCryptoKeyStore() : fUseCrypto(false), fDecryptionThoroughlyChecked(false)
{
}
- bool IsCrypted() const
- {
- return fUseCrypto;
- }
-
- bool IsLocked() const
- {
- if (!IsCrypted())
- return false;
- bool result;
- {
- LOCK(cs_KeyStore);
- result = vMasterKey.empty();
- }
- return result;
- }
-
+ bool IsCrypted() const { return fUseCrypto; }
+ bool IsLocked() const;
bool Lock();
virtual bool AddCryptedKey(const CPubKey &vchPubKey, const std::vector<unsigned char> &vchCryptedSecret);
- bool AddKeyPubKey(const CKey& key, const CPubKey &pubkey);
- bool HaveKey(const CKeyID &address) const
- {
- {
- LOCK(cs_KeyStore);
- if (!IsCrypted())
- return CBasicKeyStore::HaveKey(address);
- return mapCryptedKeys.count(address) > 0;
- }
- return false;
- }
- bool GetKey(const CKeyID &address, CKey& keyOut) const;
- bool GetPubKey(const CKeyID &address, CPubKey& vchPubKeyOut) const;
- void GetKeys(std::set<CKeyID> &setAddress) const
- {
- if (!IsCrypted())
- {
- CBasicKeyStore::GetKeys(setAddress);
- return;
- }
- setAddress.clear();
- CryptedKeyMap::const_iterator mi = mapCryptedKeys.begin();
- while (mi != mapCryptedKeys.end())
- {
- setAddress.insert((*mi).first);
- mi++;
- }
- }
+ bool AddKeyPubKey(const CKey& key, const CPubKey &pubkey) override;
+ bool HaveKey(const CKeyID &address) const override;
+ bool GetKey(const CKeyID &address, CKey& keyOut) const override;
+ bool GetPubKey(const CKeyID &address, CPubKey& vchPubKeyOut) const override;
+ std::set<CKeyID> GetKeys() const override;
/**
* Wallet status (encrypted, locked) changed.
diff --git a/src/wallet/db.cpp b/src/wallet/db.cpp
index cfd007ca1c..d0fe51801e 100644
--- a/src/wallet/db.cpp
+++ b/src/wallet/db.cpp
@@ -1,15 +1,15 @@
// Copyright (c) 2009-2010 Satoshi Nakamoto
-// Copyright (c) 2009-2015 The Bitcoin Core developers
+// Copyright (c) 2009-2018 The Bitcoin Core developers
// Distributed under the MIT software license, see the accompanying
// file COPYING or http://www.opensource.org/licenses/mit-license.php.
-#include "db.h"
+#include <wallet/db.h>
-#include "addrman.h"
-#include "hash.h"
-#include "protocol.h"
-#include "util.h"
-#include "utilstrencodings.h"
+#include <addrman.h>
+#include <hash.h>
+#include <protocol.h>
+#include <utilstrencodings.h>
+#include <wallet/walletutil.h>
#include <stdint.h>
@@ -17,75 +17,134 @@
#include <sys/stat.h>
#endif
-#include <boost/filesystem.hpp>
#include <boost/thread.hpp>
-#include <boost/version.hpp>
-using namespace std;
+namespace {
+//! Make sure database has a unique fileid within the environment. If it
+//! doesn't, throw an error. BDB caches do not work properly when more than one
+//! open database has the same fileid (values written to one database may show
+//! up in reads to other databases).
+//!
+//! BerkeleyDB generates unique fileids by default
+//! (https://docs.oracle.com/cd/E17275_01/html/programmer_reference/program_copy.html),
+//! so bitcoin should never create different databases with the same fileid, but
+//! this error can be triggered if users manually copy database files.
+void CheckUniqueFileid(const BerkeleyEnvironment& env, const std::string& filename, Db& db)
+{
+ if (env.IsMock()) return;
+ u_int8_t fileid[DB_FILE_ID_LEN];
+ int ret = db.get_mpf()->get_fileid(fileid);
+ if (ret != 0) {
+ throw std::runtime_error(strprintf("BerkeleyBatch: Can't open database %s (get_fileid failed with %d)", filename, ret));
+ }
-unsigned int nWalletDBUpdated;
+ for (const auto& item : env.mapDb) {
+ u_int8_t item_fileid[DB_FILE_ID_LEN];
+ if (item.second && item.second->get_mpf()->get_fileid(item_fileid) == 0 &&
+ memcmp(fileid, item_fileid, sizeof(fileid)) == 0) {
+ const char* item_filename = nullptr;
+ item.second->get_dbname(&item_filename, nullptr);
+ throw std::runtime_error(strprintf("BerkeleyBatch: Can't open database %s (duplicates fileid %s from %s)", filename,
+ HexStr(std::begin(item_fileid), std::end(item_fileid)),
+ item_filename ? item_filename : "(unknown database)"));
+ }
+ }
+}
+CCriticalSection cs_db;
+std::map<std::string, BerkeleyEnvironment> g_dbenvs GUARDED_BY(cs_db); //!< Map from directory name to open db environment.
+} // namespace
+
+BerkeleyEnvironment* GetWalletEnv(const fs::path& wallet_path, std::string& database_filename)
+{
+ fs::path env_directory;
+ if (fs::is_regular_file(wallet_path)) {
+ // Special case for backwards compatibility: if wallet path points to an
+ // existing file, treat it as the path to a BDB data file in a parent
+ // directory that also contains BDB log files.
+ env_directory = wallet_path.parent_path();
+ database_filename = wallet_path.filename().string();
+ } else {
+ // Normal case: Interpret wallet path as a directory path containing
+ // data and log files.
+ env_directory = wallet_path;
+ database_filename = "wallet.dat";
+ }
+ LOCK(cs_db);
+ // Note: An ununsed temporary BerkeleyEnvironment object may be created inside the
+ // emplace function if the key already exists. This is a little inefficient,
+ // but not a big concern since the map will be changed in the future to hold
+ // pointers instead of objects, anyway.
+ return &g_dbenvs.emplace(std::piecewise_construct, std::forward_as_tuple(env_directory.string()), std::forward_as_tuple(env_directory)).first->second;
+}
//
-// CDB
+// BerkeleyBatch
//
-CDBEnv bitdb;
-
-void CDBEnv::EnvShutdown()
+void BerkeleyEnvironment::Close()
{
if (!fDbEnvInit)
return;
fDbEnvInit = false;
+
+ for (auto& db : mapDb) {
+ auto count = mapFileUseCount.find(db.first);
+ assert(count == mapFileUseCount.end() || count->second == 0);
+ if (db.second) {
+ db.second->close(0);
+ delete db.second;
+ db.second = nullptr;
+ }
+ }
+
int ret = dbenv->close(0);
if (ret != 0)
- LogPrintf("CDBEnv::EnvShutdown: Error %d shutting down database environment: %s\n", ret, DbEnv::strerror(ret));
+ LogPrintf("BerkeleyEnvironment::Close: Error %d closing database environment: %s\n", ret, DbEnv::strerror(ret));
if (!fMockDb)
- DbEnv(0).remove(strPath.c_str(), 0);
+ DbEnv((u_int32_t)0).remove(strPath.c_str(), 0);
}
-void CDBEnv::Reset()
+void BerkeleyEnvironment::Reset()
{
- delete dbenv;
- dbenv = new DbEnv(DB_CXX_NO_EXCEPTIONS);
+ dbenv.reset(new DbEnv(DB_CXX_NO_EXCEPTIONS));
fDbEnvInit = false;
fMockDb = false;
}
-CDBEnv::CDBEnv() : dbenv(NULL)
+BerkeleyEnvironment::BerkeleyEnvironment(const fs::path& dir_path) : strPath(dir_path.string())
{
Reset();
}
-CDBEnv::~CDBEnv()
+BerkeleyEnvironment::~BerkeleyEnvironment()
{
- EnvShutdown();
- delete dbenv;
- dbenv = NULL;
+ Close();
}
-void CDBEnv::Close()
-{
- EnvShutdown();
-}
-
-bool CDBEnv::Open(const boost::filesystem::path& pathIn)
+bool BerkeleyEnvironment::Open(bool retry)
{
if (fDbEnvInit)
return true;
boost::this_thread::interruption_point();
- strPath = pathIn.string();
- boost::filesystem::path pathLogDir = pathIn / "database";
- TryCreateDirectory(pathLogDir);
- boost::filesystem::path pathErrorFile = pathIn / "db.log";
- LogPrintf("CDBEnv::Open: LogDir=%s ErrorFile=%s\n", pathLogDir.string(), pathErrorFile.string());
+ fs::path pathIn = strPath;
+ TryCreateDirectories(pathIn);
+ if (!LockDirectory(pathIn, ".walletlock")) {
+ LogPrintf("Cannot obtain a lock on wallet directory %s. Another instance of bitcoin may be using it.\n", strPath);
+ return false;
+ }
+
+ fs::path pathLogDir = pathIn / "database";
+ TryCreateDirectories(pathLogDir);
+ fs::path pathErrorFile = pathIn / "db.log";
+ LogPrintf("BerkeleyEnvironment::Open: LogDir=%s ErrorFile=%s\n", pathLogDir.string(), pathErrorFile.string());
unsigned int nEnvFlags = 0;
- if (GetBoolArg("-privdb", DEFAULT_WALLET_PRIVDB))
+ if (gArgs.GetBoolArg("-privdb", DEFAULT_WALLET_PRIVDB))
nEnvFlags |= DB_PRIVATE;
dbenv->set_lg_dir(pathLogDir.string().c_str());
@@ -94,7 +153,7 @@ bool CDBEnv::Open(const boost::filesystem::path& pathIn)
dbenv->set_lg_max(1048576);
dbenv->set_lk_max_locks(40000);
dbenv->set_lk_max_objects(40000);
- dbenv->set_errfile(fopen(pathErrorFile.string().c_str(), "a")); /// debug
+ dbenv->set_errfile(fsbridge::fopen(pathErrorFile, "a")); /// debug
dbenv->set_flags(DB_AUTO_COMMIT, 1);
dbenv->set_flags(DB_TXN_WRITE_NOSYNC, 1);
dbenv->log_set_config(DB_LOG_AUTO_REMOVE, 1);
@@ -108,22 +167,45 @@ bool CDBEnv::Open(const boost::filesystem::path& pathIn)
DB_RECOVER |
nEnvFlags,
S_IRUSR | S_IWUSR);
- if (ret != 0)
- return error("CDBEnv::Open: Error %d opening database environment: %s\n", ret, DbEnv::strerror(ret));
+ if (ret != 0) {
+ LogPrintf("BerkeleyEnvironment::Open: Error %d opening database environment: %s\n", ret, DbEnv::strerror(ret));
+ int ret2 = dbenv->close(0);
+ if (ret2 != 0) {
+ LogPrintf("BerkeleyEnvironment::Open: Error %d closing failed database environment: %s\n", ret2, DbEnv::strerror(ret2));
+ }
+ Reset();
+ if (retry) {
+ // try moving the database env out of the way
+ fs::path pathDatabaseBak = pathIn / strprintf("database.%d.bak", GetTime());
+ try {
+ fs::rename(pathLogDir, pathDatabaseBak);
+ LogPrintf("Moved old %s to %s. Retrying.\n", pathLogDir.string(), pathDatabaseBak.string());
+ } catch (const fs::filesystem_error&) {
+ // failure is ok (well, not really, but it's not worse than what we started with)
+ }
+ // try opening it again one more time
+ if (!Open(false /* retry */)) {
+ // if it still fails, it probably means we can't even create the database env
+ return false;
+ }
+ } else {
+ return false;
+ }
+ }
fDbEnvInit = true;
fMockDb = false;
return true;
}
-void CDBEnv::MakeMock()
+void BerkeleyEnvironment::MakeMock()
{
if (fDbEnvInit)
- throw runtime_error("CDBEnv::MakeMock: Already initialized");
+ throw std::runtime_error("BerkeleyEnvironment::MakeMock: Already initialized");
boost::this_thread::interruption_point();
- LogPrint("db", "CDBEnv::MakeMock\n");
+ LogPrint(BCLog::DB, "BerkeleyEnvironment::MakeMock\n");
dbenv->set_cachesize(1, 0, 1);
dbenv->set_lg_bsize(10485760 * 4);
@@ -132,7 +214,7 @@ void CDBEnv::MakeMock()
dbenv->set_lk_max_objects(10000);
dbenv->set_flags(DB_AUTO_COMMIT, 1);
dbenv->log_set_config(DB_LOG_IN_MEMORY, 1);
- int ret = dbenv->open(NULL,
+ int ret = dbenv->open(nullptr,
DB_CREATE |
DB_INIT_LOCK |
DB_INIT_LOG |
@@ -142,27 +224,148 @@ void CDBEnv::MakeMock()
DB_PRIVATE,
S_IRUSR | S_IWUSR);
if (ret > 0)
- throw runtime_error(strprintf("CDBEnv::MakeMock: Error %d opening database environment.", ret));
+ throw std::runtime_error(strprintf("BerkeleyEnvironment::MakeMock: Error %d opening database environment.", ret));
fDbEnvInit = true;
fMockDb = true;
}
-CDBEnv::VerifyResult CDBEnv::Verify(const std::string& strFile, bool (*recoverFunc)(CDBEnv& dbenv, const std::string& strFile))
+BerkeleyEnvironment::VerifyResult BerkeleyEnvironment::Verify(const std::string& strFile, recoverFunc_type recoverFunc, std::string& out_backup_filename)
{
LOCK(cs_db);
assert(mapFileUseCount.count(strFile) == 0);
- Db db(dbenv, 0);
- int result = db.verify(strFile.c_str(), NULL, NULL, 0);
+ Db db(dbenv.get(), 0);
+ int result = db.verify(strFile.c_str(), nullptr, nullptr, 0);
if (result == 0)
- return VERIFY_OK;
- else if (recoverFunc == NULL)
- return RECOVER_FAIL;
+ return VerifyResult::VERIFY_OK;
+ else if (recoverFunc == nullptr)
+ return VerifyResult::RECOVER_FAIL;
// Try to recover:
- bool fRecovered = (*recoverFunc)(*this, strFile);
- return (fRecovered ? RECOVER_OK : RECOVER_FAIL);
+ bool fRecovered = (*recoverFunc)(fs::path(strPath) / strFile, out_backup_filename);
+ return (fRecovered ? VerifyResult::RECOVER_OK : VerifyResult::RECOVER_FAIL);
+}
+
+bool BerkeleyBatch::Recover(const fs::path& file_path, void *callbackDataIn, bool (*recoverKVcallback)(void* callbackData, CDataStream ssKey, CDataStream ssValue), std::string& newFilename)
+{
+ std::string filename;
+ BerkeleyEnvironment* env = GetWalletEnv(file_path, filename);
+
+ // Recovery procedure:
+ // move wallet file to walletfilename.timestamp.bak
+ // Call Salvage with fAggressive=true to
+ // get as much data as possible.
+ // Rewrite salvaged data to fresh wallet file
+ // Set -rescan so any missing transactions will be
+ // found.
+ int64_t now = GetTime();
+ newFilename = strprintf("%s.%d.bak", filename, now);
+
+ int result = env->dbenv->dbrename(nullptr, filename.c_str(), nullptr,
+ newFilename.c_str(), DB_AUTO_COMMIT);
+ if (result == 0)
+ LogPrintf("Renamed %s to %s\n", filename, newFilename);
+ else
+ {
+ LogPrintf("Failed to rename %s to %s\n", filename, newFilename);
+ return false;
+ }
+
+ std::vector<BerkeleyEnvironment::KeyValPair> salvagedData;
+ bool fSuccess = env->Salvage(newFilename, true, salvagedData);
+ if (salvagedData.empty())
+ {
+ LogPrintf("Salvage(aggressive) found no records in %s.\n", newFilename);
+ return false;
+ }
+ LogPrintf("Salvage(aggressive) found %u records\n", salvagedData.size());
+
+ std::unique_ptr<Db> pdbCopy = MakeUnique<Db>(env->dbenv.get(), 0);
+ int ret = pdbCopy->open(nullptr, // Txn pointer
+ filename.c_str(), // Filename
+ "main", // Logical db name
+ DB_BTREE, // Database type
+ DB_CREATE, // Flags
+ 0);
+ if (ret > 0) {
+ LogPrintf("Cannot create database file %s\n", filename);
+ pdbCopy->close(0);
+ return false;
+ }
+
+ DbTxn* ptxn = env->TxnBegin();
+ for (BerkeleyEnvironment::KeyValPair& row : salvagedData)
+ {
+ if (recoverKVcallback)
+ {
+ CDataStream ssKey(row.first, SER_DISK, CLIENT_VERSION);
+ CDataStream ssValue(row.second, SER_DISK, CLIENT_VERSION);
+ if (!(*recoverKVcallback)(callbackDataIn, ssKey, ssValue))
+ continue;
+ }
+ Dbt datKey(&row.first[0], row.first.size());
+ Dbt datValue(&row.second[0], row.second.size());
+ int ret2 = pdbCopy->put(ptxn, &datKey, &datValue, DB_NOOVERWRITE);
+ if (ret2 > 0)
+ fSuccess = false;
+ }
+ ptxn->commit(0);
+ pdbCopy->close(0);
+
+ return fSuccess;
+}
+
+bool BerkeleyBatch::VerifyEnvironment(const fs::path& file_path, std::string& errorStr)
+{
+ std::string walletFile;
+ BerkeleyEnvironment* env = GetWalletEnv(file_path, walletFile);
+ fs::path walletDir = env->Directory();
+
+ LogPrintf("Using BerkeleyDB version %s\n", DbEnv::version(0, 0, 0));
+ LogPrintf("Using wallet %s\n", walletFile);
+
+ // Wallet file must be a plain filename without a directory
+ if (walletFile != fs::basename(walletFile) + fs::extension(walletFile))
+ {
+ errorStr = strprintf(_("Wallet %s resides outside wallet directory %s"), walletFile, walletDir.string());
+ return false;
+ }
+
+ if (!env->Open(true /* retry */)) {
+ errorStr = strprintf(_("Error initializing wallet database environment %s!"), walletDir);
+ return false;
+ }
+
+ return true;
+}
+
+bool BerkeleyBatch::VerifyDatabaseFile(const fs::path& file_path, std::string& warningStr, std::string& errorStr, BerkeleyEnvironment::recoverFunc_type recoverFunc)
+{
+ std::string walletFile;
+ BerkeleyEnvironment* env = GetWalletEnv(file_path, walletFile);
+ fs::path walletDir = env->Directory();
+
+ if (fs::exists(walletDir / walletFile))
+ {
+ std::string backup_filename;
+ BerkeleyEnvironment::VerifyResult r = env->Verify(walletFile, recoverFunc, backup_filename);
+ if (r == BerkeleyEnvironment::VerifyResult::RECOVER_OK)
+ {
+ warningStr = strprintf(_("Warning: Wallet file corrupt, data salvaged!"
+ " Original %s saved as %s in %s; if"
+ " your balance or transactions are incorrect you should"
+ " restore from a backup."),
+ walletFile, backup_filename, walletDir);
+ }
+ if (r == BerkeleyEnvironment::VerifyResult::RECOVER_FAIL)
+ {
+ errorStr = strprintf(_("%s corrupt, salvage failed"), walletFile);
+ return false;
+ }
+ }
+ // also return true if files does not exists
+ return true;
}
/* End of headers, beginning of key/value data */
@@ -170,7 +373,7 @@ static const char *HEADER_END = "HEADER=END";
/* End of key/value data */
static const char *DATA_END = "DATA=END";
-bool CDBEnv::Salvage(const std::string& strFile, bool fAggressive, std::vector<CDBEnv::KeyValPair>& vResult)
+bool BerkeleyEnvironment::Salvage(const std::string& strFile, bool fAggressive, std::vector<BerkeleyEnvironment::KeyValPair>& vResult)
{
LOCK(cs_db);
assert(mapFileUseCount.count(strFile) == 0);
@@ -179,19 +382,19 @@ bool CDBEnv::Salvage(const std::string& strFile, bool fAggressive, std::vector<C
if (fAggressive)
flags |= DB_AGGRESSIVE;
- stringstream strDump;
+ std::stringstream strDump;
- Db db(dbenv, 0);
- int result = db.verify(strFile.c_str(), NULL, &strDump, flags);
+ Db db(dbenv.get(), 0);
+ int result = db.verify(strFile.c_str(), nullptr, &strDump, flags);
if (result == DB_VERIFY_BAD) {
- LogPrintf("CDBEnv::Salvage: Database salvage found errors, all data may not be recoverable.\n");
+ LogPrintf("BerkeleyEnvironment::Salvage: Database salvage found errors, all data may not be recoverable.\n");
if (!fAggressive) {
- LogPrintf("CDBEnv::Salvage: Rerun with aggressive mode to ignore errors and continue.\n");
+ LogPrintf("BerkeleyEnvironment::Salvage: Rerun with aggressive mode to ignore errors and continue.\n");
return false;
}
}
if (result != 0 && result != DB_VERIFY_BAD) {
- LogPrintf("CDBEnv::Salvage: Database salvage failed with result %d.\n", result);
+ LogPrintf("BerkeleyEnvironment::Salvage: Database salvage failed with result %d.\n", result);
return false;
}
@@ -203,7 +406,7 @@ bool CDBEnv::Salvage(const std::string& strFile, bool fAggressive, std::vector<C
// ... repeated
// DATA=END
- string strLine;
+ std::string strLine;
while (!strDump.eof() && strLine != HEADER_END)
getline(strDump, strLine); // Skip past header
@@ -215,7 +418,7 @@ bool CDBEnv::Salvage(const std::string& strFile, bool fAggressive, std::vector<C
break;
getline(strDump, valueHex);
if (valueHex == DATA_END) {
- LogPrintf("CDBEnv::Salvage: WARNING: Number of keys in data does not match number of values.\n");
+ LogPrintf("BerkeleyEnvironment::Salvage: WARNING: Number of keys in data does not match number of values.\n");
break;
}
vResult.push_back(make_pair(ParseHex(keyHex), ParseHex(valueHex)));
@@ -223,7 +426,7 @@ bool CDBEnv::Salvage(const std::string& strFile, bool fAggressive, std::vector<C
}
if (keyHex != DATA_END) {
- LogPrintf("CDBEnv::Salvage: WARNING: Unexpected end of file while reading salvage output.\n");
+ LogPrintf("BerkeleyEnvironment::Salvage: WARNING: Unexpected end of file while reading salvage output.\n");
return false;
}
@@ -231,7 +434,7 @@ bool CDBEnv::Salvage(const std::string& strFile, bool fAggressive, std::vector<C
}
-void CDBEnv::CheckpointLSN(const std::string& strFile)
+void BerkeleyEnvironment::CheckpointLSN(const std::string& strFile)
{
dbenv->txn_checkpoint(0, 0, 0);
if (fMockDb)
@@ -240,66 +443,86 @@ void CDBEnv::CheckpointLSN(const std::string& strFile)
}
-CDB::CDB(const std::string& strFilename, const char* pszMode, bool fFlushOnCloseIn) : pdb(NULL), activeTxn(NULL)
+BerkeleyBatch::BerkeleyBatch(BerkeleyDatabase& database, const char* pszMode, bool fFlushOnCloseIn) : pdb(nullptr), activeTxn(nullptr)
{
- int ret;
fReadOnly = (!strchr(pszMode, '+') && !strchr(pszMode, 'w'));
fFlushOnClose = fFlushOnCloseIn;
- if (strFilename.empty())
+ env = database.env;
+ if (database.IsDummy()) {
return;
+ }
+ const std::string &strFilename = database.strFile;
- bool fCreate = strchr(pszMode, 'c') != NULL;
+ bool fCreate = strchr(pszMode, 'c') != nullptr;
unsigned int nFlags = DB_THREAD;
if (fCreate)
nFlags |= DB_CREATE;
{
- LOCK(bitdb.cs_db);
- if (!bitdb.Open(GetDataDir()))
- throw runtime_error("CDB: Failed to open database environment.");
+ LOCK(cs_db);
+ if (!env->Open(false /* retry */))
+ throw std::runtime_error("BerkeleyBatch: Failed to open database environment.");
- strFile = strFilename;
- ++bitdb.mapFileUseCount[strFile];
- pdb = bitdb.mapDb[strFile];
- if (pdb == NULL) {
- pdb = new Db(bitdb.dbenv, 0);
+ pdb = env->mapDb[strFilename];
+ if (pdb == nullptr) {
+ int ret;
+ std::unique_ptr<Db> pdb_temp = MakeUnique<Db>(env->dbenv.get(), 0);
- bool fMockDb = bitdb.IsMock();
+ bool fMockDb = env->IsMock();
if (fMockDb) {
- DbMpoolFile* mpf = pdb->get_mpf();
+ DbMpoolFile* mpf = pdb_temp->get_mpf();
ret = mpf->set_flags(DB_MPOOL_NOFILE, 1);
- if (ret != 0)
- throw runtime_error(strprintf("CDB: Failed to configure for no temp file backing for database %s", strFile));
+ if (ret != 0) {
+ throw std::runtime_error(strprintf("BerkeleyBatch: Failed to configure for no temp file backing for database %s", strFilename));
+ }
}
- ret = pdb->open(NULL, // Txn pointer
- fMockDb ? NULL : strFile.c_str(), // Filename
- fMockDb ? strFile.c_str() : "main", // Logical db name
- DB_BTREE, // Database type
- nFlags, // Flags
+ ret = pdb_temp->open(nullptr, // Txn pointer
+ fMockDb ? nullptr : strFilename.c_str(), // Filename
+ fMockDb ? strFilename.c_str() : "main", // Logical db name
+ DB_BTREE, // Database type
+ nFlags, // Flags
0);
if (ret != 0) {
- delete pdb;
- pdb = NULL;
- --bitdb.mapFileUseCount[strFile];
- strFile = "";
- throw runtime_error(strprintf("CDB: Error %d, can't open database %s", ret, strFilename));
+ throw std::runtime_error(strprintf("BerkeleyBatch: Error %d, can't open database %s", ret, strFilename));
+ }
+
+ // Call CheckUniqueFileid on the containing BDB environment to
+ // avoid BDB data consistency bugs that happen when different data
+ // files in the same environment have the same fileid.
+ //
+ // Also call CheckUniqueFileid on all the other g_dbenvs to prevent
+ // bitcoin from opening the same data file through another
+ // environment when the file is referenced through equivalent but
+ // not obviously identical symlinked or hard linked or bind mounted
+ // paths. In the future a more relaxed check for equal inode and
+ // device ids could be done instead, which would allow opening
+ // different backup copies of a wallet at the same time. Maybe even
+ // more ideally, an exclusive lock for accessing the database could
+ // be implemented, so no equality checks are needed at all. (Newer
+ // versions of BDB have an set_lk_exclusive method for this
+ // purpose, but the older version we use does not.)
+ for (auto& env : g_dbenvs) {
+ CheckUniqueFileid(env.second, strFilename, *pdb_temp);
}
- if (fCreate && !Exists(string("version"))) {
+ pdb = pdb_temp.release();
+ env->mapDb[strFilename] = pdb;
+
+ if (fCreate && !Exists(std::string("version"))) {
bool fTmp = fReadOnly;
fReadOnly = false;
WriteVersion(CLIENT_VERSION);
fReadOnly = fTmp;
}
-
- bitdb.mapDb[strFile] = pdb;
}
+ ++env->mapFileUseCount[strFilename];
+ strFile = strFilename;
}
}
-void CDB::Flush()
+void BerkeleyBatch::Flush()
{
if (activeTxn)
return;
@@ -309,76 +532,77 @@ void CDB::Flush()
if (fReadOnly)
nMinutes = 1;
- bitdb.dbenv->txn_checkpoint(nMinutes ? GetArg("-dblogsize", DEFAULT_WALLET_DBLOGSIZE) * 1024 : 0, nMinutes, 0);
+ env->dbenv->txn_checkpoint(nMinutes ? gArgs.GetArg("-dblogsize", DEFAULT_WALLET_DBLOGSIZE) * 1024 : 0, nMinutes, 0);
}
-void CDB::Close()
+void BerkeleyDatabase::IncrementUpdateCounter()
+{
+ ++nUpdateCounter;
+}
+
+void BerkeleyBatch::Close()
{
if (!pdb)
return;
if (activeTxn)
activeTxn->abort();
- activeTxn = NULL;
- pdb = NULL;
+ activeTxn = nullptr;
+ pdb = nullptr;
if (fFlushOnClose)
Flush();
{
- LOCK(bitdb.cs_db);
- --bitdb.mapFileUseCount[strFile];
+ LOCK(cs_db);
+ --env->mapFileUseCount[strFile];
}
}
-void CDBEnv::CloseDb(const string& strFile)
+void BerkeleyEnvironment::CloseDb(const std::string& strFile)
{
{
LOCK(cs_db);
- if (mapDb[strFile] != NULL) {
+ if (mapDb[strFile] != nullptr) {
// Close the database handle
Db* pdb = mapDb[strFile];
pdb->close(0);
delete pdb;
- mapDb[strFile] = NULL;
+ mapDb[strFile] = nullptr;
}
}
}
-bool CDBEnv::RemoveDb(const string& strFile)
-{
- this->CloseDb(strFile);
-
- LOCK(cs_db);
- int rc = dbenv->dbremove(NULL, strFile.c_str(), NULL, DB_AUTO_COMMIT);
- return (rc == 0);
-}
-
-bool CDB::Rewrite(const string& strFile, const char* pszSkip)
+bool BerkeleyBatch::Rewrite(BerkeleyDatabase& database, const char* pszSkip)
{
+ if (database.IsDummy()) {
+ return true;
+ }
+ BerkeleyEnvironment *env = database.env;
+ const std::string& strFile = database.strFile;
while (true) {
{
- LOCK(bitdb.cs_db);
- if (!bitdb.mapFileUseCount.count(strFile) || bitdb.mapFileUseCount[strFile] == 0) {
+ LOCK(cs_db);
+ if (!env->mapFileUseCount.count(strFile) || env->mapFileUseCount[strFile] == 0) {
// Flush log data to the dat file
- bitdb.CloseDb(strFile);
- bitdb.CheckpointLSN(strFile);
- bitdb.mapFileUseCount.erase(strFile);
+ env->CloseDb(strFile);
+ env->CheckpointLSN(strFile);
+ env->mapFileUseCount.erase(strFile);
bool fSuccess = true;
- LogPrintf("CDB::Rewrite: Rewriting %s...\n", strFile);
- string strFileRes = strFile + ".rewrite";
+ LogPrintf("BerkeleyBatch::Rewrite: Rewriting %s...\n", strFile);
+ std::string strFileRes = strFile + ".rewrite";
{ // surround usage of db with extra {}
- CDB db(strFile.c_str(), "r");
- Db* pdbCopy = new Db(bitdb.dbenv, 0);
+ BerkeleyBatch db(database, "r");
+ std::unique_ptr<Db> pdbCopy = MakeUnique<Db>(env->dbenv.get(), 0);
- int ret = pdbCopy->open(NULL, // Txn pointer
+ int ret = pdbCopy->open(nullptr, // Txn pointer
strFileRes.c_str(), // Filename
"main", // Logical db name
DB_BTREE, // Database type
DB_CREATE, // Flags
0);
if (ret > 0) {
- LogPrintf("CDB::Rewrite: Can't create database file %s\n", strFileRes);
+ LogPrintf("BerkeleyBatch::Rewrite: Can't create database file %s\n", strFileRes);
fSuccess = false;
}
@@ -387,92 +611,191 @@ bool CDB::Rewrite(const string& strFile, const char* pszSkip)
while (fSuccess) {
CDataStream ssKey(SER_DISK, CLIENT_VERSION);
CDataStream ssValue(SER_DISK, CLIENT_VERSION);
- int ret = db.ReadAtCursor(pcursor, ssKey, ssValue, DB_NEXT);
- if (ret == DB_NOTFOUND) {
+ int ret1 = db.ReadAtCursor(pcursor, ssKey, ssValue);
+ if (ret1 == DB_NOTFOUND) {
pcursor->close();
break;
- } else if (ret != 0) {
+ } else if (ret1 != 0) {
pcursor->close();
fSuccess = false;
break;
}
if (pszSkip &&
- strncmp(&ssKey[0], pszSkip, std::min(ssKey.size(), strlen(pszSkip))) == 0)
+ strncmp(ssKey.data(), pszSkip, std::min(ssKey.size(), strlen(pszSkip))) == 0)
continue;
- if (strncmp(&ssKey[0], "\x07version", 8) == 0) {
+ if (strncmp(ssKey.data(), "\x07version", 8) == 0) {
// Update version:
ssValue.clear();
ssValue << CLIENT_VERSION;
}
- Dbt datKey(&ssKey[0], ssKey.size());
- Dbt datValue(&ssValue[0], ssValue.size());
- int ret2 = pdbCopy->put(NULL, &datKey, &datValue, DB_NOOVERWRITE);
+ Dbt datKey(ssKey.data(), ssKey.size());
+ Dbt datValue(ssValue.data(), ssValue.size());
+ int ret2 = pdbCopy->put(nullptr, &datKey, &datValue, DB_NOOVERWRITE);
if (ret2 > 0)
fSuccess = false;
}
if (fSuccess) {
db.Close();
- bitdb.CloseDb(strFile);
+ env->CloseDb(strFile);
if (pdbCopy->close(0))
fSuccess = false;
- delete pdbCopy;
+ } else {
+ pdbCopy->close(0);
}
}
if (fSuccess) {
- Db dbA(bitdb.dbenv, 0);
- if (dbA.remove(strFile.c_str(), NULL, 0))
+ Db dbA(env->dbenv.get(), 0);
+ if (dbA.remove(strFile.c_str(), nullptr, 0))
fSuccess = false;
- Db dbB(bitdb.dbenv, 0);
- if (dbB.rename(strFileRes.c_str(), NULL, strFile.c_str(), 0))
+ Db dbB(env->dbenv.get(), 0);
+ if (dbB.rename(strFileRes.c_str(), nullptr, strFile.c_str(), 0))
fSuccess = false;
}
if (!fSuccess)
- LogPrintf("CDB::Rewrite: Failed to rewrite database file %s\n", strFileRes);
+ LogPrintf("BerkeleyBatch::Rewrite: Failed to rewrite database file %s\n", strFileRes);
return fSuccess;
}
}
MilliSleep(100);
}
- return false;
}
-void CDBEnv::Flush(bool fShutdown)
+void BerkeleyEnvironment::Flush(bool fShutdown)
{
int64_t nStart = GetTimeMillis();
// Flush log data to the actual data file on all files that are not in use
- LogPrint("db", "CDBEnv::Flush: Flush(%s)%s\n", fShutdown ? "true" : "false", fDbEnvInit ? "" : " database not started");
+ LogPrint(BCLog::DB, "BerkeleyEnvironment::Flush: Flush(%s)%s\n", fShutdown ? "true" : "false", fDbEnvInit ? "" : " database not started");
if (!fDbEnvInit)
return;
{
LOCK(cs_db);
- map<string, int>::iterator mi = mapFileUseCount.begin();
+ std::map<std::string, int>::iterator mi = mapFileUseCount.begin();
while (mi != mapFileUseCount.end()) {
- string strFile = (*mi).first;
+ std::string strFile = (*mi).first;
int nRefCount = (*mi).second;
- LogPrint("db", "CDBEnv::Flush: Flushing %s (refcount = %d)...\n", strFile, nRefCount);
+ LogPrint(BCLog::DB, "BerkeleyEnvironment::Flush: Flushing %s (refcount = %d)...\n", strFile, nRefCount);
if (nRefCount == 0) {
// Move log data to the dat file
CloseDb(strFile);
- LogPrint("db", "CDBEnv::Flush: %s checkpoint\n", strFile);
+ LogPrint(BCLog::DB, "BerkeleyEnvironment::Flush: %s checkpoint\n", strFile);
dbenv->txn_checkpoint(0, 0, 0);
- LogPrint("db", "CDBEnv::Flush: %s detach\n", strFile);
+ LogPrint(BCLog::DB, "BerkeleyEnvironment::Flush: %s detach\n", strFile);
if (!fMockDb)
dbenv->lsn_reset(strFile.c_str(), 0);
- LogPrint("db", "CDBEnv::Flush: %s closed\n", strFile);
+ LogPrint(BCLog::DB, "BerkeleyEnvironment::Flush: %s closed\n", strFile);
mapFileUseCount.erase(mi++);
} else
mi++;
}
- LogPrint("db", "CDBEnv::Flush: Flush(%s)%s took %15dms\n", fShutdown ? "true" : "false", fDbEnvInit ? "" : " database not started", GetTimeMillis() - nStart);
+ LogPrint(BCLog::DB, "BerkeleyEnvironment::Flush: Flush(%s)%s took %15dms\n", fShutdown ? "true" : "false", fDbEnvInit ? "" : " database not started", GetTimeMillis() - nStart);
if (fShutdown) {
char** listp;
if (mapFileUseCount.empty()) {
dbenv->log_archive(&listp, DB_ARCH_REMOVE);
Close();
- if (!fMockDb)
- boost::filesystem::remove_all(boost::filesystem::path(strPath) / "database");
+ if (!fMockDb) {
+ fs::remove_all(fs::path(strPath) / "database");
+ }
+ g_dbenvs.erase(strPath);
}
}
}
}
+
+bool BerkeleyBatch::PeriodicFlush(BerkeleyDatabase& database)
+{
+ if (database.IsDummy()) {
+ return true;
+ }
+ bool ret = false;
+ BerkeleyEnvironment *env = database.env;
+ const std::string& strFile = database.strFile;
+ TRY_LOCK(cs_db, lockDb);
+ if (lockDb)
+ {
+ // Don't do this if any databases are in use
+ int nRefCount = 0;
+ std::map<std::string, int>::iterator mit = env->mapFileUseCount.begin();
+ while (mit != env->mapFileUseCount.end())
+ {
+ nRefCount += (*mit).second;
+ mit++;
+ }
+
+ if (nRefCount == 0)
+ {
+ boost::this_thread::interruption_point();
+ std::map<std::string, int>::iterator mi = env->mapFileUseCount.find(strFile);
+ if (mi != env->mapFileUseCount.end())
+ {
+ LogPrint(BCLog::DB, "Flushing %s\n", strFile);
+ int64_t nStart = GetTimeMillis();
+
+ // Flush wallet file so it's self contained
+ env->CloseDb(strFile);
+ env->CheckpointLSN(strFile);
+
+ env->mapFileUseCount.erase(mi++);
+ LogPrint(BCLog::DB, "Flushed %s %dms\n", strFile, GetTimeMillis() - nStart);
+ ret = true;
+ }
+ }
+ }
+
+ return ret;
+}
+
+bool BerkeleyDatabase::Rewrite(const char* pszSkip)
+{
+ return BerkeleyBatch::Rewrite(*this, pszSkip);
+}
+
+bool BerkeleyDatabase::Backup(const std::string& strDest)
+{
+ if (IsDummy()) {
+ return false;
+ }
+ while (true)
+ {
+ {
+ LOCK(cs_db);
+ if (!env->mapFileUseCount.count(strFile) || env->mapFileUseCount[strFile] == 0)
+ {
+ // Flush log data to the dat file
+ env->CloseDb(strFile);
+ env->CheckpointLSN(strFile);
+ env->mapFileUseCount.erase(strFile);
+
+ // Copy wallet file
+ fs::path pathSrc = env->Directory() / strFile;
+ fs::path pathDest(strDest);
+ if (fs::is_directory(pathDest))
+ pathDest /= strFile;
+
+ try {
+ if (fs::equivalent(pathSrc, pathDest)) {
+ LogPrintf("cannot backup to wallet source file %s\n", pathDest.string());
+ return false;
+ }
+
+ fs::copy_file(pathSrc, pathDest, fs::copy_option::overwrite_if_exists);
+ LogPrintf("copied %s to %s\n", strFile, pathDest.string());
+ return true;
+ } catch (const fs::filesystem_error& e) {
+ LogPrintf("error copying %s to %s - %s\n", strFile, pathDest.string(), e.what());
+ return false;
+ }
+ }
+ }
+ MilliSleep(100);
+ }
+}
+
+void BerkeleyDatabase::Flush(bool shutdown)
+{
+ if (!IsDummy()) {
+ env->Flush(shutdown);
+ if (shutdown) env = nullptr;
+ }
+}
diff --git a/src/wallet/db.h b/src/wallet/db.h
index 01b8c71a04..b078edab7b 100644
--- a/src/wallet/db.h
+++ b/src/wallet/db.h
@@ -1,53 +1,52 @@
// Copyright (c) 2009-2010 Satoshi Nakamoto
-// Copyright (c) 2009-2015 The Bitcoin Core developers
+// Copyright (c) 2009-2018 The Bitcoin Core developers
// Distributed under the MIT software license, see the accompanying
// file COPYING or http://www.opensource.org/licenses/mit-license.php.
#ifndef BITCOIN_WALLET_DB_H
#define BITCOIN_WALLET_DB_H
-#include "clientversion.h"
-#include "serialize.h"
-#include "streams.h"
-#include "sync.h"
-#include "version.h"
+#include <clientversion.h>
+#include <fs.h>
+#include <serialize.h>
+#include <streams.h>
+#include <sync.h>
+#include <util.h>
+#include <version.h>
+#include <atomic>
#include <map>
+#include <memory>
#include <string>
#include <vector>
-#include <boost/filesystem/path.hpp>
-
#include <db_cxx.h>
static const unsigned int DEFAULT_WALLET_DBLOGSIZE = 100;
static const bool DEFAULT_WALLET_PRIVDB = true;
-extern unsigned int nWalletDBUpdated;
-
-class CDBEnv
+class BerkeleyEnvironment
{
private:
bool fDbEnvInit;
bool fMockDb;
- // Don't change into boost::filesystem::path, as that can result in
+ // Don't change into fs::path, as that can result in
// shutdown problems/crashes caused by a static initialized internal pointer.
std::string strPath;
- void EnvShutdown();
-
public:
- mutable CCriticalSection cs_db;
- DbEnv *dbenv;
+ std::unique_ptr<DbEnv> dbenv;
std::map<std::string, int> mapFileUseCount;
std::map<std::string, Db*> mapDb;
- CDBEnv();
- ~CDBEnv();
+ BerkeleyEnvironment(const fs::path& env_directory);
+ ~BerkeleyEnvironment();
void Reset();
void MakeMock();
- bool IsMock() { return fMockDb; }
+ bool IsMock() const { return fMockDb; }
+ bool IsInitialized() const { return fDbEnvInit; }
+ fs::path Directory() const { return strPath; }
/**
* Verify that database file strFile is OK. If it is not,
@@ -55,10 +54,11 @@ public:
* This must be called BEFORE strFile is opened.
* Returns true if strFile is OK.
*/
- enum VerifyResult { VERIFY_OK,
+ enum class VerifyResult { VERIFY_OK,
RECOVER_OK,
RECOVER_FAIL };
- VerifyResult Verify(const std::string& strFile, bool (*recoverFunc)(CDBEnv& dbenv, const std::string& strFile));
+ typedef bool (*recoverFunc_type)(const fs::path& file_path, std::string& out_backup_filename);
+ VerifyResult Verify(const std::string& strFile, recoverFunc_type recoverFunc, std::string& out_backup_filename);
/**
* Salvage data from a file that Verify says is bad.
* fAggressive sets the DB_AGGRESSIVE flag (see berkeley DB->verify() method documentation).
@@ -69,29 +69,102 @@ public:
typedef std::pair<std::vector<unsigned char>, std::vector<unsigned char> > KeyValPair;
bool Salvage(const std::string& strFile, bool fAggressive, std::vector<KeyValPair>& vResult);
- bool Open(const boost::filesystem::path& path);
+ bool Open(bool retry);
void Close();
void Flush(bool fShutdown);
void CheckpointLSN(const std::string& strFile);
void CloseDb(const std::string& strFile);
- bool RemoveDb(const std::string& strFile);
DbTxn* TxnBegin(int flags = DB_TXN_WRITE_NOSYNC)
{
- DbTxn* ptxn = NULL;
- int ret = dbenv->txn_begin(NULL, &ptxn, flags);
+ DbTxn* ptxn = nullptr;
+ int ret = dbenv->txn_begin(nullptr, &ptxn, flags);
if (!ptxn || ret != 0)
- return NULL;
+ return nullptr;
return ptxn;
}
};
-extern CDBEnv bitdb;
+/** Get BerkeleyEnvironment and database filename given a wallet path. */
+BerkeleyEnvironment* GetWalletEnv(const fs::path& wallet_path, std::string& database_filename);
+
+/** An instance of this class represents one database.
+ * For BerkeleyDB this is just a (env, strFile) tuple.
+ **/
+class BerkeleyDatabase
+{
+ friend class BerkeleyBatch;
+public:
+ /** Create dummy DB handle */
+ BerkeleyDatabase() : nUpdateCounter(0), nLastSeen(0), nLastFlushed(0), nLastWalletUpdate(0), env(nullptr)
+ {
+ }
+
+ /** Create DB handle to real database */
+ BerkeleyDatabase(const fs::path& wallet_path, bool mock = false) :
+ nUpdateCounter(0), nLastSeen(0), nLastFlushed(0), nLastWalletUpdate(0)
+ {
+ env = GetWalletEnv(wallet_path, strFile);
+ if (mock) {
+ env->Close();
+ env->Reset();
+ env->MakeMock();
+ }
+ }
+
+ /** Return object for accessing database at specified path. */
+ static std::unique_ptr<BerkeleyDatabase> Create(const fs::path& path)
+ {
+ return MakeUnique<BerkeleyDatabase>(path);
+ }
+
+ /** Return object for accessing dummy database with no read/write capabilities. */
+ static std::unique_ptr<BerkeleyDatabase> CreateDummy()
+ {
+ return MakeUnique<BerkeleyDatabase>();
+ }
+
+ /** Return object for accessing temporary in-memory database. */
+ static std::unique_ptr<BerkeleyDatabase> CreateMock()
+ {
+ return MakeUnique<BerkeleyDatabase>("", true /* mock */);
+ }
+
+ /** Rewrite the entire database on disk, with the exception of key pszSkip if non-zero
+ */
+ bool Rewrite(const char* pszSkip=nullptr);
+
+ /** Back up the entire database to a file.
+ */
+ bool Backup(const std::string& strDest);
+
+ /** Make sure all changes are flushed to disk.
+ */
+ void Flush(bool shutdown);
+
+ void IncrementUpdateCounter();
+
+ std::atomic<unsigned int> nUpdateCounter;
+ unsigned int nLastSeen;
+ unsigned int nLastFlushed;
+ int64_t nLastWalletUpdate;
+
+private:
+ /** BerkeleyDB specific */
+ BerkeleyEnvironment *env;
+ std::string strFile;
+
+ /** Return whether this database handle is a dummy for testing.
+ * Only to be used at a low level, application should ideally not care
+ * about this.
+ */
+ bool IsDummy() { return env == nullptr; }
+};
/** RAII class that provides access to a Berkeley database */
-class CDB
+class BerkeleyBatch
{
protected:
Db* pdb;
@@ -99,19 +172,28 @@ protected:
DbTxn* activeTxn;
bool fReadOnly;
bool fFlushOnClose;
-
- explicit CDB(const std::string& strFilename, const char* pszMode = "r+", bool fFlushOnCloseIn=true);
- ~CDB() { Close(); }
+ BerkeleyEnvironment *env;
public:
+ explicit BerkeleyBatch(BerkeleyDatabase& database, const char* pszMode = "r+", bool fFlushOnCloseIn=true);
+ ~BerkeleyBatch() { Close(); }
+
+ BerkeleyBatch(const BerkeleyBatch&) = delete;
+ BerkeleyBatch& operator=(const BerkeleyBatch&) = delete;
+
void Flush();
void Close();
+ static bool Recover(const fs::path& file_path, void *callbackDataIn, bool (*recoverKVcallback)(void* callbackData, CDataStream ssKey, CDataStream ssValue), std::string& out_backup_filename);
-private:
- CDB(const CDB&);
- void operator=(const CDB&);
+ /* flush the wallet passively (TRY_LOCK)
+ ideal to be called periodically */
+ static bool PeriodicFlush(BerkeleyDatabase& database);
+ /* verifies the database environment */
+ static bool VerifyEnvironment(const fs::path& file_path, std::string& errorStr);
+ /* verifies the database file */
+ static bool VerifyDatabaseFile(const fs::path& file_path, std::string& warningStr, std::string& errorStr, BerkeleyEnvironment::recoverFunc_type recoverFunc);
-protected:
+public:
template <typename K, typename T>
bool Read(const K& key, T& value)
{
@@ -122,35 +204,36 @@ protected:
CDataStream ssKey(SER_DISK, CLIENT_VERSION);
ssKey.reserve(1000);
ssKey << key;
- Dbt datKey(&ssKey[0], ssKey.size());
+ Dbt datKey(ssKey.data(), ssKey.size());
// Read
Dbt datValue;
datValue.set_flags(DB_DBT_MALLOC);
int ret = pdb->get(activeTxn, &datKey, &datValue, 0);
- memset(datKey.get_data(), 0, datKey.get_size());
- if (datValue.get_data() == NULL)
- return false;
-
- // Unserialize value
- try {
- CDataStream ssValue((char*)datValue.get_data(), (char*)datValue.get_data() + datValue.get_size(), SER_DISK, CLIENT_VERSION);
- ssValue >> value;
- } catch (const std::exception&) {
- return false;
+ memory_cleanse(datKey.get_data(), datKey.get_size());
+ bool success = false;
+ if (datValue.get_data() != nullptr) {
+ // Unserialize value
+ try {
+ CDataStream ssValue((char*)datValue.get_data(), (char*)datValue.get_data() + datValue.get_size(), SER_DISK, CLIENT_VERSION);
+ ssValue >> value;
+ success = true;
+ } catch (const std::exception&) {
+ // In this case success remains 'false'
+ }
+
+ // Clear and free memory
+ memory_cleanse(datValue.get_data(), datValue.get_size());
+ free(datValue.get_data());
}
-
- // Clear and free memory
- memset(datValue.get_data(), 0, datValue.get_size());
- free(datValue.get_data());
- return (ret == 0);
+ return ret == 0 && success;
}
template <typename K, typename T>
bool Write(const K& key, const T& value, bool fOverwrite = true)
{
if (!pdb)
- return false;
+ return true;
if (fReadOnly)
assert(!"Write called on database in read-only mode");
@@ -158,20 +241,20 @@ protected:
CDataStream ssKey(SER_DISK, CLIENT_VERSION);
ssKey.reserve(1000);
ssKey << key;
- Dbt datKey(&ssKey[0], ssKey.size());
+ Dbt datKey(ssKey.data(), ssKey.size());
// Value
CDataStream ssValue(SER_DISK, CLIENT_VERSION);
ssValue.reserve(10000);
ssValue << value;
- Dbt datValue(&ssValue[0], ssValue.size());
+ Dbt datValue(ssValue.data(), ssValue.size());
// Write
int ret = pdb->put(activeTxn, &datKey, &datValue, (fOverwrite ? 0 : DB_NOOVERWRITE));
// Clear memory in case it was a private key
- memset(datKey.get_data(), 0, datKey.get_size());
- memset(datValue.get_data(), 0, datValue.get_size());
+ memory_cleanse(datKey.get_data(), datKey.get_size());
+ memory_cleanse(datValue.get_data(), datValue.get_size());
return (ret == 0);
}
@@ -187,13 +270,13 @@ protected:
CDataStream ssKey(SER_DISK, CLIENT_VERSION);
ssKey.reserve(1000);
ssKey << key;
- Dbt datKey(&ssKey[0], ssKey.size());
+ Dbt datKey(ssKey.data(), ssKey.size());
// Erase
int ret = pdb->del(activeTxn, &datKey, 0);
// Clear memory
- memset(datKey.get_data(), 0, datKey.get_size());
+ memory_cleanse(datKey.get_data(), datKey.get_size());
return (ret == 0 || ret == DB_NOTFOUND);
}
@@ -207,46 +290,44 @@ protected:
CDataStream ssKey(SER_DISK, CLIENT_VERSION);
ssKey.reserve(1000);
ssKey << key;
- Dbt datKey(&ssKey[0], ssKey.size());
+ Dbt datKey(ssKey.data(), ssKey.size());
// Exists
int ret = pdb->exists(activeTxn, &datKey, 0);
// Clear memory
- memset(datKey.get_data(), 0, datKey.get_size());
+ memory_cleanse(datKey.get_data(), datKey.get_size());
return (ret == 0);
}
Dbc* GetCursor()
{
if (!pdb)
- return NULL;
- Dbc* pcursor = NULL;
- int ret = pdb->cursor(NULL, &pcursor, 0);
+ return nullptr;
+ Dbc* pcursor = nullptr;
+ int ret = pdb->cursor(nullptr, &pcursor, 0);
if (ret != 0)
- return NULL;
+ return nullptr;
return pcursor;
}
- int ReadAtCursor(Dbc* pcursor, CDataStream& ssKey, CDataStream& ssValue, unsigned int fFlags = DB_NEXT)
+ int ReadAtCursor(Dbc* pcursor, CDataStream& ssKey, CDataStream& ssValue, bool setRange = false)
{
// Read at cursor
Dbt datKey;
- if (fFlags == DB_SET || fFlags == DB_SET_RANGE || fFlags == DB_GET_BOTH || fFlags == DB_GET_BOTH_RANGE) {
- datKey.set_data(&ssKey[0]);
+ unsigned int fFlags = DB_NEXT;
+ if (setRange) {
+ datKey.set_data(ssKey.data());
datKey.set_size(ssKey.size());
+ fFlags = DB_SET_RANGE;
}
Dbt datValue;
- if (fFlags == DB_GET_BOTH || fFlags == DB_GET_BOTH_RANGE) {
- datValue.set_data(&ssValue[0]);
- datValue.set_size(ssValue.size());
- }
datKey.set_flags(DB_DBT_MALLOC);
datValue.set_flags(DB_DBT_MALLOC);
int ret = pcursor->get(&datKey, &datValue, fFlags);
if (ret != 0)
return ret;
- else if (datKey.get_data() == NULL || datValue.get_data() == NULL)
+ else if (datKey.get_data() == nullptr || datValue.get_data() == nullptr)
return 99999;
// Convert to streams
@@ -258,8 +339,8 @@ protected:
ssValue.write((char*)datValue.get_data(), datValue.get_size());
// Clear and free memory
- memset(datKey.get_data(), 0, datKey.get_size());
- memset(datValue.get_data(), 0, datValue.get_size());
+ memory_cleanse(datKey.get_data(), datKey.get_size());
+ memory_cleanse(datValue.get_data(), datValue.get_size());
free(datKey.get_data());
free(datValue.get_data());
return 0;
@@ -270,7 +351,7 @@ public:
{
if (!pdb || activeTxn)
return false;
- DbTxn* ptxn = bitdb.TxnBegin();
+ DbTxn* ptxn = env->TxnBegin();
if (!ptxn)
return false;
activeTxn = ptxn;
@@ -282,7 +363,7 @@ public:
if (!pdb || !activeTxn)
return false;
int ret = activeTxn->commit(0);
- activeTxn = NULL;
+ activeTxn = nullptr;
return (ret == 0);
}
@@ -291,7 +372,7 @@ public:
if (!pdb || !activeTxn)
return false;
int ret = activeTxn->abort();
- activeTxn = NULL;
+ activeTxn = nullptr;
return (ret == 0);
}
@@ -306,7 +387,7 @@ public:
return Write(std::string("version"), nVersion);
}
- bool static Rewrite(const std::string& strFile, const char* pszSkip = NULL);
+ bool static Rewrite(BerkeleyDatabase& database, const char* pszSkip = nullptr);
};
#endif // BITCOIN_WALLET_DB_H
diff --git a/src/wallet/feebumper.cpp b/src/wallet/feebumper.cpp
new file mode 100644
index 0000000000..d5d4021305
--- /dev/null
+++ b/src/wallet/feebumper.cpp
@@ -0,0 +1,269 @@
+// Copyright (c) 2017-2018 The Bitcoin Core developers
+// Distributed under the MIT software license, see the accompanying
+// file COPYING or http://www.opensource.org/licenses/mit-license.php.
+
+#include <consensus/validation.h>
+#include <wallet/coincontrol.h>
+#include <wallet/feebumper.h>
+#include <wallet/fees.h>
+#include <wallet/wallet.h>
+#include <policy/fees.h>
+#include <policy/policy.h>
+#include <policy/rbf.h>
+#include <validation.h> //for mempool access
+#include <txmempool.h>
+#include <utilmoneystr.h>
+#include <util.h>
+#include <net.h>
+
+//! Check whether transaction has descendant in wallet or mempool, or has been
+//! mined, or conflicts with a mined transaction. Return a feebumper::Result.
+static feebumper::Result PreconditionChecks(const CWallet* wallet, const CWalletTx& wtx, std::vector<std::string>& errors) EXCLUSIVE_LOCKS_REQUIRED(wallet->cs_wallet)
+{
+ if (wallet->HasWalletSpend(wtx.GetHash())) {
+ errors.push_back("Transaction has descendants in the wallet");
+ return feebumper::Result::INVALID_PARAMETER;
+ }
+
+ {
+ LOCK(mempool.cs);
+ auto it_mp = mempool.mapTx.find(wtx.GetHash());
+ if (it_mp != mempool.mapTx.end() && it_mp->GetCountWithDescendants() > 1) {
+ errors.push_back("Transaction has descendants in the mempool");
+ return feebumper::Result::INVALID_PARAMETER;
+ }
+ }
+
+ if (wtx.GetDepthInMainChain() != 0) {
+ errors.push_back("Transaction has been mined, or is conflicted with a mined transaction");
+ return feebumper::Result::WALLET_ERROR;
+ }
+
+ if (!SignalsOptInRBF(*wtx.tx)) {
+ errors.push_back("Transaction is not BIP 125 replaceable");
+ return feebumper::Result::WALLET_ERROR;
+ }
+
+ if (wtx.mapValue.count("replaced_by_txid")) {
+ errors.push_back(strprintf("Cannot bump transaction %s which was already bumped by transaction %s", wtx.GetHash().ToString(), wtx.mapValue.at("replaced_by_txid")));
+ return feebumper::Result::WALLET_ERROR;
+ }
+
+ // check that original tx consists entirely of our inputs
+ // if not, we can't bump the fee, because the wallet has no way of knowing the value of the other inputs (thus the fee)
+ if (!wallet->IsAllFromMe(*wtx.tx, ISMINE_SPENDABLE)) {
+ errors.push_back("Transaction contains inputs that don't belong to this wallet");
+ return feebumper::Result::WALLET_ERROR;
+ }
+
+
+ return feebumper::Result::OK;
+}
+
+namespace feebumper {
+
+bool TransactionCanBeBumped(const CWallet* wallet, const uint256& txid)
+{
+ LOCK2(cs_main, wallet->cs_wallet);
+ const CWalletTx* wtx = wallet->GetWalletTx(txid);
+ if (wtx == nullptr) return false;
+
+ std::vector<std::string> errors_dummy;
+ feebumper::Result res = PreconditionChecks(wallet, *wtx, errors_dummy);
+ return res == feebumper::Result::OK;
+}
+
+Result CreateTransaction(const CWallet* wallet, const uint256& txid, const CCoinControl& coin_control, CAmount total_fee, std::vector<std::string>& errors,
+ CAmount& old_fee, CAmount& new_fee, CMutableTransaction& mtx)
+{
+ LOCK2(cs_main, wallet->cs_wallet);
+ errors.clear();
+ auto it = wallet->mapWallet.find(txid);
+ if (it == wallet->mapWallet.end()) {
+ errors.push_back("Invalid or non-wallet transaction id");
+ return Result::INVALID_ADDRESS_OR_KEY;
+ }
+ const CWalletTx& wtx = it->second;
+
+ Result result = PreconditionChecks(wallet, wtx, errors);
+ if (result != Result::OK) {
+ return result;
+ }
+
+ // figure out which output was change
+ // if there was no change output or multiple change outputs, fail
+ int nOutput = -1;
+ for (size_t i = 0; i < wtx.tx->vout.size(); ++i) {
+ if (wallet->IsChange(wtx.tx->vout[i])) {
+ if (nOutput != -1) {
+ errors.push_back("Transaction has multiple change outputs");
+ return Result::WALLET_ERROR;
+ }
+ nOutput = i;
+ }
+ }
+ if (nOutput == -1) {
+ errors.push_back("Transaction does not have a change output");
+ return Result::WALLET_ERROR;
+ }
+
+ // Calculate the expected size of the new transaction.
+ int64_t txSize = GetVirtualTransactionSize(*(wtx.tx));
+ const int64_t maxNewTxSize = CalculateMaximumSignedTxSize(*wtx.tx, wallet);
+ if (maxNewTxSize < 0) {
+ errors.push_back("Transaction contains inputs that cannot be signed");
+ return Result::INVALID_ADDRESS_OR_KEY;
+ }
+
+ // calculate the old fee and fee-rate
+ old_fee = wtx.GetDebit(ISMINE_SPENDABLE) - wtx.tx->GetValueOut();
+ CFeeRate nOldFeeRate(old_fee, txSize);
+ CFeeRate nNewFeeRate;
+ // The wallet uses a conservative WALLET_INCREMENTAL_RELAY_FEE value to
+ // future proof against changes to network wide policy for incremental relay
+ // fee that our node may not be aware of.
+ CFeeRate walletIncrementalRelayFee = CFeeRate(WALLET_INCREMENTAL_RELAY_FEE);
+ if (::incrementalRelayFee > walletIncrementalRelayFee) {
+ walletIncrementalRelayFee = ::incrementalRelayFee;
+ }
+
+ if (total_fee > 0) {
+ CAmount minTotalFee = nOldFeeRate.GetFee(maxNewTxSize) + ::incrementalRelayFee.GetFee(maxNewTxSize);
+ if (total_fee < minTotalFee) {
+ errors.push_back(strprintf("Insufficient totalFee, must be at least %s (oldFee %s + incrementalFee %s)",
+ FormatMoney(minTotalFee), FormatMoney(nOldFeeRate.GetFee(maxNewTxSize)), FormatMoney(::incrementalRelayFee.GetFee(maxNewTxSize))));
+ return Result::INVALID_PARAMETER;
+ }
+ CAmount requiredFee = GetRequiredFee(*wallet, maxNewTxSize);
+ if (total_fee < requiredFee) {
+ errors.push_back(strprintf("Insufficient totalFee (cannot be less than required fee %s)",
+ FormatMoney(requiredFee)));
+ return Result::INVALID_PARAMETER;
+ }
+ new_fee = total_fee;
+ nNewFeeRate = CFeeRate(total_fee, maxNewTxSize);
+ } else {
+ new_fee = GetMinimumFee(*wallet, maxNewTxSize, coin_control, mempool, ::feeEstimator, nullptr /* FeeCalculation */);
+ nNewFeeRate = CFeeRate(new_fee, maxNewTxSize);
+
+ // New fee rate must be at least old rate + minimum incremental relay rate
+ // walletIncrementalRelayFee.GetFeePerK() should be exact, because it's initialized
+ // in that unit (fee per kb).
+ // However, nOldFeeRate is a calculated value from the tx fee/size, so
+ // add 1 satoshi to the result, because it may have been rounded down.
+ if (nNewFeeRate.GetFeePerK() < nOldFeeRate.GetFeePerK() + 1 + walletIncrementalRelayFee.GetFeePerK()) {
+ nNewFeeRate = CFeeRate(nOldFeeRate.GetFeePerK() + 1 + walletIncrementalRelayFee.GetFeePerK());
+ new_fee = nNewFeeRate.GetFee(maxNewTxSize);
+ }
+ }
+
+ // Check that in all cases the new fee doesn't violate maxTxFee
+ if (new_fee > maxTxFee) {
+ errors.push_back(strprintf("Specified or calculated fee %s is too high (cannot be higher than maxTxFee %s)",
+ FormatMoney(new_fee), FormatMoney(maxTxFee)));
+ return Result::WALLET_ERROR;
+ }
+
+ // check that fee rate is higher than mempool's minimum fee
+ // (no point in bumping fee if we know that the new tx won't be accepted to the mempool)
+ // This may occur if the user set TotalFee or paytxfee too low, if fallbackfee is too low, or, perhaps,
+ // in a rare situation where the mempool minimum fee increased significantly since the fee estimation just a
+ // moment earlier. In this case, we report an error to the user, who may use total_fee to make an adjustment.
+ CFeeRate minMempoolFeeRate = mempool.GetMinFee(gArgs.GetArg("-maxmempool", DEFAULT_MAX_MEMPOOL_SIZE) * 1000000);
+ if (nNewFeeRate.GetFeePerK() < minMempoolFeeRate.GetFeePerK()) {
+ errors.push_back(strprintf(
+ "New fee rate (%s) is lower than the minimum fee rate (%s) to get into the mempool -- "
+ "the totalFee value should be at least %s or the settxfee value should be at least %s to add transaction",
+ FormatMoney(nNewFeeRate.GetFeePerK()),
+ FormatMoney(minMempoolFeeRate.GetFeePerK()),
+ FormatMoney(minMempoolFeeRate.GetFee(maxNewTxSize)),
+ FormatMoney(minMempoolFeeRate.GetFeePerK())));
+ return Result::WALLET_ERROR;
+ }
+
+ // Now modify the output to increase the fee.
+ // If the output is not large enough to pay the fee, fail.
+ CAmount nDelta = new_fee - old_fee;
+ assert(nDelta > 0);
+ mtx = CMutableTransaction{*wtx.tx};
+ CTxOut* poutput = &(mtx.vout[nOutput]);
+ if (poutput->nValue < nDelta) {
+ errors.push_back("Change output is too small to bump the fee");
+ return Result::WALLET_ERROR;
+ }
+
+ // If the output would become dust, discard it (converting the dust to fee)
+ poutput->nValue -= nDelta;
+ if (poutput->nValue <= GetDustThreshold(*poutput, GetDiscardRate(*wallet, ::feeEstimator))) {
+ wallet->WalletLogPrintf("Bumping fee and discarding dust output\n");
+ new_fee += poutput->nValue;
+ mtx.vout.erase(mtx.vout.begin() + nOutput);
+ }
+
+ // Mark new tx not replaceable, if requested.
+ if (!coin_control.m_signal_bip125_rbf.get_value_or(wallet->m_signal_rbf)) {
+ for (auto& input : mtx.vin) {
+ if (input.nSequence < 0xfffffffe) input.nSequence = 0xfffffffe;
+ }
+ }
+
+
+ return Result::OK;
+}
+
+bool SignTransaction(CWallet* wallet, CMutableTransaction& mtx) {
+ LOCK2(cs_main, wallet->cs_wallet);
+ return wallet->SignTransaction(mtx);
+}
+
+Result CommitTransaction(CWallet* wallet, const uint256& txid, CMutableTransaction&& mtx, std::vector<std::string>& errors, uint256& bumped_txid)
+{
+ LOCK2(cs_main, wallet->cs_wallet);
+ if (!errors.empty()) {
+ return Result::MISC_ERROR;
+ }
+ auto it = txid.IsNull() ? wallet->mapWallet.end() : wallet->mapWallet.find(txid);
+ if (it == wallet->mapWallet.end()) {
+ errors.push_back("Invalid or non-wallet transaction id");
+ return Result::MISC_ERROR;
+ }
+ CWalletTx& oldWtx = it->second;
+
+ // make sure the transaction still has no descendants and hasn't been mined in the meantime
+ Result result = PreconditionChecks(wallet, oldWtx, errors);
+ if (result != Result::OK) {
+ return result;
+ }
+
+ // commit/broadcast the tx
+ CTransactionRef tx = MakeTransactionRef(std::move(mtx));
+ mapValue_t mapValue = oldWtx.mapValue;
+ mapValue["replaces_txid"] = oldWtx.GetHash().ToString();
+
+ CReserveKey reservekey(wallet);
+ CValidationState state;
+ if (!wallet->CommitTransaction(tx, std::move(mapValue), oldWtx.vOrderForm, oldWtx.strFromAccount, reservekey, g_connman.get(), state)) {
+ // NOTE: CommitTransaction never returns false, so this should never happen.
+ errors.push_back(strprintf("The transaction was rejected: %s", FormatStateMessage(state)));
+ return Result::WALLET_ERROR;
+ }
+
+ bumped_txid = tx->GetHash();
+ if (state.IsInvalid()) {
+ // This can happen if the mempool rejected the transaction. Report
+ // what happened in the "errors" response.
+ errors.push_back(strprintf("Error: The transaction was rejected: %s", FormatStateMessage(state)));
+ }
+
+ // mark the original tx as bumped
+ if (!wallet->MarkReplaced(oldWtx.GetHash(), bumped_txid)) {
+ // TODO: see if JSON-RPC has a standard way of returning a response
+ // along with an exception. It would be good to return information about
+ // wtxBumped to the caller even if marking the original transaction
+ // replaced does not succeed for some reason.
+ errors.push_back("Created new bumpfee transaction but could not mark the original transaction as replaced");
+ }
+ return Result::OK;
+}
+
+} // namespace feebumper
diff --git a/src/wallet/feebumper.h b/src/wallet/feebumper.h
new file mode 100644
index 0000000000..a1eb5d0e0d
--- /dev/null
+++ b/src/wallet/feebumper.h
@@ -0,0 +1,58 @@
+// Copyright (c) 2017-2018 The Bitcoin Core developers
+// Distributed under the MIT software license, see the accompanying
+// file COPYING or http://www.opensource.org/licenses/mit-license.php.
+
+#ifndef BITCOIN_WALLET_FEEBUMPER_H
+#define BITCOIN_WALLET_FEEBUMPER_H
+
+#include <primitives/transaction.h>
+
+class CWallet;
+class CWalletTx;
+class uint256;
+class CCoinControl;
+enum class FeeEstimateMode;
+
+namespace feebumper {
+
+enum class Result
+{
+ OK,
+ INVALID_ADDRESS_OR_KEY,
+ INVALID_REQUEST,
+ INVALID_PARAMETER,
+ WALLET_ERROR,
+ MISC_ERROR,
+};
+
+//! Return whether transaction can be bumped.
+bool TransactionCanBeBumped(const CWallet* wallet, const uint256& txid);
+
+//! Create bumpfee transaction.
+Result CreateTransaction(const CWallet* wallet,
+ const uint256& txid,
+ const CCoinControl& coin_control,
+ CAmount total_fee,
+ std::vector<std::string>& errors,
+ CAmount& old_fee,
+ CAmount& new_fee,
+ CMutableTransaction& mtx);
+
+//! Sign the new transaction,
+//! @return false if the tx couldn't be found or if it was
+//! impossible to create the signature(s)
+bool SignTransaction(CWallet* wallet, CMutableTransaction& mtx);
+
+//! Commit the bumpfee transaction.
+//! @return success in case of CWallet::CommitTransaction was successful,
+//! but sets errors if the tx could not be added to the mempool (will try later)
+//! or if the old transaction could not be marked as replaced.
+Result CommitTransaction(CWallet* wallet,
+ const uint256& txid,
+ CMutableTransaction&& mtx,
+ std::vector<std::string>& errors,
+ uint256& bumped_txid);
+
+} // namespace feebumper
+
+#endif // BITCOIN_WALLET_FEEBUMPER_H
diff --git a/src/wallet/fees.cpp b/src/wallet/fees.cpp
new file mode 100644
index 0000000000..d620e25f2b
--- /dev/null
+++ b/src/wallet/fees.cpp
@@ -0,0 +1,102 @@
+// Copyright (c) 2009-2010 Satoshi Nakamoto
+// Copyright (c) 2009-2018 The Bitcoin Core developers
+// Distributed under the MIT software license, see the accompanying
+// file COPYING or http://www.opensource.org/licenses/mit-license.php.
+
+#include <wallet/fees.h>
+
+#include <policy/policy.h>
+#include <txmempool.h>
+#include <util.h>
+#include <validation.h>
+#include <wallet/coincontrol.h>
+#include <wallet/wallet.h>
+
+
+CAmount GetRequiredFee(const CWallet& wallet, unsigned int nTxBytes)
+{
+ return GetRequiredFeeRate(wallet).GetFee(nTxBytes);
+}
+
+
+CAmount GetMinimumFee(const CWallet& wallet, unsigned int nTxBytes, const CCoinControl& coin_control, const CTxMemPool& pool, const CBlockPolicyEstimator& estimator, FeeCalculation* feeCalc)
+{
+ CAmount fee_needed = GetMinimumFeeRate(wallet, coin_control, pool, estimator, feeCalc).GetFee(nTxBytes);
+ // Always obey the maximum
+ if (fee_needed > maxTxFee) {
+ fee_needed = maxTxFee;
+ if (feeCalc) feeCalc->reason = FeeReason::MAXTXFEE;
+ }
+ return fee_needed;
+}
+
+CFeeRate GetRequiredFeeRate(const CWallet& wallet)
+{
+ return std::max(wallet.m_min_fee, ::minRelayTxFee);
+}
+
+CFeeRate GetMinimumFeeRate(const CWallet& wallet, const CCoinControl& coin_control, const CTxMemPool& pool, const CBlockPolicyEstimator& estimator, FeeCalculation* feeCalc)
+{
+ /* User control of how to calculate fee uses the following parameter precedence:
+ 1. coin_control.m_feerate
+ 2. coin_control.m_confirm_target
+ 3. m_pay_tx_fee (user-set member variable of wallet)
+ 4. m_confirm_target (user-set member variable of wallet)
+ The first parameter that is set is used.
+ */
+ CFeeRate feerate_needed;
+ if (coin_control.m_feerate) { // 1.
+ feerate_needed = *(coin_control.m_feerate);
+ if (feeCalc) feeCalc->reason = FeeReason::PAYTXFEE;
+ // Allow to override automatic min/max check over coin control instance
+ if (coin_control.fOverrideFeeRate) return feerate_needed;
+ }
+ else if (!coin_control.m_confirm_target && wallet.m_pay_tx_fee != CFeeRate(0)) { // 3. TODO: remove magic value of 0 for wallet member m_pay_tx_fee
+ feerate_needed = wallet.m_pay_tx_fee;
+ if (feeCalc) feeCalc->reason = FeeReason::PAYTXFEE;
+ }
+ else { // 2. or 4.
+ // We will use smart fee estimation
+ unsigned int target = coin_control.m_confirm_target ? *coin_control.m_confirm_target : wallet.m_confirm_target;
+ // By default estimates are economical iff we are signaling opt-in-RBF
+ bool conservative_estimate = !coin_control.m_signal_bip125_rbf.get_value_or(wallet.m_signal_rbf);
+ // Allow to override the default fee estimate mode over the CoinControl instance
+ if (coin_control.m_fee_mode == FeeEstimateMode::CONSERVATIVE) conservative_estimate = true;
+ else if (coin_control.m_fee_mode == FeeEstimateMode::ECONOMICAL) conservative_estimate = false;
+
+ feerate_needed = estimator.estimateSmartFee(target, feeCalc, conservative_estimate);
+ if (feerate_needed == CFeeRate(0)) {
+ // if we don't have enough data for estimateSmartFee, then use fallback fee
+ feerate_needed = wallet.m_fallback_fee;
+ if (feeCalc) feeCalc->reason = FeeReason::FALLBACK;
+
+ // directly return if fallback fee is disabled (feerate 0 == disabled)
+ if (wallet.m_fallback_fee == CFeeRate(0)) return feerate_needed;
+ }
+ // Obey mempool min fee when using smart fee estimation
+ CFeeRate min_mempool_feerate = pool.GetMinFee(gArgs.GetArg("-maxmempool", DEFAULT_MAX_MEMPOOL_SIZE) * 1000000);
+ if (feerate_needed < min_mempool_feerate) {
+ feerate_needed = min_mempool_feerate;
+ if (feeCalc) feeCalc->reason = FeeReason::MEMPOOL_MIN;
+ }
+ }
+
+ // prevent user from paying a fee below the required fee rate
+ CFeeRate required_feerate = GetRequiredFeeRate(wallet);
+ if (required_feerate > feerate_needed) {
+ feerate_needed = required_feerate;
+ if (feeCalc) feeCalc->reason = FeeReason::REQUIRED;
+ }
+ return feerate_needed;
+}
+
+CFeeRate GetDiscardRate(const CWallet& wallet, const CBlockPolicyEstimator& estimator)
+{
+ unsigned int highest_target = estimator.HighestTargetTracked(FeeEstimateHorizon::LONG_HALFLIFE);
+ CFeeRate discard_rate = estimator.estimateSmartFee(highest_target, nullptr /* FeeCalculation */, false /* conservative */);
+ // Don't let discard_rate be greater than longest possible fee estimate if we get a valid fee estimate
+ discard_rate = (discard_rate == CFeeRate(0)) ? wallet.m_discard_rate : std::min(discard_rate, wallet.m_discard_rate);
+ // Discard rate must be at least dustRelayFee
+ discard_rate = std::max(discard_rate, ::dustRelayFee);
+ return discard_rate;
+}
diff --git a/src/wallet/fees.h b/src/wallet/fees.h
new file mode 100644
index 0000000000..6bfee456c0
--- /dev/null
+++ b/src/wallet/fees.h
@@ -0,0 +1,47 @@
+// Copyright (c) 2009-2010 Satoshi Nakamoto
+// Copyright (c) 2009-2018 The Bitcoin Core developers
+// Distributed under the MIT software license, see the accompanying
+// file COPYING or http://www.opensource.org/licenses/mit-license.php.
+
+#ifndef BITCOIN_WALLET_FEES_H
+#define BITCOIN_WALLET_FEES_H
+
+#include <amount.h>
+
+class CBlockPolicyEstimator;
+class CCoinControl;
+class CFeeRate;
+class CTxMemPool;
+class CWallet;
+struct FeeCalculation;
+
+/**
+ * Return the minimum required absolute fee for this size
+ * based on the required fee rate
+ */
+CAmount GetRequiredFee(const CWallet& wallet, unsigned int nTxBytes);
+
+/**
+ * Estimate the minimum fee considering user set parameters
+ * and the required fee
+ */
+CAmount GetMinimumFee(const CWallet& wallet, unsigned int nTxBytes, const CCoinControl& coin_control, const CTxMemPool& pool, const CBlockPolicyEstimator& estimator, FeeCalculation* feeCalc);
+
+/**
+ * Return the minimum required feerate taking into account the
+ * minimum relay feerate and user set minimum transaction feerate
+ */
+CFeeRate GetRequiredFeeRate(const CWallet& wallet);
+
+/**
+ * Estimate the minimum fee rate considering user set parameters
+ * and the required fee
+ */
+CFeeRate GetMinimumFeeRate(const CWallet& wallet, const CCoinControl& coin_control, const CTxMemPool& pool, const CBlockPolicyEstimator& estimator, FeeCalculation* feeCalc);
+
+/**
+ * Return the maximum feerate for discarding change.
+ */
+CFeeRate GetDiscardRate(const CWallet& wallet, const CBlockPolicyEstimator& estimator);
+
+#endif // BITCOIN_WALLET_FEES_H
diff --git a/src/wallet/init.cpp b/src/wallet/init.cpp
new file mode 100644
index 0000000000..b9f267210e
--- /dev/null
+++ b/src/wallet/init.cpp
@@ -0,0 +1,270 @@
+// Copyright (c) 2009-2010 Satoshi Nakamoto
+// Copyright (c) 2009-2018 The Bitcoin Core developers
+// Distributed under the MIT software license, see the accompanying
+// file COPYING or http://www.opensource.org/licenses/mit-license.php.
+
+#include <chainparams.h>
+#include <init.h>
+#include <net.h>
+#include <scheduler.h>
+#include <outputtype.h>
+#include <util.h>
+#include <utilmoneystr.h>
+#include <validation.h>
+#include <walletinitinterface.h>
+#include <wallet/rpcwallet.h>
+#include <wallet/wallet.h>
+#include <wallet/walletutil.h>
+
+class WalletInit : public WalletInitInterface {
+public:
+
+ //! Return the wallets help message.
+ void AddWalletOptions() const override;
+
+ //! Wallets parameter interaction
+ bool ParameterInteraction() const override;
+
+ //! Register wallet RPCs.
+ void RegisterRPC(CRPCTable &tableRPC) const override;
+
+ //! Responsible for reading and validating the -wallet arguments and verifying the wallet database.
+ // This function will perform salvage on the wallet if requested, as long as only one wallet is
+ // being loaded (WalletParameterInteraction forbids -salvagewallet, -zapwallettxes or -upgradewallet with multiwallet).
+ bool Verify() const override;
+
+ //! Load wallet databases.
+ bool Open() const override;
+
+ //! Complete startup of wallets.
+ void Start(CScheduler& scheduler) const override;
+
+ //! Flush all wallets in preparation for shutdown.
+ void Flush() const override;
+
+ //! Stop all wallets. Wallets will be flushed first.
+ void Stop() const override;
+
+ //! Close all wallets.
+ void Close() const override;
+};
+
+const WalletInitInterface& g_wallet_init_interface = WalletInit();
+
+void WalletInit::AddWalletOptions() const
+{
+ gArgs.AddArg("-addresstype", strprintf("What type of addresses to use (\"legacy\", \"p2sh-segwit\", or \"bech32\", default: \"%s\")", FormatOutputType(DEFAULT_ADDRESS_TYPE)), false, OptionsCategory::WALLET);
+ gArgs.AddArg("-avoidpartialspends", strprintf(_("Group outputs by address, selecting all or none, instead of selecting on a per-output basis. Privacy is improved as an address is only used once (unless someone sends to it after spending from it), but may result in slightly higher fees as suboptimal coin selection may result due to the added limitation (default: %u)"), DEFAULT_AVOIDPARTIALSPENDS), false, OptionsCategory::WALLET);
+ gArgs.AddArg("-changetype", "What type of change to use (\"legacy\", \"p2sh-segwit\", or \"bech32\"). Default is same as -addresstype, except when -addresstype=p2sh-segwit a native segwit output is used when sending to a native segwit address)", false, OptionsCategory::WALLET);
+ gArgs.AddArg("-disablewallet", "Do not load the wallet and disable wallet RPC calls", false, OptionsCategory::WALLET);
+ gArgs.AddArg("-discardfee=<amt>", strprintf("The fee rate (in %s/kB) that indicates your tolerance for discarding change by adding it to the fee (default: %s). "
+ "Note: An output is discarded if it is dust at this rate, but we will always discard up to the dust relay fee and a discard fee above that is limited by the fee estimate for the longest target",
+ CURRENCY_UNIT, FormatMoney(DEFAULT_DISCARD_FEE)), false, OptionsCategory::WALLET);
+ gArgs.AddArg("-fallbackfee=<amt>", strprintf("A fee rate (in %s/kB) that will be used when fee estimation has insufficient data (default: %s)",
+ CURRENCY_UNIT, FormatMoney(DEFAULT_FALLBACK_FEE)), false, OptionsCategory::WALLET);
+ gArgs.AddArg("-keypool=<n>", strprintf("Set key pool size to <n> (default: %u)", DEFAULT_KEYPOOL_SIZE), false, OptionsCategory::WALLET);
+ gArgs.AddArg("-mintxfee=<amt>", strprintf("Fees (in %s/kB) smaller than this are considered zero fee for transaction creation (default: %s)",
+ CURRENCY_UNIT, FormatMoney(DEFAULT_TRANSACTION_MINFEE)), false, OptionsCategory::WALLET);
+ gArgs.AddArg("-paytxfee=<amt>", strprintf("Fee (in %s/kB) to add to transactions you send (default: %s)",
+ CURRENCY_UNIT, FormatMoney(CFeeRate{DEFAULT_PAY_TX_FEE}.GetFeePerK())), false, OptionsCategory::WALLET);
+ gArgs.AddArg("-rescan", "Rescan the block chain for missing wallet transactions on startup", false, OptionsCategory::WALLET);
+ gArgs.AddArg("-salvagewallet", "Attempt to recover private keys from a corrupt wallet on startup", false, OptionsCategory::WALLET);
+ gArgs.AddArg("-spendzeroconfchange", strprintf("Spend unconfirmed change when sending transactions (default: %u)", DEFAULT_SPEND_ZEROCONF_CHANGE), false, OptionsCategory::WALLET);
+ gArgs.AddArg("-txconfirmtarget=<n>", strprintf("If paytxfee is not set, include enough fee so transactions begin confirmation on average within n blocks (default: %u)", DEFAULT_TX_CONFIRM_TARGET), false, OptionsCategory::WALLET);
+ gArgs.AddArg("-upgradewallet", "Upgrade wallet to latest format on startup", false, OptionsCategory::WALLET);
+ gArgs.AddArg("-wallet=<path>", "Specify wallet database path. Can be specified multiple times to load multiple wallets. Path is interpreted relative to <walletdir> if it is not absolute, and will be created if it does not exist (as a directory containing a wallet.dat file and log files). For backwards compatibility this will also accept names of existing data files in <walletdir>.)", false, OptionsCategory::WALLET);
+ gArgs.AddArg("-walletbroadcast", strprintf("Make the wallet broadcast transactions (default: %u)", DEFAULT_WALLETBROADCAST), false, OptionsCategory::WALLET);
+ gArgs.AddArg("-walletdir=<dir>", "Specify directory to hold wallets (default: <datadir>/wallets if it exists, otherwise <datadir>)", false, OptionsCategory::WALLET);
+ gArgs.AddArg("-walletnotify=<cmd>", "Execute command when a wallet transaction changes (%s in cmd is replaced by TxID)", false, OptionsCategory::WALLET);
+ gArgs.AddArg("-walletrbf", strprintf("Send transactions with full-RBF opt-in enabled (RPC only, default: %u)", DEFAULT_WALLET_RBF), false, OptionsCategory::WALLET);
+ gArgs.AddArg("-zapwallettxes=<mode>", "Delete all wallet transactions and only recover those parts of the blockchain through -rescan on startup"
+ " (1 = keep tx meta data e.g. account owner and payment request information, 2 = drop tx meta data)", false, OptionsCategory::WALLET);
+
+ gArgs.AddArg("-dblogsize=<n>", strprintf("Flush wallet database activity from memory to disk log every <n> megabytes (default: %u)", DEFAULT_WALLET_DBLOGSIZE), true, OptionsCategory::WALLET_DEBUG_TEST);
+ gArgs.AddArg("-flushwallet", strprintf("Run a thread to flush wallet periodically (default: %u)", DEFAULT_FLUSHWALLET), true, OptionsCategory::WALLET_DEBUG_TEST);
+ gArgs.AddArg("-privdb", strprintf("Sets the DB_PRIVATE flag in the wallet db environment (default: %u)", DEFAULT_WALLET_PRIVDB), true, OptionsCategory::WALLET_DEBUG_TEST);
+ gArgs.AddArg("-walletrejectlongchains", strprintf("Wallet will not create transactions that violate mempool chain limits (default: %u)", DEFAULT_WALLET_REJECT_LONG_CHAINS), true, OptionsCategory::WALLET_DEBUG_TEST);
+}
+
+bool WalletInit::ParameterInteraction() const
+{
+ if (gArgs.GetBoolArg("-disablewallet", DEFAULT_DISABLE_WALLET)) {
+ for (const std::string& wallet : gArgs.GetArgs("-wallet")) {
+ LogPrintf("%s: parameter interaction: -disablewallet -> ignoring -wallet=%s\n", __func__, wallet);
+ }
+
+ return true;
+ }
+
+ gArgs.SoftSetArg("-wallet", "");
+ const bool is_multiwallet = gArgs.GetArgs("-wallet").size() > 1;
+
+ if (gArgs.GetBoolArg("-blocksonly", DEFAULT_BLOCKSONLY) && gArgs.SoftSetBoolArg("-walletbroadcast", false)) {
+ LogPrintf("%s: parameter interaction: -blocksonly=1 -> setting -walletbroadcast=0\n", __func__);
+ }
+
+ if (gArgs.GetBoolArg("-salvagewallet", false)) {
+ if (is_multiwallet) {
+ return InitError(strprintf("%s is only allowed with a single wallet file", "-salvagewallet"));
+ }
+ // Rewrite just private keys: rescan to find transactions
+ if (gArgs.SoftSetBoolArg("-rescan", true)) {
+ LogPrintf("%s: parameter interaction: -salvagewallet=1 -> setting -rescan=1\n", __func__);
+ }
+ }
+
+ bool zapwallettxes = gArgs.GetBoolArg("-zapwallettxes", false);
+ // -zapwallettxes implies dropping the mempool on startup
+ if (zapwallettxes && gArgs.SoftSetBoolArg("-persistmempool", false)) {
+ LogPrintf("%s: parameter interaction: -zapwallettxes enabled -> setting -persistmempool=0\n", __func__);
+ }
+
+ // -zapwallettxes implies a rescan
+ if (zapwallettxes) {
+ if (is_multiwallet) {
+ return InitError(strprintf("%s is only allowed with a single wallet file", "-zapwallettxes"));
+ }
+ if (gArgs.SoftSetBoolArg("-rescan", true)) {
+ LogPrintf("%s: parameter interaction: -zapwallettxes enabled -> setting -rescan=1\n", __func__);
+ }
+ }
+
+ if (is_multiwallet) {
+ if (gArgs.GetBoolArg("-upgradewallet", false)) {
+ return InitError(strprintf("%s is only allowed with a single wallet file", "-upgradewallet"));
+ }
+ }
+
+ if (gArgs.GetBoolArg("-sysperms", false))
+ return InitError("-sysperms is not allowed in combination with enabled wallet functionality");
+ if (gArgs.GetArg("-prune", 0) && gArgs.GetBoolArg("-rescan", false))
+ return InitError(_("Rescans are not possible in pruned mode. You will need to use -reindex which will download the whole blockchain again."));
+
+ if (::minRelayTxFee.GetFeePerK() > HIGH_TX_FEE_PER_KB)
+ InitWarning(AmountHighWarn("-minrelaytxfee") + " " +
+ _("The wallet will avoid paying less than the minimum relay fee."));
+
+ if (gArgs.IsArgSet("-maxtxfee"))
+ {
+ CAmount nMaxFee = 0;
+ if (!ParseMoney(gArgs.GetArg("-maxtxfee", ""), nMaxFee))
+ return InitError(AmountErrMsg("maxtxfee", gArgs.GetArg("-maxtxfee", "")));
+ if (nMaxFee > HIGH_MAX_TX_FEE)
+ InitWarning(_("-maxtxfee is set very high! Fees this large could be paid on a single transaction."));
+ maxTxFee = nMaxFee;
+ if (CFeeRate(maxTxFee, 1000) < ::minRelayTxFee)
+ {
+ return InitError(strprintf(_("Invalid amount for -maxtxfee=<amount>: '%s' (must be at least the minrelay fee of %s to prevent stuck transactions)"),
+ gArgs.GetArg("-maxtxfee", ""), ::minRelayTxFee.ToString()));
+ }
+ }
+
+ return true;
+}
+
+void WalletInit::RegisterRPC(CRPCTable &t) const
+{
+ if (gArgs.GetBoolArg("-disablewallet", DEFAULT_DISABLE_WALLET)) {
+ return;
+ }
+
+ RegisterWalletRPCCommands(t);
+}
+
+bool WalletInit::Verify() const
+{
+ if (gArgs.GetBoolArg("-disablewallet", DEFAULT_DISABLE_WALLET)) {
+ return true;
+ }
+
+ if (gArgs.IsArgSet("-walletdir")) {
+ fs::path wallet_dir = gArgs.GetArg("-walletdir", "");
+ if (!fs::exists(wallet_dir)) {
+ return InitError(strprintf(_("Specified -walletdir \"%s\" does not exist"), wallet_dir.string()));
+ } else if (!fs::is_directory(wallet_dir)) {
+ return InitError(strprintf(_("Specified -walletdir \"%s\" is not a directory"), wallet_dir.string()));
+ } else if (!wallet_dir.is_absolute()) {
+ return InitError(strprintf(_("Specified -walletdir \"%s\" is a relative path"), wallet_dir.string()));
+ }
+ }
+
+ LogPrintf("Using wallet directory %s\n", GetWalletDir().string());
+
+ uiInterface.InitMessage(_("Verifying wallet(s)..."));
+
+ std::vector<std::string> wallet_files = gArgs.GetArgs("-wallet");
+
+ // Parameter interaction code should have thrown an error if -salvagewallet
+ // was enabled with more than wallet file, so the wallet_files size check
+ // here should have no effect.
+ bool salvage_wallet = gArgs.GetBoolArg("-salvagewallet", false) && wallet_files.size() <= 1;
+
+ // Keep track of each wallet absolute path to detect duplicates.
+ std::set<fs::path> wallet_paths;
+
+ for (const auto& wallet_file : wallet_files) {
+ fs::path wallet_path = fs::absolute(wallet_file, GetWalletDir());
+
+ if (!wallet_paths.insert(wallet_path).second) {
+ return InitError(strprintf(_("Error loading wallet %s. Duplicate -wallet filename specified."), wallet_file));
+ }
+
+ std::string error_string;
+ std::string warning_string;
+ bool verify_success = CWallet::Verify(wallet_file, salvage_wallet, error_string, warning_string);
+ if (!error_string.empty()) InitError(error_string);
+ if (!warning_string.empty()) InitWarning(warning_string);
+ if (!verify_success) return false;
+ }
+
+ return true;
+}
+
+bool WalletInit::Open() const
+{
+ if (gArgs.GetBoolArg("-disablewallet", DEFAULT_DISABLE_WALLET)) {
+ LogPrintf("Wallet disabled!\n");
+ return true;
+ }
+
+ for (const std::string& walletFile : gArgs.GetArgs("-wallet")) {
+ std::shared_ptr<CWallet> pwallet = CWallet::CreateWalletFromFile(walletFile, fs::absolute(walletFile, GetWalletDir()));
+ if (!pwallet) {
+ return false;
+ }
+ AddWallet(pwallet);
+ }
+
+ return true;
+}
+
+void WalletInit::Start(CScheduler& scheduler) const
+{
+ for (const std::shared_ptr<CWallet>& pwallet : GetWallets()) {
+ pwallet->postInitProcess();
+ }
+
+ // Run a thread to flush wallet periodically
+ scheduler.scheduleEvery(MaybeCompactWalletDB, 500);
+}
+
+void WalletInit::Flush() const
+{
+ for (const std::shared_ptr<CWallet>& pwallet : GetWallets()) {
+ pwallet->Flush(false);
+ }
+}
+
+void WalletInit::Stop() const
+{
+ for (const std::shared_ptr<CWallet>& pwallet : GetWallets()) {
+ pwallet->Flush(true);
+ }
+}
+
+void WalletInit::Close() const
+{
+ for (const std::shared_ptr<CWallet>& pwallet : GetWallets()) {
+ RemoveWallet(pwallet);
+ }
+}
diff --git a/src/wallet/rpcdump.cpp b/src/wallet/rpcdump.cpp
index 42ebdb9b9b..7cb41da39e 100644
--- a/src/wallet/rpcdump.cpp
+++ b/src/wallet/rpcdump.cpp
@@ -1,20 +1,21 @@
-// Copyright (c) 2009-2015 The Bitcoin Core developers
+// Copyright (c) 2009-2018 The Bitcoin Core developers
// Distributed under the MIT software license, see the accompanying
// file COPYING or http://www.opensource.org/licenses/mit-license.php.
-#include "base58.h"
-#include "chain.h"
-#include "rpc/server.h"
-#include "init.h"
-#include "main.h"
-#include "script/script.h"
-#include "script/standard.h"
-#include "sync.h"
-#include "util.h"
-#include "utiltime.h"
-#include "wallet.h"
-#include "merkleblock.h"
-#include "core_io.h"
+#include <chain.h>
+#include <key_io.h>
+#include <rpc/server.h>
+#include <validation.h>
+#include <script/script.h>
+#include <script/standard.h>
+#include <sync.h>
+#include <util.h>
+#include <utiltime.h>
+#include <wallet/wallet.h>
+#include <merkleblock.h>
+#include <core_io.h>
+
+#include <wallet/rpcwallet.h>
#include <fstream>
#include <stdint.h>
@@ -24,16 +25,6 @@
#include <univalue.h>
-#include <boost/foreach.hpp>
-
-using namespace std;
-
-void EnsureWalletIsUnlocked();
-bool EnsureWalletIsAvailable(bool avoidException);
-
-std::string static EncodeDumpTime(int64_t nTime) {
- return DateTimeStrFormat("%Y-%m-%dT%H:%M:%SZ", nTime);
-}
int64_t static DecodeDumpTime(const std::string &str) {
static const boost::posix_time::ptime epoch = boost::posix_time::from_time_t(0);
@@ -50,7 +41,7 @@ int64_t static DecodeDumpTime(const std::string &str) {
std::string static EncodeDumpString(const std::string &str) {
std::stringstream ret;
- BOOST_FOREACH(unsigned char c, str) {
+ for (unsigned char c : str) {
if (c <= 32 || c >= 128 || c == '%') {
ret << '%' << HexStr(&c, &c + 1);
} else {
@@ -60,12 +51,12 @@ std::string static EncodeDumpString(const std::string &str) {
return ret.str();
}
-std::string DecodeDumpString(const std::string &str) {
+static std::string DecodeDumpString(const std::string &str) {
std::stringstream ret;
for (unsigned int pos = 0; pos < str.length(); pos++) {
unsigned char c = str[pos];
if (c == '%' && pos+2 < str.length()) {
- c = (((str[pos+1]>>6)*9+((str[pos+1]-'0')&15)) << 4) |
+ c = (((str[pos+1]>>6)*9+((str[pos+1]-'0')&15)) << 4) |
((str[pos+2]>>6)*9+((str[pos+2]-'0')&15));
pos += 2;
}
@@ -74,20 +65,58 @@ std::string DecodeDumpString(const std::string &str) {
return ret.str();
}
-UniValue importprivkey(const UniValue& params, bool fHelp)
+static bool GetWalletAddressesForKey(CWallet * const pwallet, const CKeyID &keyid, std::string &strAddr, std::string &strLabel)
+{
+ bool fLabelFound = false;
+ CKey key;
+ pwallet->GetKey(keyid, key);
+ for (const auto& dest : GetAllDestinationsForKey(key.GetPubKey())) {
+ if (pwallet->mapAddressBook.count(dest)) {
+ if (!strAddr.empty()) {
+ strAddr += ",";
+ }
+ strAddr += EncodeDestination(dest);
+ strLabel = EncodeDumpString(pwallet->mapAddressBook[dest].name);
+ fLabelFound = true;
+ }
+ }
+ if (!fLabelFound) {
+ strAddr = EncodeDestination(GetDestinationForKey(key.GetPubKey(), pwallet->m_default_address_type));
+ }
+ return fLabelFound;
+}
+
+static const int64_t TIMESTAMP_MIN = 0;
+
+static void RescanWallet(CWallet& wallet, const WalletRescanReserver& reserver, int64_t time_begin = TIMESTAMP_MIN, bool update = true)
+{
+ int64_t scanned_time = wallet.RescanFromTime(time_begin, reserver, update);
+ if (wallet.IsAbortingRescan()) {
+ throw JSONRPCError(RPC_MISC_ERROR, "Rescan aborted by user.");
+ } else if (scanned_time > time_begin) {
+ throw JSONRPCError(RPC_WALLET_ERROR, "Rescan was unable to fully rescan the blockchain. Some transactions may be missing.");
+ }
+}
+
+UniValue importprivkey(const JSONRPCRequest& request)
{
- if (!EnsureWalletIsAvailable(fHelp))
+ std::shared_ptr<CWallet> const wallet = GetWalletForJSONRPCRequest(request);
+ CWallet* const pwallet = wallet.get();
+ if (!EnsureWalletIsAvailable(pwallet, request.fHelp)) {
return NullUniValue;
-
- if (fHelp || params.size() < 1 || params.size() > 3)
- throw runtime_error(
- "importprivkey \"bitcoinprivkey\" ( \"label\" rescan )\n"
- "\nAdds a private key (as returned by dumpprivkey) to your wallet.\n"
+ }
+
+ if (request.fHelp || request.params.size() < 1 || request.params.size() > 3)
+ throw std::runtime_error(
+ "importprivkey \"privkey\" ( \"label\" ) ( rescan )\n"
+ "\nAdds a private key (as returned by dumpprivkey) to your wallet. Requires a new wallet backup.\n"
+ "Hint: use importmulti to import more than one private key.\n"
"\nArguments:\n"
- "1. \"bitcoinprivkey\" (string, required) The private key (see dumpprivkey)\n"
+ "1. \"privkey\" (string, required) The private key (see dumpprivkey)\n"
"2. \"label\" (string, optional, default=\"\") An optional label\n"
"3. rescan (boolean, optional, default=true) Rescan the wallet for transactions\n"
- "\nNote: This call can take minutes to complete if rescan is true.\n"
+ "\nNote: This call can take over an hour to complete if rescan is true, during that time, other rpc calls\n"
+ "may report that the imported key exists but related transactions are still missing, leading to temporarily incorrect/bogus balances and unspent outputs until rescan completes.\n"
"\nExamples:\n"
"\nDump a private key\n"
+ HelpExampleCli("dumpprivkey", "\"myaddress\"") +
@@ -95,170 +124,222 @@ UniValue importprivkey(const UniValue& params, bool fHelp)
+ HelpExampleCli("importprivkey", "\"mykey\"") +
"\nImport using a label and without rescan\n"
+ HelpExampleCli("importprivkey", "\"mykey\" \"testing\" false") +
+ "\nImport using default blank label and without rescan\n"
+ + HelpExampleCli("importprivkey", "\"mykey\" \"\" false") +
"\nAs a JSON-RPC call\n"
+ HelpExampleRpc("importprivkey", "\"mykey\", \"testing\", false")
);
- LOCK2(cs_main, pwalletMain->cs_wallet);
-
- EnsureWalletIsUnlocked();
-
- string strSecret = params[0].get_str();
- string strLabel = "";
- if (params.size() > 1)
- strLabel = params[1].get_str();
-
- // Whether to perform rescan after import
+ WalletRescanReserver reserver(pwallet);
bool fRescan = true;
- if (params.size() > 2)
- fRescan = params[2].get_bool();
+ {
+ LOCK2(cs_main, pwallet->cs_wallet);
- if (fRescan && fPruneMode)
- throw JSONRPCError(RPC_WALLET_ERROR, "Rescan is disabled in pruned mode");
+ EnsureWalletIsUnlocked(pwallet);
- CBitcoinSecret vchSecret;
- bool fGood = vchSecret.SetString(strSecret);
+ std::string strSecret = request.params[0].get_str();
+ std::string strLabel = "";
+ if (!request.params[1].isNull())
+ strLabel = request.params[1].get_str();
- if (!fGood) throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Invalid private key encoding");
+ // Whether to perform rescan after import
+ if (!request.params[2].isNull())
+ fRescan = request.params[2].get_bool();
- CKey key = vchSecret.GetKey();
- if (!key.IsValid()) throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Private key outside allowed range");
+ if (fRescan && fPruneMode)
+ throw JSONRPCError(RPC_WALLET_ERROR, "Rescan is disabled in pruned mode");
- CPubKey pubkey = key.GetPubKey();
- assert(key.VerifyPubKey(pubkey));
- CKeyID vchAddress = pubkey.GetID();
- {
- pwalletMain->MarkDirty();
- pwalletMain->SetAddressBook(vchAddress, strLabel, "receive");
+ if (fRescan && !reserver.reserve()) {
+ throw JSONRPCError(RPC_WALLET_ERROR, "Wallet is currently rescanning. Abort existing rescan or wait.");
+ }
- // Don't throw error in case a key is already there
- if (pwalletMain->HaveKey(vchAddress))
- return NullUniValue;
+ CKey key = DecodeSecret(strSecret);
+ if (!key.IsValid()) throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Invalid private key encoding");
- pwalletMain->mapKeyMetadata[vchAddress].nCreateTime = 1;
+ CPubKey pubkey = key.GetPubKey();
+ assert(key.VerifyPubKey(pubkey));
+ CKeyID vchAddress = pubkey.GetID();
+ {
+ pwallet->MarkDirty();
+ // We don't know which corresponding address will be used; label them all
+ for (const auto& dest : GetAllDestinationsForKey(pubkey)) {
+ pwallet->SetAddressBook(dest, strLabel, "receive");
+ }
- if (!pwalletMain->AddKeyPubKey(key, pubkey))
- throw JSONRPCError(RPC_WALLET_ERROR, "Error adding key to wallet");
+ // Don't throw error in case a key is already there
+ if (pwallet->HaveKey(vchAddress)) {
+ return NullUniValue;
+ }
- // whenever a key is imported, we need to scan the whole chain
- pwalletMain->nTimeFirstKey = 1; // 0 would be considered 'no value'
+ // whenever a key is imported, we need to scan the whole chain
+ pwallet->UpdateTimeFirstKey(1);
+ pwallet->mapKeyMetadata[vchAddress].nCreateTime = 1;
- if (fRescan) {
- pwalletMain->ScanForWalletTransactions(chainActive.Genesis(), true);
+ if (!pwallet->AddKeyPubKey(key, pubkey)) {
+ throw JSONRPCError(RPC_WALLET_ERROR, "Error adding key to wallet");
+ }
+ pwallet->LearnAllRelatedScripts(pubkey);
}
}
+ if (fRescan) {
+ RescanWallet(*pwallet, reserver);
+ }
return NullUniValue;
}
-void ImportAddress(const CBitcoinAddress& address, const string& strLabel);
-void ImportScript(const CScript& script, const string& strLabel, bool isRedeemScript)
+UniValue abortrescan(const JSONRPCRequest& request)
+{
+ std::shared_ptr<CWallet> const wallet = GetWalletForJSONRPCRequest(request);
+ CWallet* const pwallet = wallet.get();
+ if (!EnsureWalletIsAvailable(pwallet, request.fHelp)) {
+ return NullUniValue;
+ }
+
+ if (request.fHelp || request.params.size() > 0)
+ throw std::runtime_error(
+ "abortrescan\n"
+ "\nStops current wallet rescan triggered by an RPC call, e.g. by an importprivkey call.\n"
+ "\nExamples:\n"
+ "\nImport a private key\n"
+ + HelpExampleCli("importprivkey", "\"mykey\"") +
+ "\nAbort the running wallet rescan\n"
+ + HelpExampleCli("abortrescan", "") +
+ "\nAs a JSON-RPC call\n"
+ + HelpExampleRpc("abortrescan", "")
+ );
+
+ if (!pwallet->IsScanning() || pwallet->IsAbortingRescan()) return false;
+ pwallet->AbortRescan();
+ return true;
+}
+
+static void ImportAddress(CWallet*, const CTxDestination& dest, const std::string& strLabel);
+static void ImportScript(CWallet* const pwallet, const CScript& script, const std::string& strLabel, bool isRedeemScript) EXCLUSIVE_LOCKS_REQUIRED(pwallet->cs_wallet)
{
- if (!isRedeemScript && ::IsMine(*pwalletMain, script) == ISMINE_SPENDABLE)
+ if (!isRedeemScript && ::IsMine(*pwallet, script) == ISMINE_SPENDABLE) {
throw JSONRPCError(RPC_WALLET_ERROR, "The wallet already contains the private key for this address or script");
+ }
- pwalletMain->MarkDirty();
+ pwallet->MarkDirty();
- if (!pwalletMain->HaveWatchOnly(script) && !pwalletMain->AddWatchOnly(script))
+ if (!pwallet->HaveWatchOnly(script) && !pwallet->AddWatchOnly(script, 0 /* nCreateTime */)) {
throw JSONRPCError(RPC_WALLET_ERROR, "Error adding address to wallet");
+ }
if (isRedeemScript) {
- if (!pwalletMain->HaveCScript(script) && !pwalletMain->AddCScript(script))
+ const CScriptID id(script);
+ if (!pwallet->HaveCScript(id) && !pwallet->AddCScript(script)) {
throw JSONRPCError(RPC_WALLET_ERROR, "Error adding p2sh redeemScript to wallet");
- ImportAddress(CBitcoinAddress(CScriptID(script)), strLabel);
+ }
+ ImportAddress(pwallet, id, strLabel);
} else {
CTxDestination destination;
if (ExtractDestination(script, destination)) {
- pwalletMain->SetAddressBook(destination, strLabel, "receive");
+ pwallet->SetAddressBook(destination, strLabel, "receive");
}
}
}
-void ImportAddress(const CBitcoinAddress& address, const string& strLabel)
+static void ImportAddress(CWallet* const pwallet, const CTxDestination& dest, const std::string& strLabel) EXCLUSIVE_LOCKS_REQUIRED(pwallet->cs_wallet)
{
- CScript script = GetScriptForDestination(address.Get());
- ImportScript(script, strLabel, false);
+ CScript script = GetScriptForDestination(dest);
+ ImportScript(pwallet, script, strLabel, false);
// add to address book or update label
- if (address.IsValid())
- pwalletMain->SetAddressBook(address.Get(), strLabel, "receive");
+ if (IsValidDestination(dest))
+ pwallet->SetAddressBook(dest, strLabel, "receive");
}
-UniValue importaddress(const UniValue& params, bool fHelp)
+UniValue importaddress(const JSONRPCRequest& request)
{
- if (!EnsureWalletIsAvailable(fHelp))
+ std::shared_ptr<CWallet> const wallet = GetWalletForJSONRPCRequest(request);
+ CWallet* const pwallet = wallet.get();
+ if (!EnsureWalletIsAvailable(pwallet, request.fHelp)) {
return NullUniValue;
-
- if (fHelp || params.size() < 1 || params.size() > 4)
- throw runtime_error(
+ }
+
+ if (request.fHelp || request.params.size() < 1 || request.params.size() > 4)
+ throw std::runtime_error(
"importaddress \"address\" ( \"label\" rescan p2sh )\n"
- "\nAdds a script (in hex) or address that can be watched as if it were in your wallet but cannot be used to spend.\n"
+ "\nAdds an address or script (in hex) that can be watched as if it were in your wallet but cannot be used to spend. Requires a new wallet backup.\n"
"\nArguments:\n"
- "1. \"script\" (string, required) The hex-encoded script (or address)\n"
+ "1. \"address\" (string, required) The Bitcoin address (or hex-encoded script)\n"
"2. \"label\" (string, optional, default=\"\") An optional label\n"
"3. rescan (boolean, optional, default=true) Rescan the wallet for transactions\n"
"4. p2sh (boolean, optional, default=false) Add the P2SH version of the script as well\n"
- "\nNote: This call can take minutes to complete if rescan is true.\n"
+ "\nNote: This call can take over an hour to complete if rescan is true, during that time, other rpc calls\n"
+ "may report that the imported address exists but related transactions are still missing, leading to temporarily incorrect/bogus balances and unspent outputs until rescan completes.\n"
"If you have the full public key, you should call importpubkey instead of this.\n"
"\nNote: If you import a non-standard raw script in hex form, outputs sending to it will be treated\n"
"as change, and not show up in many RPCs.\n"
"\nExamples:\n"
- "\nImport a script with rescan\n"
- + HelpExampleCli("importaddress", "\"myscript\"") +
+ "\nImport an address with rescan\n"
+ + HelpExampleCli("importaddress", "\"myaddress\"") +
"\nImport using a label without rescan\n"
- + HelpExampleCli("importaddress", "\"myscript\" \"testing\" false") +
+ + HelpExampleCli("importaddress", "\"myaddress\" \"testing\" false") +
"\nAs a JSON-RPC call\n"
- + HelpExampleRpc("importaddress", "\"myscript\", \"testing\", false")
+ + HelpExampleRpc("importaddress", "\"myaddress\", \"testing\", false")
);
- string strLabel = "";
- if (params.size() > 1)
- strLabel = params[1].get_str();
+ std::string strLabel;
+ if (!request.params[1].isNull())
+ strLabel = request.params[1].get_str();
// Whether to perform rescan after import
bool fRescan = true;
- if (params.size() > 2)
- fRescan = params[2].get_bool();
+ if (!request.params[2].isNull())
+ fRescan = request.params[2].get_bool();
if (fRescan && fPruneMode)
throw JSONRPCError(RPC_WALLET_ERROR, "Rescan is disabled in pruned mode");
+ WalletRescanReserver reserver(pwallet);
+ if (fRescan && !reserver.reserve()) {
+ throw JSONRPCError(RPC_WALLET_ERROR, "Wallet is currently rescanning. Abort existing rescan or wait.");
+ }
+
// Whether to import a p2sh version, too
bool fP2SH = false;
- if (params.size() > 3)
- fP2SH = params[3].get_bool();
-
- LOCK2(cs_main, pwalletMain->cs_wallet);
-
- CBitcoinAddress address(params[0].get_str());
- if (address.IsValid()) {
- if (fP2SH)
- throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Cannot use the p2sh flag with an address - use a script instead");
- ImportAddress(address, strLabel);
- } else if (IsHex(params[0].get_str())) {
- std::vector<unsigned char> data(ParseHex(params[0].get_str()));
- ImportScript(CScript(data.begin(), data.end()), strLabel, fP2SH);
- } else {
- throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Invalid Bitcoin address or script");
- }
+ if (!request.params[3].isNull())
+ fP2SH = request.params[3].get_bool();
+
+ {
+ LOCK2(cs_main, pwallet->cs_wallet);
+ CTxDestination dest = DecodeDestination(request.params[0].get_str());
+ if (IsValidDestination(dest)) {
+ if (fP2SH) {
+ throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Cannot use the p2sh flag with an address - use a script instead");
+ }
+ ImportAddress(pwallet, dest, strLabel);
+ } else if (IsHex(request.params[0].get_str())) {
+ std::vector<unsigned char> data(ParseHex(request.params[0].get_str()));
+ ImportScript(pwallet, CScript(data.begin(), data.end()), strLabel, fP2SH);
+ } else {
+ throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Invalid Bitcoin address or script");
+ }
+ }
if (fRescan)
{
- pwalletMain->ScanForWalletTransactions(chainActive.Genesis(), true);
- pwalletMain->ReacceptWalletTransactions();
+ RescanWallet(*pwallet, reserver);
+ pwallet->ReacceptWalletTransactions();
}
return NullUniValue;
}
-UniValue importprunedfunds(const UniValue& params, bool fHelp)
+UniValue importprunedfunds(const JSONRPCRequest& request)
{
- if (!EnsureWalletIsAvailable(fHelp))
+ std::shared_ptr<CWallet> const wallet = GetWalletForJSONRPCRequest(request);
+ CWallet* const pwallet = wallet.get();
+ if (!EnsureWalletIsAvailable(pwallet, request.fHelp)) {
return NullUniValue;
+ }
- if (fHelp || params.size() != 2)
- throw runtime_error(
+ if (request.fHelp || request.params.size() != 2)
+ throw std::runtime_error(
"importprunedfunds\n"
"\nImports funds without rescan. Corresponding address or script must previously be included in wallet. Aimed towards pruned wallets. The end-user is responsible to import additional transactions that subsequently spend the imported outputs or rescan after the point in the blockchain the transaction is included.\n"
"\nArguments:\n"
@@ -266,28 +347,29 @@ UniValue importprunedfunds(const UniValue& params, bool fHelp)
"2. \"txoutproof\" (string, required) The hex output from gettxoutproof that contains the transaction\n"
);
- CTransaction tx;
- if (!DecodeHexTx(tx, params[0].get_str()))
+ CMutableTransaction tx;
+ if (!DecodeHexTx(tx, request.params[0].get_str()))
throw JSONRPCError(RPC_DESERIALIZATION_ERROR, "TX decode failed");
uint256 hashTx = tx.GetHash();
- CWalletTx wtx(pwalletMain,tx);
+ CWalletTx wtx(pwallet, MakeTransactionRef(std::move(tx)));
- CDataStream ssMB(ParseHexV(params[1], "proof"), SER_NETWORK, PROTOCOL_VERSION);
+ CDataStream ssMB(ParseHexV(request.params[1], "proof"), SER_NETWORK, PROTOCOL_VERSION);
CMerkleBlock merkleBlock;
ssMB >> merkleBlock;
//Search partial merkle tree in proof for our transaction and index in valid block
- vector<uint256> vMatch;
- vector<unsigned int> vIndex;
+ std::vector<uint256> vMatch;
+ std::vector<unsigned int> vIndex;
unsigned int txnIndex = 0;
if (merkleBlock.txn.ExtractMatches(vMatch, vIndex) == merkleBlock.header.hashMerkleRoot) {
LOCK(cs_main);
-
- if (!mapBlockIndex.count(merkleBlock.header.GetHash()) || !chainActive.Contains(mapBlockIndex[merkleBlock.header.GetHash()]))
+ const CBlockIndex* pindex = LookupBlockIndex(merkleBlock.header.GetHash());
+ if (!pindex || !chainActive.Contains(pindex)) {
throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Block not found in chain");
+ }
- vector<uint256>::const_iterator it;
+ std::vector<uint256>::const_iterator it;
if ((it = std::find(vMatch.begin(), vMatch.end(), hashTx))==vMatch.end()) {
throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Transaction given doesn't exist in proof");
}
@@ -301,68 +383,73 @@ UniValue importprunedfunds(const UniValue& params, bool fHelp)
wtx.nIndex = txnIndex;
wtx.hashBlock = merkleBlock.header.GetHash();
- LOCK2(cs_main, pwalletMain->cs_wallet);
+ LOCK2(cs_main, pwallet->cs_wallet);
- if (pwalletMain->IsMine(tx)) {
- pwalletMain->AddToWallet(wtx, false);
+ if (pwallet->IsMine(*wtx.tx)) {
+ pwallet->AddToWallet(wtx, false);
return NullUniValue;
}
throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "No addresses in wallet correspond to included transaction");
}
-UniValue removeprunedfunds(const UniValue& params, bool fHelp)
+UniValue removeprunedfunds(const JSONRPCRequest& request)
{
- if (!EnsureWalletIsAvailable(fHelp))
+ std::shared_ptr<CWallet> const wallet = GetWalletForJSONRPCRequest(request);
+ CWallet* const pwallet = wallet.get();
+ if (!EnsureWalletIsAvailable(pwallet, request.fHelp)) {
return NullUniValue;
+ }
- if (fHelp || params.size() != 1)
- throw runtime_error(
+ if (request.fHelp || request.params.size() != 1)
+ throw std::runtime_error(
"removeprunedfunds \"txid\"\n"
- "\nDeletes the specified transaction from the wallet. Meant for use with pruned wallets and as a companion to importprunedfunds. This will effect wallet balances.\n"
+ "\nDeletes the specified transaction from the wallet. Meant for use with pruned wallets and as a companion to importprunedfunds. This will affect wallet balances.\n"
"\nArguments:\n"
"1. \"txid\" (string, required) The hex-encoded id of the transaction you are deleting\n"
"\nExamples:\n"
+ HelpExampleCli("removeprunedfunds", "\"a8d0c0184dde994a09ec054286f1ce581bebf46446a512166eae7628734ea0a5\"") +
"\nAs a JSON-RPC call\n"
- + HelpExampleRpc("removprunedfunds", "\"a8d0c0184dde994a09ec054286f1ce581bebf46446a512166eae7628734ea0a5\"")
+ + HelpExampleRpc("removeprunedfunds", "\"a8d0c0184dde994a09ec054286f1ce581bebf46446a512166eae7628734ea0a5\"")
);
- LOCK2(cs_main, pwalletMain->cs_wallet);
+ LOCK2(cs_main, pwallet->cs_wallet);
uint256 hash;
- hash.SetHex(params[0].get_str());
- vector<uint256> vHash;
+ hash.SetHex(request.params[0].get_str());
+ std::vector<uint256> vHash;
vHash.push_back(hash);
- vector<uint256> vHashOut;
+ std::vector<uint256> vHashOut;
- if(pwalletMain->ZapSelectTx(vHash, vHashOut) != DB_LOAD_OK) {
- throw JSONRPCError(RPC_INTERNAL_ERROR, "Could not properly delete the transaction.");
+ if (pwallet->ZapSelectTx(vHash, vHashOut) != DBErrors::LOAD_OK) {
+ throw JSONRPCError(RPC_WALLET_ERROR, "Could not properly delete the transaction.");
}
if(vHashOut.empty()) {
- throw JSONRPCError(RPC_INTERNAL_ERROR, "Transaction does not exist in wallet.");
+ throw JSONRPCError(RPC_INVALID_PARAMETER, "Transaction does not exist in wallet.");
}
- ThreadFlushWalletDB(pwalletMain->strWalletFile);
-
return NullUniValue;
}
-UniValue importpubkey(const UniValue& params, bool fHelp)
+UniValue importpubkey(const JSONRPCRequest& request)
{
- if (!EnsureWalletIsAvailable(fHelp))
+ std::shared_ptr<CWallet> const wallet = GetWalletForJSONRPCRequest(request);
+ CWallet* const pwallet = wallet.get();
+ if (!EnsureWalletIsAvailable(pwallet, request.fHelp)) {
return NullUniValue;
+ }
- if (fHelp || params.size() < 1 || params.size() > 4)
- throw runtime_error(
+ if (request.fHelp || request.params.size() < 1 || request.params.size() > 3)
+ throw std::runtime_error(
"importpubkey \"pubkey\" ( \"label\" rescan )\n"
- "\nAdds a public key (in hex) that can be watched as if it were in your wallet but cannot be used to spend.\n"
+ "\nAdds a public key (in hex) that can be watched as if it were in your wallet but cannot be used to spend. Requires a new wallet backup.\n"
"\nArguments:\n"
"1. \"pubkey\" (string, required) The hex-encoded public key\n"
"2. \"label\" (string, optional, default=\"\") An optional label\n"
"3. rescan (boolean, optional, default=true) Rescan the wallet for transactions\n"
- "\nNote: This call can take minutes to complete if rescan is true.\n"
+ "\nNote: This call can take over an hour to complete if rescan is true, during that time, other rpc calls\n"
+ "may report that the imported pubkey exists but related transactions are still missing, leading to temporarily incorrect/bogus balances and unspent outputs until rescan completes.\n"
"\nExamples:\n"
"\nImport a public key with rescan\n"
+ HelpExampleCli("importpubkey", "\"mypubkey\"") +
@@ -373,49 +460,61 @@ UniValue importpubkey(const UniValue& params, bool fHelp)
);
- string strLabel = "";
- if (params.size() > 1)
- strLabel = params[1].get_str();
+ std::string strLabel;
+ if (!request.params[1].isNull())
+ strLabel = request.params[1].get_str();
// Whether to perform rescan after import
bool fRescan = true;
- if (params.size() > 2)
- fRescan = params[2].get_bool();
+ if (!request.params[2].isNull())
+ fRescan = request.params[2].get_bool();
if (fRescan && fPruneMode)
throw JSONRPCError(RPC_WALLET_ERROR, "Rescan is disabled in pruned mode");
- if (!IsHex(params[0].get_str()))
+ WalletRescanReserver reserver(pwallet);
+ if (fRescan && !reserver.reserve()) {
+ throw JSONRPCError(RPC_WALLET_ERROR, "Wallet is currently rescanning. Abort existing rescan or wait.");
+ }
+
+ if (!IsHex(request.params[0].get_str()))
throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Pubkey must be a hex string");
- std::vector<unsigned char> data(ParseHex(params[0].get_str()));
+ std::vector<unsigned char> data(ParseHex(request.params[0].get_str()));
CPubKey pubKey(data.begin(), data.end());
if (!pubKey.IsFullyValid())
throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Pubkey is not a valid public key");
- LOCK2(cs_main, pwalletMain->cs_wallet);
-
- ImportAddress(CBitcoinAddress(pubKey.GetID()), strLabel);
- ImportScript(GetScriptForRawPubKey(pubKey), strLabel, false);
+ {
+ LOCK2(cs_main, pwallet->cs_wallet);
+ for (const auto& dest : GetAllDestinationsForKey(pubKey)) {
+ ImportAddress(pwallet, dest, strLabel);
+ }
+ ImportScript(pwallet, GetScriptForRawPubKey(pubKey), strLabel, false);
+ pwallet->LearnAllRelatedScripts(pubKey);
+ }
if (fRescan)
{
- pwalletMain->ScanForWalletTransactions(chainActive.Genesis(), true);
- pwalletMain->ReacceptWalletTransactions();
+ RescanWallet(*pwallet, reserver);
+ pwallet->ReacceptWalletTransactions();
}
return NullUniValue;
}
-UniValue importwallet(const UniValue& params, bool fHelp)
+UniValue importwallet(const JSONRPCRequest& request)
{
- if (!EnsureWalletIsAvailable(fHelp))
+ std::shared_ptr<CWallet> const wallet = GetWalletForJSONRPCRequest(request);
+ CWallet* const pwallet = wallet.get();
+ if (!EnsureWalletIsAvailable(pwallet, request.fHelp)) {
return NullUniValue;
-
- if (fHelp || params.size() != 1)
- throw runtime_error(
+ }
+
+ if (request.fHelp || request.params.size() != 1)
+ throw std::runtime_error(
"importwallet \"filename\"\n"
- "\nImports keys from a wallet dump file (see dumpwallet).\n"
+ "\nImports keys from a wallet dump file (see dumpwallet). Requires a new wallet backup to include imported keys.\n"
"\nArguments:\n"
"1. \"filename\" (string, required) The wallet file\n"
"\nExamples:\n"
@@ -430,102 +529,124 @@ UniValue importwallet(const UniValue& params, bool fHelp)
if (fPruneMode)
throw JSONRPCError(RPC_WALLET_ERROR, "Importing wallets is disabled in pruned mode");
- LOCK2(cs_main, pwalletMain->cs_wallet);
-
- EnsureWalletIsUnlocked();
-
- ifstream file;
- file.open(params[0].get_str().c_str(), std::ios::in | std::ios::ate);
- if (!file.is_open())
- throw JSONRPCError(RPC_INVALID_PARAMETER, "Cannot open wallet dump file");
-
- int64_t nTimeBegin = chainActive.Tip()->GetBlockTime();
+ WalletRescanReserver reserver(pwallet);
+ if (!reserver.reserve()) {
+ throw JSONRPCError(RPC_WALLET_ERROR, "Wallet is currently rescanning. Abort existing rescan or wait.");
+ }
+ int64_t nTimeBegin = 0;
bool fGood = true;
+ {
+ LOCK2(cs_main, pwallet->cs_wallet);
- int64_t nFilesize = std::max((int64_t)1, (int64_t)file.tellg());
- file.seekg(0, file.beg);
-
- pwalletMain->ShowProgress(_("Importing..."), 0); // show progress dialog in GUI
- while (file.good()) {
- pwalletMain->ShowProgress("", std::max(1, std::min(99, (int)(((double)file.tellg() / (double)nFilesize) * 100))));
- std::string line;
- std::getline(file, line);
- if (line.empty() || line[0] == '#')
- continue;
-
- std::vector<std::string> vstr;
- boost::split(vstr, line, boost::is_any_of(" "));
- if (vstr.size() < 2)
- continue;
- CBitcoinSecret vchSecret;
- if (!vchSecret.SetString(vstr[0]))
- continue;
- CKey key = vchSecret.GetKey();
- CPubKey pubkey = key.GetPubKey();
- assert(key.VerifyPubKey(pubkey));
- CKeyID keyid = pubkey.GetID();
- if (pwalletMain->HaveKey(keyid)) {
- LogPrintf("Skipping import of %s (key already present)\n", CBitcoinAddress(keyid).ToString());
- continue;
+ EnsureWalletIsUnlocked(pwallet);
+
+ std::ifstream file;
+ file.open(request.params[0].get_str().c_str(), std::ios::in | std::ios::ate);
+ if (!file.is_open()) {
+ throw JSONRPCError(RPC_INVALID_PARAMETER, "Cannot open wallet dump file");
}
- int64_t nTime = DecodeDumpTime(vstr[1]);
- std::string strLabel;
- bool fLabel = true;
- for (unsigned int nStr = 2; nStr < vstr.size(); nStr++) {
- if (boost::algorithm::starts_with(vstr[nStr], "#"))
- break;
- if (vstr[nStr] == "change=1")
- fLabel = false;
- if (vstr[nStr] == "reserve=1")
- fLabel = false;
- if (boost::algorithm::starts_with(vstr[nStr], "label=")) {
- strLabel = DecodeDumpString(vstr[nStr].substr(6));
- fLabel = true;
+ nTimeBegin = chainActive.Tip()->GetBlockTime();
+
+ int64_t nFilesize = std::max((int64_t)1, (int64_t)file.tellg());
+ file.seekg(0, file.beg);
+
+ // Use uiInterface.ShowProgress instead of pwallet.ShowProgress because pwallet.ShowProgress has a cancel button tied to AbortRescan which
+ // we don't want for this progress bar showing the import progress. uiInterface.ShowProgress does not have a cancel button.
+ uiInterface.ShowProgress(strprintf("%s " + _("Importing..."), pwallet->GetDisplayName()), 0, false); // show progress dialog in GUI
+ while (file.good()) {
+ uiInterface.ShowProgress("", std::max(1, std::min(99, (int)(((double)file.tellg() / (double)nFilesize) * 100))), false);
+ std::string line;
+ std::getline(file, line);
+ if (line.empty() || line[0] == '#')
+ continue;
+
+ std::vector<std::string> vstr;
+ boost::split(vstr, line, boost::is_any_of(" "));
+ if (vstr.size() < 2)
+ continue;
+ CKey key = DecodeSecret(vstr[0]);
+ if (key.IsValid()) {
+ CPubKey pubkey = key.GetPubKey();
+ assert(key.VerifyPubKey(pubkey));
+ CKeyID keyid = pubkey.GetID();
+ if (pwallet->HaveKey(keyid)) {
+ pwallet->WalletLogPrintf("Skipping import of %s (key already present)\n", EncodeDestination(keyid));
+ continue;
+ }
+ int64_t nTime = DecodeDumpTime(vstr[1]);
+ std::string strLabel;
+ bool fLabel = true;
+ for (unsigned int nStr = 2; nStr < vstr.size(); nStr++) {
+ if (vstr[nStr].front() == '#')
+ break;
+ if (vstr[nStr] == "change=1")
+ fLabel = false;
+ if (vstr[nStr] == "reserve=1")
+ fLabel = false;
+ if (vstr[nStr].substr(0,6) == "label=") {
+ strLabel = DecodeDumpString(vstr[nStr].substr(6));
+ fLabel = true;
+ }
+ }
+ pwallet->WalletLogPrintf("Importing %s...\n", EncodeDestination(keyid));
+ if (!pwallet->AddKeyPubKey(key, pubkey)) {
+ fGood = false;
+ continue;
+ }
+ pwallet->mapKeyMetadata[keyid].nCreateTime = nTime;
+ if (fLabel)
+ pwallet->SetAddressBook(keyid, strLabel, "receive");
+ nTimeBegin = std::min(nTimeBegin, nTime);
+ } else if(IsHex(vstr[0])) {
+ std::vector<unsigned char> vData(ParseHex(vstr[0]));
+ CScript script = CScript(vData.begin(), vData.end());
+ CScriptID id(script);
+ if (pwallet->HaveCScript(id)) {
+ pwallet->WalletLogPrintf("Skipping import of %s (script already present)\n", vstr[0]);
+ continue;
+ }
+ if(!pwallet->AddCScript(script)) {
+ pwallet->WalletLogPrintf("Error importing script %s\n", vstr[0]);
+ fGood = false;
+ continue;
+ }
+ int64_t birth_time = DecodeDumpTime(vstr[1]);
+ if (birth_time > 0) {
+ pwallet->m_script_metadata[id].nCreateTime = birth_time;
+ nTimeBegin = std::min(nTimeBegin, birth_time);
+ }
}
}
- LogPrintf("Importing %s...\n", CBitcoinAddress(keyid).ToString());
- if (!pwalletMain->AddKeyPubKey(key, pubkey)) {
- fGood = false;
- continue;
- }
- pwalletMain->mapKeyMetadata[keyid].nCreateTime = nTime;
- if (fLabel)
- pwalletMain->SetAddressBook(keyid, strLabel, "receive");
- nTimeBegin = std::min(nTimeBegin, nTime);
+ file.close();
+ uiInterface.ShowProgress("", 100, false); // hide progress dialog in GUI
+ pwallet->UpdateTimeFirstKey(nTimeBegin);
}
- file.close();
- pwalletMain->ShowProgress("", 100); // hide progress dialog in GUI
-
- CBlockIndex *pindex = chainActive.Tip();
- while (pindex && pindex->pprev && pindex->GetBlockTime() > nTimeBegin - 7200)
- pindex = pindex->pprev;
-
- if (!pwalletMain->nTimeFirstKey || nTimeBegin < pwalletMain->nTimeFirstKey)
- pwalletMain->nTimeFirstKey = nTimeBegin;
-
- LogPrintf("Rescanning last %i blocks\n", chainActive.Height() - pindex->nHeight + 1);
- pwalletMain->ScanForWalletTransactions(pindex);
- pwalletMain->MarkDirty();
+ uiInterface.ShowProgress("", 100, false); // hide progress dialog in GUI
+ RescanWallet(*pwallet, reserver, nTimeBegin, false /* update */);
+ pwallet->MarkDirty();
if (!fGood)
- throw JSONRPCError(RPC_WALLET_ERROR, "Error adding some keys to wallet");
+ throw JSONRPCError(RPC_WALLET_ERROR, "Error adding some keys/scripts to wallet");
return NullUniValue;
}
-UniValue dumpprivkey(const UniValue& params, bool fHelp)
+UniValue dumpprivkey(const JSONRPCRequest& request)
{
- if (!EnsureWalletIsAvailable(fHelp))
+ std::shared_ptr<CWallet> const wallet = GetWalletForJSONRPCRequest(request);
+ CWallet* const pwallet = wallet.get();
+ if (!EnsureWalletIsAvailable(pwallet, request.fHelp)) {
return NullUniValue;
-
- if (fHelp || params.size() != 1)
- throw runtime_error(
- "dumpprivkey \"bitcoinaddress\"\n"
- "\nReveals the private key corresponding to 'bitcoinaddress'.\n"
+ }
+
+ if (request.fHelp || request.params.size() != 1)
+ throw std::runtime_error(
+ "dumpprivkey \"address\"\n"
+ "\nReveals the private key corresponding to 'address'.\n"
"Then the importprivkey can be used with this output\n"
"\nArguments:\n"
- "1. \"bitcoinaddress\" (string, required) The bitcoin address for the private key\n"
+ "1. \"address\" (string, required) The bitcoin address for the private key\n"
"\nResult:\n"
"\"key\" (string) The private key\n"
"\nExamples:\n"
@@ -534,108 +655,623 @@ UniValue dumpprivkey(const UniValue& params, bool fHelp)
+ HelpExampleRpc("dumpprivkey", "\"myaddress\"")
);
- LOCK2(cs_main, pwalletMain->cs_wallet);
+ LOCK2(cs_main, pwallet->cs_wallet);
- EnsureWalletIsUnlocked();
+ EnsureWalletIsUnlocked(pwallet);
- string strAddress = params[0].get_str();
- CBitcoinAddress address;
- if (!address.SetString(strAddress))
+ std::string strAddress = request.params[0].get_str();
+ CTxDestination dest = DecodeDestination(strAddress);
+ if (!IsValidDestination(dest)) {
throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Invalid Bitcoin address");
- CKeyID keyID;
- if (!address.GetKeyID(keyID))
+ }
+ auto keyid = GetKeyForDestination(*pwallet, dest);
+ if (keyid.IsNull()) {
throw JSONRPCError(RPC_TYPE_ERROR, "Address does not refer to a key");
+ }
CKey vchSecret;
- if (!pwalletMain->GetKey(keyID, vchSecret))
+ if (!pwallet->GetKey(keyid, vchSecret)) {
throw JSONRPCError(RPC_WALLET_ERROR, "Private key for address " + strAddress + " is not known");
- return CBitcoinSecret(vchSecret).ToString();
+ }
+ return EncodeSecret(vchSecret);
}
-UniValue dumpwallet(const UniValue& params, bool fHelp)
+UniValue dumpwallet(const JSONRPCRequest& request)
{
- if (!EnsureWalletIsAvailable(fHelp))
+ std::shared_ptr<CWallet> const wallet = GetWalletForJSONRPCRequest(request);
+ CWallet* const pwallet = wallet.get();
+ if (!EnsureWalletIsAvailable(pwallet, request.fHelp)) {
return NullUniValue;
-
- if (fHelp || params.size() != 1)
- throw runtime_error(
+ }
+
+ if (request.fHelp || request.params.size() != 1)
+ throw std::runtime_error(
"dumpwallet \"filename\"\n"
- "\nDumps all wallet keys in a human-readable format.\n"
+ "\nDumps all wallet keys in a human-readable format to a server-side file. This does not allow overwriting existing files.\n"
+ "Imported scripts are included in the dumpfile, but corresponding BIP173 addresses, etc. may not be added automatically by importwallet.\n"
+ "Note that if your wallet contains keys which are not derived from your HD seed (e.g. imported keys), these are not covered by\n"
+ "only backing up the seed itself, and must be backed up too (e.g. ensure you back up the whole dumpfile).\n"
"\nArguments:\n"
- "1. \"filename\" (string, required) The filename\n"
+ "1. \"filename\" (string, required) The filename with path (either absolute or relative to bitcoind)\n"
+ "\nResult:\n"
+ "{ (json object)\n"
+ " \"filename\" : { (string) The filename with full absolute path\n"
+ "}\n"
"\nExamples:\n"
+ HelpExampleCli("dumpwallet", "\"test\"")
+ HelpExampleRpc("dumpwallet", "\"test\"")
);
- LOCK2(cs_main, pwalletMain->cs_wallet);
+ LOCK2(cs_main, pwallet->cs_wallet);
- EnsureWalletIsUnlocked();
+ EnsureWalletIsUnlocked(pwallet);
- ofstream file;
- file.open(params[0].get_str().c_str());
+ fs::path filepath = request.params[0].get_str();
+ filepath = fs::absolute(filepath);
+
+ /* Prevent arbitrary files from being overwritten. There have been reports
+ * that users have overwritten wallet files this way:
+ * https://github.com/bitcoin/bitcoin/issues/9934
+ * It may also avoid other security issues.
+ */
+ if (fs::exists(filepath)) {
+ throw JSONRPCError(RPC_INVALID_PARAMETER, filepath.string() + " already exists. If you are sure this is what you want, move it out of the way first");
+ }
+
+ std::ofstream file;
+ file.open(filepath.string().c_str());
if (!file.is_open())
throw JSONRPCError(RPC_INVALID_PARAMETER, "Cannot open wallet dump file");
- std::map<CKeyID, int64_t> mapKeyBirth;
- std::set<CKeyID> setKeyPool;
- pwalletMain->GetKeyBirthTimes(mapKeyBirth);
- pwalletMain->GetAllReserveKeys(setKeyPool);
+ std::map<CTxDestination, int64_t> mapKeyBirth;
+ const std::map<CKeyID, int64_t>& mapKeyPool = pwallet->GetAllReserveKeys();
+ pwallet->GetKeyBirthTimes(mapKeyBirth);
+
+ std::set<CScriptID> scripts = pwallet->GetCScripts();
+ // TODO: include scripts in GetKeyBirthTimes() output instead of separate
// sort time/key pairs
std::vector<std::pair<int64_t, CKeyID> > vKeyBirth;
- for (std::map<CKeyID, int64_t>::const_iterator it = mapKeyBirth.begin(); it != mapKeyBirth.end(); it++) {
- vKeyBirth.push_back(std::make_pair(it->second, it->first));
+ for (const auto& entry : mapKeyBirth) {
+ if (const CKeyID* keyID = boost::get<CKeyID>(&entry.first)) { // set and test
+ vKeyBirth.push_back(std::make_pair(entry.second, *keyID));
+ }
}
mapKeyBirth.clear();
std::sort(vKeyBirth.begin(), vKeyBirth.end());
// produce output
file << strprintf("# Wallet dump created by Bitcoin %s\n", CLIENT_BUILD);
- file << strprintf("# * Created on %s\n", EncodeDumpTime(GetTime()));
+ file << strprintf("# * Created on %s\n", FormatISO8601DateTime(GetTime()));
file << strprintf("# * Best block at time of backup was %i (%s),\n", chainActive.Height(), chainActive.Tip()->GetBlockHash().ToString());
- file << strprintf("# mined on %s\n", EncodeDumpTime(chainActive.Tip()->GetBlockTime()));
+ file << strprintf("# mined on %s\n", FormatISO8601DateTime(chainActive.Tip()->GetBlockTime()));
file << "\n";
- // add the base58check encoded extended master if the wallet uses HD
- CKeyID masterKeyID = pwalletMain->GetHDChain().masterKeyID;
- if (!masterKeyID.IsNull())
+ // add the base58check encoded extended master if the wallet uses HD
+ CKeyID seed_id = pwallet->GetHDChain().seed_id;
+ if (!seed_id.IsNull())
{
- CKey key;
- if (pwalletMain->GetKey(masterKeyID, key))
- {
+ CKey seed;
+ if (pwallet->GetKey(seed_id, seed)) {
CExtKey masterKey;
- masterKey.SetMaster(key.begin(), key.size());
-
- CBitcoinExtKey b58extkey;
- b58extkey.SetKey(masterKey);
+ masterKey.SetSeed(seed.begin(), seed.size());
- file << "# extended private masterkey: " << b58extkey.ToString() << "\n\n";
+ file << "# extended private masterkey: " << EncodeExtKey(masterKey) << "\n\n";
}
}
for (std::vector<std::pair<int64_t, CKeyID> >::const_iterator it = vKeyBirth.begin(); it != vKeyBirth.end(); it++) {
const CKeyID &keyid = it->second;
- std::string strTime = EncodeDumpTime(it->first);
- std::string strAddr = CBitcoinAddress(keyid).ToString();
+ std::string strTime = FormatISO8601DateTime(it->first);
+ std::string strAddr;
+ std::string strLabel;
CKey key;
- if (pwalletMain->GetKey(keyid, key)) {
- file << strprintf("%s %s ", CBitcoinSecret(key).ToString(), strTime);
- if (pwalletMain->mapAddressBook.count(keyid)) {
- file << strprintf("label=%s", EncodeDumpString(pwalletMain->mapAddressBook[keyid].name));
- } else if (keyid == masterKeyID) {
- file << "hdmaster=1";
- } else if (setKeyPool.count(keyid)) {
+ if (pwallet->GetKey(keyid, key)) {
+ file << strprintf("%s %s ", EncodeSecret(key), strTime);
+ if (GetWalletAddressesForKey(pwallet, keyid, strAddr, strLabel)) {
+ file << strprintf("label=%s", strLabel);
+ } else if (keyid == seed_id) {
+ file << "hdseed=1";
+ } else if (mapKeyPool.count(keyid)) {
file << "reserve=1";
- } else if (pwalletMain->mapKeyMetadata[keyid].hdKeypath == "m") {
- file << "inactivehdmaster=1";
+ } else if (pwallet->mapKeyMetadata[keyid].hdKeypath == "s") {
+ file << "inactivehdseed=1";
} else {
file << "change=1";
}
- file << strprintf(" # addr=%s%s\n", strAddr, (pwalletMain->mapKeyMetadata[keyid].hdKeypath.size() > 0 ? " hdkeypath="+pwalletMain->mapKeyMetadata[keyid].hdKeypath : ""));
+ file << strprintf(" # addr=%s%s\n", strAddr, (pwallet->mapKeyMetadata[keyid].hdKeypath.size() > 0 ? " hdkeypath="+pwallet->mapKeyMetadata[keyid].hdKeypath : ""));
+ }
+ }
+ file << "\n";
+ for (const CScriptID &scriptid : scripts) {
+ CScript script;
+ std::string create_time = "0";
+ std::string address = EncodeDestination(scriptid);
+ // get birth times for scripts with metadata
+ auto it = pwallet->m_script_metadata.find(scriptid);
+ if (it != pwallet->m_script_metadata.end()) {
+ create_time = FormatISO8601DateTime(it->second.nCreateTime);
+ }
+ if(pwallet->GetCScript(scriptid, script)) {
+ file << strprintf("%s %s script=1", HexStr(script.begin(), script.end()), create_time);
+ file << strprintf(" # addr=%s\n", address);
}
}
file << "\n";
file << "# End of dump\n";
file.close();
- return NullUniValue;
+
+ UniValue reply(UniValue::VOBJ);
+ reply.pushKV("filename", filepath.string());
+
+ return reply;
+}
+
+
+static UniValue ProcessImport(CWallet * const pwallet, const UniValue& data, const int64_t timestamp) EXCLUSIVE_LOCKS_REQUIRED(pwallet->cs_wallet)
+{
+ try {
+ bool success = false;
+
+ // Required fields.
+ const UniValue& scriptPubKey = data["scriptPubKey"];
+
+ // Should have script or JSON with "address".
+ if (!(scriptPubKey.getType() == UniValue::VOBJ && scriptPubKey.exists("address")) && !(scriptPubKey.getType() == UniValue::VSTR)) {
+ throw JSONRPCError(RPC_INVALID_PARAMETER, "Invalid scriptPubKey");
+ }
+
+ // Optional fields.
+ const std::string& strRedeemScript = data.exists("redeemscript") ? data["redeemscript"].get_str() : "";
+ const UniValue& pubKeys = data.exists("pubkeys") ? data["pubkeys"].get_array() : UniValue();
+ const UniValue& keys = data.exists("keys") ? data["keys"].get_array() : UniValue();
+ const bool internal = data.exists("internal") ? data["internal"].get_bool() : false;
+ const bool watchOnly = data.exists("watchonly") ? data["watchonly"].get_bool() : false;
+ const std::string& label = data.exists("label") && !internal ? data["label"].get_str() : "";
+
+ bool isScript = scriptPubKey.getType() == UniValue::VSTR;
+ bool isP2SH = strRedeemScript.length() > 0;
+ const std::string& output = isScript ? scriptPubKey.get_str() : scriptPubKey["address"].get_str();
+
+ // Parse the output.
+ CScript script;
+ CTxDestination dest;
+
+ if (!isScript) {
+ dest = DecodeDestination(output);
+ if (!IsValidDestination(dest)) {
+ throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Invalid address");
+ }
+ script = GetScriptForDestination(dest);
+ } else {
+ if (!IsHex(output)) {
+ throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Invalid scriptPubKey");
+ }
+
+ std::vector<unsigned char> vData(ParseHex(output));
+ script = CScript(vData.begin(), vData.end());
+ }
+
+ // Watchonly and private keys
+ if (watchOnly && keys.size()) {
+ throw JSONRPCError(RPC_INVALID_PARAMETER, "Incompatibility found between watchonly and keys");
+ }
+
+ // Internal + Label
+ if (internal && data.exists("label")) {
+ throw JSONRPCError(RPC_INVALID_PARAMETER, "Incompatibility found between internal and label");
+ }
+
+ // Not having Internal + Script
+ if (!internal && isScript) {
+ throw JSONRPCError(RPC_INVALID_PARAMETER, "Internal must be set for hex scriptPubKey");
+ }
+
+ // Keys / PubKeys size check.
+ if (!isP2SH && (keys.size() > 1 || pubKeys.size() > 1)) { // Address / scriptPubKey
+ throw JSONRPCError(RPC_INVALID_PARAMETER, "More than private key given for one address");
+ }
+
+ // Invalid P2SH redeemScript
+ if (isP2SH && !IsHex(strRedeemScript)) {
+ throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Invalid redeem script");
+ }
+
+ // Process. //
+
+ // P2SH
+ if (isP2SH) {
+ // Import redeem script.
+ std::vector<unsigned char> vData(ParseHex(strRedeemScript));
+ CScript redeemScript = CScript(vData.begin(), vData.end());
+
+ // Invalid P2SH address
+ if (!script.IsPayToScriptHash()) {
+ throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Invalid P2SH address / script");
+ }
+
+ pwallet->MarkDirty();
+
+ if (!pwallet->AddWatchOnly(redeemScript, timestamp)) {
+ throw JSONRPCError(RPC_WALLET_ERROR, "Error adding address to wallet");
+ }
+
+ CScriptID redeem_id(redeemScript);
+ if (!pwallet->HaveCScript(redeem_id) && !pwallet->AddCScript(redeemScript)) {
+ throw JSONRPCError(RPC_WALLET_ERROR, "Error adding p2sh redeemScript to wallet");
+ }
+
+ CScript redeemDestination = GetScriptForDestination(redeem_id);
+
+ if (::IsMine(*pwallet, redeemDestination) == ISMINE_SPENDABLE) {
+ throw JSONRPCError(RPC_WALLET_ERROR, "The wallet already contains the private key for this address or script");
+ }
+
+ pwallet->MarkDirty();
+
+ if (!pwallet->AddWatchOnly(redeemDestination, timestamp)) {
+ throw JSONRPCError(RPC_WALLET_ERROR, "Error adding address to wallet");
+ }
+
+ // add to address book or update label
+ if (IsValidDestination(dest)) {
+ pwallet->SetAddressBook(dest, label, "receive");
+ }
+
+ // Import private keys.
+ if (keys.size()) {
+ for (size_t i = 0; i < keys.size(); i++) {
+ const std::string& privkey = keys[i].get_str();
+
+ CKey key = DecodeSecret(privkey);
+
+ if (!key.IsValid()) {
+ throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Invalid private key encoding");
+ }
+
+ CPubKey pubkey = key.GetPubKey();
+ assert(key.VerifyPubKey(pubkey));
+
+ CKeyID vchAddress = pubkey.GetID();
+ pwallet->MarkDirty();
+ pwallet->SetAddressBook(vchAddress, label, "receive");
+
+ if (pwallet->HaveKey(vchAddress)) {
+ throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Already have this key");
+ }
+
+ pwallet->mapKeyMetadata[vchAddress].nCreateTime = timestamp;
+
+ if (!pwallet->AddKeyPubKey(key, pubkey)) {
+ throw JSONRPCError(RPC_WALLET_ERROR, "Error adding key to wallet");
+ }
+
+ pwallet->UpdateTimeFirstKey(timestamp);
+ }
+ }
+
+ success = true;
+ } else {
+ // Import public keys.
+ if (pubKeys.size() && keys.size() == 0) {
+ const std::string& strPubKey = pubKeys[0].get_str();
+
+ if (!IsHex(strPubKey)) {
+ throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Pubkey must be a hex string");
+ }
+
+ std::vector<unsigned char> vData(ParseHex(strPubKey));
+ CPubKey pubKey(vData.begin(), vData.end());
+
+ if (!pubKey.IsFullyValid()) {
+ throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Pubkey is not a valid public key");
+ }
+
+ CTxDestination pubkey_dest = pubKey.GetID();
+
+ // Consistency check.
+ if (!isScript && !(pubkey_dest == dest)) {
+ throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Consistency check failed");
+ }
+
+ // Consistency check.
+ if (isScript) {
+ CTxDestination destination;
+
+ if (ExtractDestination(script, destination)) {
+ if (!(destination == pubkey_dest)) {
+ throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Consistency check failed");
+ }
+ }
+ }
+
+ CScript pubKeyScript = GetScriptForDestination(pubkey_dest);
+
+ if (::IsMine(*pwallet, pubKeyScript) == ISMINE_SPENDABLE) {
+ throw JSONRPCError(RPC_WALLET_ERROR, "The wallet already contains the private key for this address or script");
+ }
+
+ pwallet->MarkDirty();
+
+ if (!pwallet->AddWatchOnly(pubKeyScript, timestamp)) {
+ throw JSONRPCError(RPC_WALLET_ERROR, "Error adding address to wallet");
+ }
+
+ // add to address book or update label
+ if (IsValidDestination(pubkey_dest)) {
+ pwallet->SetAddressBook(pubkey_dest, label, "receive");
+ }
+
+ // TODO Is this necessary?
+ CScript scriptRawPubKey = GetScriptForRawPubKey(pubKey);
+
+ if (::IsMine(*pwallet, scriptRawPubKey) == ISMINE_SPENDABLE) {
+ throw JSONRPCError(RPC_WALLET_ERROR, "The wallet already contains the private key for this address or script");
+ }
+
+ pwallet->MarkDirty();
+
+ if (!pwallet->AddWatchOnly(scriptRawPubKey, timestamp)) {
+ throw JSONRPCError(RPC_WALLET_ERROR, "Error adding address to wallet");
+ }
+
+ success = true;
+ }
+
+ // Import private keys.
+ if (keys.size()) {
+ const std::string& strPrivkey = keys[0].get_str();
+
+ // Checks.
+ CKey key = DecodeSecret(strPrivkey);
+
+ if (!key.IsValid()) {
+ throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Invalid private key encoding");
+ }
+
+ CPubKey pubKey = key.GetPubKey();
+ assert(key.VerifyPubKey(pubKey));
+
+ CTxDestination pubkey_dest = pubKey.GetID();
+
+ // Consistency check.
+ if (!isScript && !(pubkey_dest == dest)) {
+ throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Consistency check failed");
+ }
+
+ // Consistency check.
+ if (isScript) {
+ CTxDestination destination;
+
+ if (ExtractDestination(script, destination)) {
+ if (!(destination == pubkey_dest)) {
+ throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Consistency check failed");
+ }
+ }
+ }
+
+ CKeyID vchAddress = pubKey.GetID();
+ pwallet->MarkDirty();
+ pwallet->SetAddressBook(vchAddress, label, "receive");
+
+ if (pwallet->HaveKey(vchAddress)) {
+ throw JSONRPCError(RPC_WALLET_ERROR, "The wallet already contains the private key for this address or script");
+ }
+
+ pwallet->mapKeyMetadata[vchAddress].nCreateTime = timestamp;
+
+ if (!pwallet->AddKeyPubKey(key, pubKey)) {
+ throw JSONRPCError(RPC_WALLET_ERROR, "Error adding key to wallet");
+ }
+
+ pwallet->UpdateTimeFirstKey(timestamp);
+
+ success = true;
+ }
+
+ // Import scriptPubKey only.
+ if (pubKeys.size() == 0 && keys.size() == 0) {
+ if (::IsMine(*pwallet, script) == ISMINE_SPENDABLE) {
+ throw JSONRPCError(RPC_WALLET_ERROR, "The wallet already contains the private key for this address or script");
+ }
+
+ pwallet->MarkDirty();
+
+ if (!pwallet->AddWatchOnly(script, timestamp)) {
+ throw JSONRPCError(RPC_WALLET_ERROR, "Error adding address to wallet");
+ }
+
+ if (scriptPubKey.getType() == UniValue::VOBJ) {
+ // add to address book or update label
+ if (IsValidDestination(dest)) {
+ pwallet->SetAddressBook(dest, label, "receive");
+ }
+ }
+
+ success = true;
+ }
+ }
+
+ UniValue result = UniValue(UniValue::VOBJ);
+ result.pushKV("success", UniValue(success));
+ return result;
+ } catch (const UniValue& e) {
+ UniValue result = UniValue(UniValue::VOBJ);
+ result.pushKV("success", UniValue(false));
+ result.pushKV("error", e);
+ return result;
+ } catch (...) {
+ UniValue result = UniValue(UniValue::VOBJ);
+ result.pushKV("success", UniValue(false));
+ result.pushKV("error", JSONRPCError(RPC_MISC_ERROR, "Missing required fields"));
+ return result;
+ }
+}
+
+static int64_t GetImportTimestamp(const UniValue& data, int64_t now)
+{
+ if (data.exists("timestamp")) {
+ const UniValue& timestamp = data["timestamp"];
+ if (timestamp.isNum()) {
+ return timestamp.get_int64();
+ } else if (timestamp.isStr() && timestamp.get_str() == "now") {
+ return now;
+ }
+ throw JSONRPCError(RPC_TYPE_ERROR, strprintf("Expected number or \"now\" timestamp value for key. got type %s", uvTypeName(timestamp.type())));
+ }
+ throw JSONRPCError(RPC_TYPE_ERROR, "Missing required timestamp field for key");
+}
+
+UniValue importmulti(const JSONRPCRequest& mainRequest)
+{
+ std::shared_ptr<CWallet> const wallet = GetWalletForJSONRPCRequest(mainRequest);
+ CWallet* const pwallet = wallet.get();
+ if (!EnsureWalletIsAvailable(pwallet, mainRequest.fHelp)) {
+ return NullUniValue;
+ }
+
+ // clang-format off
+ if (mainRequest.fHelp || mainRequest.params.size() < 1 || mainRequest.params.size() > 2)
+ throw std::runtime_error(
+ "importmulti \"requests\" ( \"options\" )\n\n"
+ "Import addresses/scripts (with private or public keys, redeem script (P2SH)), rescanning all addresses in one-shot-only (rescan can be disabled via options). Requires a new wallet backup.\n\n"
+ "Arguments:\n"
+ "1. requests (array, required) Data to be imported\n"
+ " [ (array of json objects)\n"
+ " {\n"
+ " \"scriptPubKey\": \"<script>\" | { \"address\":\"<address>\" }, (string / json, required) Type of scriptPubKey (string for script, json for address)\n"
+ " \"timestamp\": timestamp | \"now\" , (integer / string, required) Creation time of the key in seconds since epoch (Jan 1 1970 GMT),\n"
+ " or the string \"now\" to substitute the current synced blockchain time. The timestamp of the oldest\n"
+ " key will determine how far back blockchain rescans need to begin for missing wallet transactions.\n"
+ " \"now\" can be specified to bypass scanning, for keys which are known to never have been used, and\n"
+ " 0 can be specified to scan the entire blockchain. Blocks up to 2 hours before the earliest key\n"
+ " creation time of all keys being imported by the importmulti call will be scanned.\n"
+ " \"redeemscript\": \"<script>\" , (string, optional) Allowed only if the scriptPubKey is a P2SH address or a P2SH scriptPubKey\n"
+ " \"pubkeys\": [\"<pubKey>\", ... ] , (array, optional) Array of strings giving pubkeys that must occur in the output or redeemscript\n"
+ " \"keys\": [\"<key>\", ... ] , (array, optional) Array of strings giving private keys whose corresponding public keys must occur in the output or redeemscript\n"
+ " \"internal\": <true> , (boolean, optional, default: false) Stating whether matching outputs should be treated as not incoming payments\n"
+ " \"watchonly\": <true> , (boolean, optional, default: false) Stating whether matching outputs should be considered watched even when they're not spendable, only allowed if keys are empty\n"
+ " \"label\": <label> , (string, optional, default: '') Label to assign to the address (aka account name, for now), only allowed with internal=false\n"
+ " }\n"
+ " ,...\n"
+ " ]\n"
+ "2. options (json, optional)\n"
+ " {\n"
+ " \"rescan\": <false>, (boolean, optional, default: true) Stating if should rescan the blockchain after all imports\n"
+ " }\n"
+ "\nNote: This call can take over an hour to complete if rescan is true, during that time, other rpc calls\n"
+ "may report that the imported keys, addresses or scripts exists but related transactions are still missing.\n"
+ "\nExamples:\n" +
+ HelpExampleCli("importmulti", "'[{ \"scriptPubKey\": { \"address\": \"<my address>\" }, \"timestamp\":1455191478 }, "
+ "{ \"scriptPubKey\": { \"address\": \"<my 2nd address>\" }, \"label\": \"example 2\", \"timestamp\": 1455191480 }]'") +
+ HelpExampleCli("importmulti", "'[{ \"scriptPubKey\": { \"address\": \"<my address>\" }, \"timestamp\":1455191478 }]' '{ \"rescan\": false}'") +
+
+ "\nResponse is an array with the same size as the input that has the execution result :\n"
+ " [{ \"success\": true } , { \"success\": false, \"error\": { \"code\": -1, \"message\": \"Internal Server Error\"} }, ... ]\n");
+
+ // clang-format on
+
+ RPCTypeCheck(mainRequest.params, {UniValue::VARR, UniValue::VOBJ});
+
+ const UniValue& requests = mainRequest.params[0];
+
+ //Default options
+ bool fRescan = true;
+
+ if (!mainRequest.params[1].isNull()) {
+ const UniValue& options = mainRequest.params[1];
+
+ if (options.exists("rescan")) {
+ fRescan = options["rescan"].get_bool();
+ }
+ }
+
+ WalletRescanReserver reserver(pwallet);
+ if (fRescan && !reserver.reserve()) {
+ throw JSONRPCError(RPC_WALLET_ERROR, "Wallet is currently rescanning. Abort existing rescan or wait.");
+ }
+
+ int64_t now = 0;
+ bool fRunScan = false;
+ int64_t nLowestTimestamp = 0;
+ UniValue response(UniValue::VARR);
+ {
+ LOCK2(cs_main, pwallet->cs_wallet);
+ EnsureWalletIsUnlocked(pwallet);
+
+ // Verify all timestamps are present before importing any keys.
+ now = chainActive.Tip() ? chainActive.Tip()->GetMedianTimePast() : 0;
+ for (const UniValue& data : requests.getValues()) {
+ GetImportTimestamp(data, now);
+ }
+
+ const int64_t minimumTimestamp = 1;
+
+ if (fRescan && chainActive.Tip()) {
+ nLowestTimestamp = chainActive.Tip()->GetBlockTime();
+ } else {
+ fRescan = false;
+ }
+
+ for (const UniValue& data : requests.getValues()) {
+ const int64_t timestamp = std::max(GetImportTimestamp(data, now), minimumTimestamp);
+ const UniValue result = ProcessImport(pwallet, data, timestamp);
+ response.push_back(result);
+
+ if (!fRescan) {
+ continue;
+ }
+
+ // If at least one request was successful then allow rescan.
+ if (result["success"].get_bool()) {
+ fRunScan = true;
+ }
+
+ // Get the lowest timestamp.
+ if (timestamp < nLowestTimestamp) {
+ nLowestTimestamp = timestamp;
+ }
+ }
+ }
+ if (fRescan && fRunScan && requests.size()) {
+ int64_t scannedTime = pwallet->RescanFromTime(nLowestTimestamp, reserver, true /* update */);
+ pwallet->ReacceptWalletTransactions();
+
+ if (pwallet->IsAbortingRescan()) {
+ throw JSONRPCError(RPC_MISC_ERROR, "Rescan aborted by user.");
+ }
+ if (scannedTime > nLowestTimestamp) {
+ std::vector<UniValue> results = response.getValues();
+ response.clear();
+ response.setArray();
+ size_t i = 0;
+ for (const UniValue& request : requests.getValues()) {
+ // If key creation date is within the successfully scanned
+ // range, or if the import result already has an error set, let
+ // the result stand unmodified. Otherwise replace the result
+ // with an error message.
+ if (scannedTime <= GetImportTimestamp(request, now) || results.at(i).exists("error")) {
+ response.push_back(results.at(i));
+ } else {
+ UniValue result = UniValue(UniValue::VOBJ);
+ result.pushKV("success", UniValue(false));
+ result.pushKV(
+ "error",
+ JSONRPCError(
+ RPC_MISC_ERROR,
+ strprintf("Rescan failed for key with creation timestamp %d. There was an error reading a "
+ "block from time %d, which is after or within %d seconds of key creation, and "
+ "could contain transactions pertaining to the key. As a result, transactions "
+ "and coins using this key may not appear in the wallet. This error could be "
+ "caused by pruning or data corruption (see bitcoind log for details) and could "
+ "be dealt with by downloading and rescanning the relevant blocks (see -reindex "
+ "and -rescan options).",
+ GetImportTimestamp(request, now), scannedTime - TIMESTAMP_WINDOW - 1, TIMESTAMP_WINDOW)));
+ response.push_back(std::move(result));
+ }
+ ++i;
+ }
+ }
+ }
+
+ return response;
}
diff --git a/src/wallet/rpcwallet.cpp b/src/wallet/rpcwallet.cpp
index aa0a9374c1..b108d6df53 100644
--- a/src/wallet/rpcwallet.cpp
+++ b/src/wallet/rpcwallet.cpp
@@ -1,171 +1,235 @@
// Copyright (c) 2010 Satoshi Nakamoto
-// Copyright (c) 2009-2015 The Bitcoin Core developers
+// Copyright (c) 2009-2018 The Bitcoin Core developers
// Distributed under the MIT software license, see the accompanying
// file COPYING or http://www.opensource.org/licenses/mit-license.php.
-#include "amount.h"
-#include "base58.h"
-#include "chain.h"
-#include "core_io.h"
-#include "init.h"
-#include "main.h"
-#include "net.h"
-#include "policy/rbf.h"
-#include "rpc/server.h"
-#include "timedata.h"
-#include "util.h"
-#include "utilmoneystr.h"
-#include "wallet.h"
-#include "walletdb.h"
+#include <amount.h>
+#include <chain.h>
+#include <consensus/validation.h>
+#include <core_io.h>
+#include <httpserver.h>
+#include <validation.h>
+#include <key_io.h>
+#include <net.h>
+#include <outputtype.h>
+#include <policy/feerate.h>
+#include <policy/fees.h>
+#include <policy/policy.h>
+#include <policy/rbf.h>
+#include <rpc/mining.h>
+#include <rpc/rawtransaction.h>
+#include <rpc/server.h>
+#include <rpc/util.h>
+#include <script/sign.h>
+#include <shutdown.h>
+#include <timedata.h>
+#include <util.h>
+#include <utilmoneystr.h>
+#include <wallet/coincontrol.h>
+#include <wallet/feebumper.h>
+#include <wallet/rpcwallet.h>
+#include <wallet/wallet.h>
+#include <wallet/walletdb.h>
+#include <wallet/walletutil.h>
#include <stdint.h>
-#include <boost/assign/list_of.hpp>
-
#include <univalue.h>
-using namespace std;
+#include <functional>
+
+static const std::string WALLET_ENDPOINT_BASE = "/wallet/";
+
+bool GetWalletNameFromJSONRPCRequest(const JSONRPCRequest& request, std::string& wallet_name)
+{
+ if (request.URI.substr(0, WALLET_ENDPOINT_BASE.size()) == WALLET_ENDPOINT_BASE) {
+ // wallet endpoint was used
+ wallet_name = urlDecode(request.URI.substr(WALLET_ENDPOINT_BASE.size()));
+ return true;
+ }
+ return false;
+}
+
+std::shared_ptr<CWallet> GetWalletForJSONRPCRequest(const JSONRPCRequest& request)
+{
+ std::string wallet_name;
+ if (GetWalletNameFromJSONRPCRequest(request, wallet_name)) {
+ std::shared_ptr<CWallet> pwallet = GetWallet(wallet_name);
+ if (!pwallet) throw JSONRPCError(RPC_WALLET_NOT_FOUND, "Requested wallet does not exist or is not loaded");
+ return pwallet;
+ }
-int64_t nWalletUnlockTime;
-static CCriticalSection cs_nWalletUnlockTime;
+ std::vector<std::shared_ptr<CWallet>> wallets = GetWallets();
+ return wallets.size() == 1 || (request.fHelp && wallets.size() > 0) ? wallets[0] : nullptr;
+}
-std::string HelpRequiringPassphrase()
+std::string HelpRequiringPassphrase(CWallet * const pwallet)
{
- return pwalletMain && pwalletMain->IsCrypted()
+ return pwallet && pwallet->IsCrypted()
? "\nRequires wallet passphrase to be set with walletpassphrase call."
: "";
}
-bool EnsureWalletIsAvailable(bool avoidException)
+bool EnsureWalletIsAvailable(CWallet * const pwallet, bool avoidException)
{
- if (!pwalletMain)
- {
- if (!avoidException)
- throw JSONRPCError(RPC_METHOD_NOT_FOUND, "Method not found (disabled)");
- else
- return false;
+ if (pwallet) return true;
+ if (avoidException) return false;
+ if (!HasWallets()) {
+ throw JSONRPCError(
+ RPC_METHOD_NOT_FOUND, "Method not found (wallet method is disabled because no wallet is loaded)");
}
- return true;
+ throw JSONRPCError(RPC_WALLET_NOT_SPECIFIED,
+ "Wallet file not specified (must request wallet RPC through /wallet/<filename> uri-path).");
}
-void EnsureWalletIsUnlocked()
+void EnsureWalletIsUnlocked(CWallet * const pwallet)
{
- if (pwalletMain->IsLocked())
+ if (pwallet->IsLocked()) {
throw JSONRPCError(RPC_WALLET_UNLOCK_NEEDED, "Error: Please enter the wallet passphrase with walletpassphrase first.");
+ }
}
-void WalletTxToJSON(const CWalletTx& wtx, UniValue& entry)
+static void WalletTxToJSON(const CWalletTx& wtx, UniValue& entry)
{
int confirms = wtx.GetDepthInMainChain();
- entry.push_back(Pair("confirmations", confirms));
+ entry.pushKV("confirmations", confirms);
if (wtx.IsCoinBase())
- entry.push_back(Pair("generated", true));
+ entry.pushKV("generated", true);
if (confirms > 0)
{
- entry.push_back(Pair("blockhash", wtx.hashBlock.GetHex()));
- entry.push_back(Pair("blockindex", wtx.nIndex));
- entry.push_back(Pair("blocktime", mapBlockIndex[wtx.hashBlock]->GetBlockTime()));
+ entry.pushKV("blockhash", wtx.hashBlock.GetHex());
+ entry.pushKV("blockindex", wtx.nIndex);
+ entry.pushKV("blocktime", LookupBlockIndex(wtx.hashBlock)->GetBlockTime());
} else {
- entry.push_back(Pair("trusted", wtx.IsTrusted()));
+ entry.pushKV("trusted", wtx.IsTrusted());
}
uint256 hash = wtx.GetHash();
- entry.push_back(Pair("txid", hash.GetHex()));
+ entry.pushKV("txid", hash.GetHex());
UniValue conflicts(UniValue::VARR);
- BOOST_FOREACH(const uint256& conflict, wtx.GetConflicts())
+ for (const uint256& conflict : wtx.GetConflicts())
conflicts.push_back(conflict.GetHex());
- entry.push_back(Pair("walletconflicts", conflicts));
- entry.push_back(Pair("time", wtx.GetTxTime()));
- entry.push_back(Pair("timereceived", (int64_t)wtx.nTimeReceived));
+ entry.pushKV("walletconflicts", conflicts);
+ entry.pushKV("time", wtx.GetTxTime());
+ entry.pushKV("timereceived", (int64_t)wtx.nTimeReceived);
// Add opt-in RBF status
std::string rbfStatus = "no";
if (confirms <= 0) {
LOCK(mempool.cs);
- RBFTransactionState rbfState = IsRBFOptIn(wtx, mempool);
- if (rbfState == RBF_TRANSACTIONSTATE_UNKNOWN)
+ RBFTransactionState rbfState = IsRBFOptIn(*wtx.tx, mempool);
+ if (rbfState == RBFTransactionState::UNKNOWN)
rbfStatus = "unknown";
- else if (rbfState == RBF_TRANSACTIONSTATE_REPLACEABLE_BIP125)
+ else if (rbfState == RBFTransactionState::REPLACEABLE_BIP125)
rbfStatus = "yes";
}
- entry.push_back(Pair("bip125-replaceable", rbfStatus));
+ entry.pushKV("bip125-replaceable", rbfStatus);
- BOOST_FOREACH(const PAIRTYPE(string,string)& item, wtx.mapValue)
- entry.push_back(Pair(item.first, item.second));
+ for (const std::pair<const std::string, std::string>& item : wtx.mapValue)
+ entry.pushKV(item.first, item.second);
}
-string AccountFromValue(const UniValue& value)
+static std::string LabelFromValue(const UniValue& value)
{
- string strAccount = value.get_str();
- if (strAccount == "*")
- throw JSONRPCError(RPC_WALLET_INVALID_ACCOUNT_NAME, "Invalid account name");
- return strAccount;
+ std::string label = value.get_str();
+ if (label == "*")
+ throw JSONRPCError(RPC_WALLET_INVALID_LABEL_NAME, "Invalid label name");
+ return label;
}
-UniValue getnewaddress(const UniValue& params, bool fHelp)
+static UniValue getnewaddress(const JSONRPCRequest& request)
{
- if (!EnsureWalletIsAvailable(fHelp))
+ std::shared_ptr<CWallet> const wallet = GetWalletForJSONRPCRequest(request);
+ CWallet* const pwallet = wallet.get();
+
+ if (!EnsureWalletIsAvailable(pwallet, request.fHelp)) {
return NullUniValue;
+ }
- if (fHelp || params.size() > 1)
- throw runtime_error(
- "getnewaddress ( \"account\" )\n"
+ if (request.fHelp || request.params.size() > 2)
+ throw std::runtime_error(
+ "getnewaddress ( \"label\" \"address_type\" )\n"
"\nReturns a new Bitcoin address for receiving payments.\n"
- "If 'account' is specified (DEPRECATED), it is added to the address book \n"
- "so payments received with the address will be credited to 'account'.\n"
+ "If 'label' is specified, it is added to the address book \n"
+ "so payments received with the address will be associated with 'label'.\n"
"\nArguments:\n"
- "1. \"account\" (string, optional) DEPRECATED. The account name for the address to be linked to. If not provided, the default account \"\" is used. It can also be set to the empty string \"\" to represent the default account. The account does not need to exist, it will be created if there is no account by the given name.\n"
+ "1. \"label\" (string, optional) The label name for the address to be linked to. If not provided, the default label \"\" is used. It can also be set to the empty string \"\" to represent the default label. The label does not need to exist, it will be created if there is no label by the given name.\n"
+ "2. \"address_type\" (string, optional) The address type to use. Options are \"legacy\", \"p2sh-segwit\", and \"bech32\". Default is set by -addresstype.\n"
"\nResult:\n"
- "\"bitcoinaddress\" (string) The new bitcoin address\n"
+ "\"address\" (string) The new bitcoin address\n"
"\nExamples:\n"
+ HelpExampleCli("getnewaddress", "")
+ HelpExampleRpc("getnewaddress", "")
);
- LOCK2(cs_main, pwalletMain->cs_wallet);
+ if (pwallet->IsWalletFlagSet(WALLET_FLAG_DISABLE_PRIVATE_KEYS)) {
+ throw JSONRPCError(RPC_WALLET_ERROR, "Error: Private keys are disabled for this wallet");
+ }
+
+ LOCK2(cs_main, pwallet->cs_wallet);
- // Parse the account first so we don't generate a key if there's an error
- string strAccount;
- if (params.size() > 0)
- strAccount = AccountFromValue(params[0]);
+ // Parse the label first so we don't generate a key if there's an error
+ std::string label;
+ if (!request.params[0].isNull())
+ label = LabelFromValue(request.params[0]);
- if (!pwalletMain->IsLocked())
- pwalletMain->TopUpKeyPool();
+ OutputType output_type = pwallet->m_default_address_type;
+ if (!request.params[1].isNull()) {
+ if (!ParseOutputType(request.params[1].get_str(), output_type)) {
+ throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, strprintf("Unknown address type '%s'", request.params[1].get_str()));
+ }
+ }
+
+ if (!pwallet->IsLocked()) {
+ pwallet->TopUpKeyPool();
+ }
// Generate a new key that is added to wallet
CPubKey newKey;
- if (!pwalletMain->GetKeyFromPool(newKey))
+ if (!pwallet->GetKeyFromPool(newKey)) {
throw JSONRPCError(RPC_WALLET_KEYPOOL_RAN_OUT, "Error: Keypool ran out, please call keypoolrefill first");
- CKeyID keyID = newKey.GetID();
+ }
+ pwallet->LearnRelatedScripts(newKey, output_type);
+ CTxDestination dest = GetDestinationForKey(newKey, output_type);
- pwalletMain->SetAddressBook(keyID, strAccount, "receive");
+ pwallet->SetAddressBook(dest, label, "receive");
- return CBitcoinAddress(keyID).ToString();
+ return EncodeDestination(dest);
}
-
-CBitcoinAddress GetAccountAddress(string strAccount, bool bForceNew=false)
+CTxDestination GetLabelDestination(CWallet* const pwallet, const std::string& label, bool bForceNew=false)
{
- CPubKey pubKey;
- if (!pwalletMain->GetAccountPubkey(pubKey, strAccount, bForceNew)) {
+ CTxDestination dest;
+ if (!pwallet->GetLabelDestination(dest, label, bForceNew)) {
throw JSONRPCError(RPC_WALLET_KEYPOOL_RAN_OUT, "Error: Keypool ran out, please call keypoolrefill first");
}
- return CBitcoinAddress(pubKey.GetID());
+ return dest;
}
-UniValue getaccountaddress(const UniValue& params, bool fHelp)
+static UniValue getaccountaddress(const JSONRPCRequest& request)
{
- if (!EnsureWalletIsAvailable(fHelp))
+ std::shared_ptr<CWallet> const wallet = GetWalletForJSONRPCRequest(request);
+ CWallet* const pwallet = wallet.get();
+
+ if (!EnsureWalletIsAvailable(pwallet, request.fHelp)) {
return NullUniValue;
+ }
- if (fHelp || params.size() != 1)
- throw runtime_error(
+ if (!IsDeprecatedRPCEnabled("accounts")) {
+ if (request.fHelp) {
+ throw std::runtime_error("getaccountaddress (Deprecated, will be removed in V0.18. To use this command, start bitcoind with -deprecatedrpc=accounts)");
+ }
+ throw JSONRPCError(RPC_METHOD_DEPRECATED, "getaccountaddress is deprecated and will be removed in V0.18. To use this command, start bitcoind with -deprecatedrpc=accounts.");
+ }
+
+ if (request.fHelp || request.params.size() != 1)
+ throw std::runtime_error(
"getaccountaddress \"account\"\n"
- "\nDEPRECATED. Returns the current Bitcoin address for receiving payments to this account.\n"
+ "\n\nDEPRECATED. Returns the current Bitcoin address for receiving payments to this account.\n"
"\nArguments:\n"
- "1. \"account\" (string, required) The account name for the address. It can also be set to the empty string \"\" to represent the default account. The account does not need to exist, it will be created and a new address created if there is no account by the given name.\n"
+ "1. \"account\" (string, required) The account for the address. It can also be set to the empty string \"\" to represent the default account. The account does not need to exist, it will be created and a new address created if there is no account by the given name.\n"
"\nResult:\n"
- "\"bitcoinaddress\" (string) The account bitcoin address\n"
+ "\"address\" (string) The account bitcoin address\n"
"\nExamples:\n"
+ HelpExampleCli("getaccountaddress", "")
+ HelpExampleCli("getaccountaddress", "\"\"")
@@ -173,28 +237,34 @@ UniValue getaccountaddress(const UniValue& params, bool fHelp)
+ HelpExampleRpc("getaccountaddress", "\"myaccount\"")
);
- LOCK2(cs_main, pwalletMain->cs_wallet);
+ LOCK2(cs_main, pwallet->cs_wallet);
// Parse the account first so we don't generate a key if there's an error
- string strAccount = AccountFromValue(params[0]);
+ std::string account = LabelFromValue(request.params[0]);
UniValue ret(UniValue::VSTR);
- ret = GetAccountAddress(strAccount).ToString();
+ ret = EncodeDestination(GetLabelDestination(pwallet, account));
return ret;
}
-UniValue getrawchangeaddress(const UniValue& params, bool fHelp)
+static UniValue getrawchangeaddress(const JSONRPCRequest& request)
{
- if (!EnsureWalletIsAvailable(fHelp))
+ std::shared_ptr<CWallet> const wallet = GetWalletForJSONRPCRequest(request);
+ CWallet* const pwallet = wallet.get();
+
+ if (!EnsureWalletIsAvailable(pwallet, request.fHelp)) {
return NullUniValue;
+ }
- if (fHelp || params.size() > 1)
- throw runtime_error(
- "getrawchangeaddress\n"
+ if (request.fHelp || request.params.size() > 1)
+ throw std::runtime_error(
+ "getrawchangeaddress ( \"address_type\" )\n"
"\nReturns a new Bitcoin address, for receiving change.\n"
"This is for use with raw transactions, NOT normal use.\n"
+ "\nArguments:\n"
+ "1. \"address_type\" (string, optional) The address type to use. Options are \"legacy\", \"p2sh-segwit\", and \"bech32\". Default is set by -changetype.\n"
"\nResult:\n"
"\"address\" (string) The address\n"
"\nExamples:\n"
@@ -202,116 +272,173 @@ UniValue getrawchangeaddress(const UniValue& params, bool fHelp)
+ HelpExampleRpc("getrawchangeaddress", "")
);
- LOCK2(cs_main, pwalletMain->cs_wallet);
+ if (pwallet->IsWalletFlagSet(WALLET_FLAG_DISABLE_PRIVATE_KEYS)) {
+ throw JSONRPCError(RPC_WALLET_ERROR, "Error: Private keys are disabled for this wallet");
+ }
+
+ LOCK2(cs_main, pwallet->cs_wallet);
- if (!pwalletMain->IsLocked())
- pwalletMain->TopUpKeyPool();
+ if (!pwallet->IsLocked()) {
+ pwallet->TopUpKeyPool();
+ }
- CReserveKey reservekey(pwalletMain);
+ OutputType output_type = pwallet->m_default_change_type != OutputType::CHANGE_AUTO ? pwallet->m_default_change_type : pwallet->m_default_address_type;
+ if (!request.params[0].isNull()) {
+ if (!ParseOutputType(request.params[0].get_str(), output_type)) {
+ throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, strprintf("Unknown address type '%s'", request.params[0].get_str()));
+ }
+ }
+
+ CReserveKey reservekey(pwallet);
CPubKey vchPubKey;
- if (!reservekey.GetReservedKey(vchPubKey))
+ if (!reservekey.GetReservedKey(vchPubKey, true))
throw JSONRPCError(RPC_WALLET_KEYPOOL_RAN_OUT, "Error: Keypool ran out, please call keypoolrefill first");
reservekey.KeepKey();
- CKeyID keyID = vchPubKey.GetID();
+ pwallet->LearnRelatedScripts(vchPubKey, output_type);
+ CTxDestination dest = GetDestinationForKey(vchPubKey, output_type);
- return CBitcoinAddress(keyID).ToString();
+ return EncodeDestination(dest);
}
-UniValue setaccount(const UniValue& params, bool fHelp)
+static UniValue setlabel(const JSONRPCRequest& request)
{
- if (!EnsureWalletIsAvailable(fHelp))
+ std::shared_ptr<CWallet> const wallet = GetWalletForJSONRPCRequest(request);
+ CWallet* const pwallet = wallet.get();
+
+ if (!EnsureWalletIsAvailable(pwallet, request.fHelp)) {
return NullUniValue;
+ }
+
+ if (!IsDeprecatedRPCEnabled("accounts") && request.strMethod == "setaccount") {
+ if (request.fHelp) {
+ throw std::runtime_error("setaccount (Deprecated, will be removed in V0.18. To use this command, start bitcoind with -deprecatedrpc=accounts)");
+ }
+ throw JSONRPCError(RPC_METHOD_DEPRECATED, "setaccount is deprecated and will be removed in V0.18. To use this command, start bitcoind with -deprecatedrpc=accounts.");
+ }
- if (fHelp || params.size() < 1 || params.size() > 2)
- throw runtime_error(
- "setaccount \"bitcoinaddress\" \"account\"\n"
- "\nDEPRECATED. Sets the account associated with the given address.\n"
+ if (request.fHelp || request.params.size() != 2)
+ throw std::runtime_error(
+ "setlabel \"address\" \"label\"\n"
+ "\nSets the label associated with the given address.\n"
"\nArguments:\n"
- "1. \"bitcoinaddress\" (string, required) The bitcoin address to be associated with an account.\n"
- "2. \"account\" (string, required) The account to assign the address to.\n"
+ "1. \"address\" (string, required) The bitcoin address to be associated with a label.\n"
+ "2. \"label\" (string, required) The label to assign to the address.\n"
"\nExamples:\n"
- + HelpExampleCli("setaccount", "\"1D1ZrZNe3JUo7ZycKEYQQiQAWd9y54F4XZ\" \"tabby\"")
- + HelpExampleRpc("setaccount", "\"1D1ZrZNe3JUo7ZycKEYQQiQAWd9y54F4XZ\", \"tabby\"")
+ + HelpExampleCli("setlabel", "\"1D1ZrZNe3JUo7ZycKEYQQiQAWd9y54F4XX\" \"tabby\"")
+ + HelpExampleRpc("setlabel", "\"1D1ZrZNe3JUo7ZycKEYQQiQAWd9y54F4XX\", \"tabby\"")
);
- LOCK2(cs_main, pwalletMain->cs_wallet);
+ LOCK2(cs_main, pwallet->cs_wallet);
- CBitcoinAddress address(params[0].get_str());
- if (!address.IsValid())
+ CTxDestination dest = DecodeDestination(request.params[0].get_str());
+ if (!IsValidDestination(dest)) {
throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Invalid Bitcoin address");
+ }
- string strAccount;
- if (params.size() > 1)
- strAccount = AccountFromValue(params[1]);
+ std::string old_label = pwallet->mapAddressBook[dest].name;
+ std::string label = LabelFromValue(request.params[1]);
- // Only add the account if the address is yours.
- if (IsMine(*pwalletMain, address.Get()))
- {
- // Detect when changing the account of an address that is the 'unused current key' of another account:
- if (pwalletMain->mapAddressBook.count(address.Get()))
- {
- string strOldAccount = pwalletMain->mapAddressBook[address.Get()].name;
- if (address == GetAccountAddress(strOldAccount))
- GetAccountAddress(strOldAccount, true);
+ if (IsMine(*pwallet, dest)) {
+ pwallet->SetAddressBook(dest, label, "receive");
+ if (request.strMethod == "setaccount" && old_label != label && dest == GetLabelDestination(pwallet, old_label)) {
+ // for setaccount, call GetLabelDestination so a new receive address is created for the old account
+ GetLabelDestination(pwallet, old_label, true);
}
- pwalletMain->SetAddressBook(address.Get(), strAccount, "receive");
+ } else {
+ pwallet->SetAddressBook(dest, label, "send");
+ }
+
+ // Detect when there are no addresses using this label.
+ // If so, delete the account record for it. Labels, unlike addresses, can be deleted,
+ // and if we wouldn't do this, the record would stick around forever.
+ bool found_address = false;
+ for (const std::pair<const CTxDestination, CAddressBookData>& item : pwallet->mapAddressBook) {
+ if (item.second.name == label) {
+ found_address = true;
+ break;
+ }
+ }
+ if (!found_address) {
+ pwallet->DeleteLabel(old_label);
}
- else
- throw JSONRPCError(RPC_MISC_ERROR, "setaccount can only be used with own address");
return NullUniValue;
}
-UniValue getaccount(const UniValue& params, bool fHelp)
+static UniValue getaccount(const JSONRPCRequest& request)
{
- if (!EnsureWalletIsAvailable(fHelp))
+ std::shared_ptr<CWallet> const wallet = GetWalletForJSONRPCRequest(request);
+ CWallet* const pwallet = wallet.get();
+
+ if (!EnsureWalletIsAvailable(pwallet, request.fHelp)) {
return NullUniValue;
+ }
+
+ if (!IsDeprecatedRPCEnabled("accounts")) {
+ if (request.fHelp) {
+ throw std::runtime_error("getaccount (Deprecated, will be removed in V0.18. To use this command, start bitcoind with -deprecatedrpc=accounts)");
+ }
+ throw JSONRPCError(RPC_METHOD_DEPRECATED, "getaccount is deprecated and will be removed in V0.18. To use this command, start bitcoind with -deprecatedrpc=accounts.");
+ }
- if (fHelp || params.size() != 1)
- throw runtime_error(
- "getaccount \"bitcoinaddress\"\n"
+ if (request.fHelp || request.params.size() != 1)
+ throw std::runtime_error(
+ "getaccount \"address\"\n"
"\nDEPRECATED. Returns the account associated with the given address.\n"
"\nArguments:\n"
- "1. \"bitcoinaddress\" (string, required) The bitcoin address for account lookup.\n"
+ "1. \"address\" (string, required) The bitcoin address for account lookup.\n"
"\nResult:\n"
"\"accountname\" (string) the account address\n"
"\nExamples:\n"
- + HelpExampleCli("getaccount", "\"1D1ZrZNe3JUo7ZycKEYQQiQAWd9y54F4XZ\"")
- + HelpExampleRpc("getaccount", "\"1D1ZrZNe3JUo7ZycKEYQQiQAWd9y54F4XZ\"")
+ + HelpExampleCli("getaccount", "\"1D1ZrZNe3JUo7ZycKEYQQiQAWd9y54F4XX\"")
+ + HelpExampleRpc("getaccount", "\"1D1ZrZNe3JUo7ZycKEYQQiQAWd9y54F4XX\"")
);
- LOCK2(cs_main, pwalletMain->cs_wallet);
+ LOCK2(cs_main, pwallet->cs_wallet);
- CBitcoinAddress address(params[0].get_str());
- if (!address.IsValid())
+ CTxDestination dest = DecodeDestination(request.params[0].get_str());
+ if (!IsValidDestination(dest)) {
throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Invalid Bitcoin address");
+ }
- string strAccount;
- map<CTxDestination, CAddressBookData>::iterator mi = pwalletMain->mapAddressBook.find(address.Get());
- if (mi != pwalletMain->mapAddressBook.end() && !(*mi).second.name.empty())
+ std::string strAccount;
+ std::map<CTxDestination, CAddressBookData>::iterator mi = pwallet->mapAddressBook.find(dest);
+ if (mi != pwallet->mapAddressBook.end() && !(*mi).second.name.empty()) {
strAccount = (*mi).second.name;
+ }
return strAccount;
}
-UniValue getaddressesbyaccount(const UniValue& params, bool fHelp)
+static UniValue getaddressesbyaccount(const JSONRPCRequest& request)
{
- if (!EnsureWalletIsAvailable(fHelp))
+ std::shared_ptr<CWallet> const wallet = GetWalletForJSONRPCRequest(request);
+ CWallet* const pwallet = wallet.get();
+
+ if (!EnsureWalletIsAvailable(pwallet, request.fHelp)) {
return NullUniValue;
+ }
- if (fHelp || params.size() != 1)
- throw runtime_error(
+ if (!IsDeprecatedRPCEnabled("accounts")) {
+ if (request.fHelp) {
+ throw std::runtime_error("getaddressbyaccount (Deprecated, will be removed in V0.18. To use this command, start bitcoind with -deprecatedrpc=accounts)");
+ }
+ throw JSONRPCError(RPC_METHOD_DEPRECATED, "getaddressesbyaccount is deprecated and will be removed in V0.18. To use this command, start bitcoind with -deprecatedrpc=accounts.");
+ }
+
+ if (request.fHelp || request.params.size() != 1)
+ throw std::runtime_error(
"getaddressesbyaccount \"account\"\n"
"\nDEPRECATED. Returns the list of addresses for the given account.\n"
"\nArguments:\n"
- "1. \"account\" (string, required) The account name.\n"
+ "1. \"account\" (string, required) The account name.\n"
"\nResult:\n"
"[ (json array of string)\n"
- " \"bitcoinaddress\" (string) a bitcoin address associated with the given account\n"
+ " \"address\" (string) a bitcoin address associated with the given account\n"
" ,...\n"
"]\n"
"\nExamples:\n"
@@ -319,25 +446,25 @@ UniValue getaddressesbyaccount(const UniValue& params, bool fHelp)
+ HelpExampleRpc("getaddressesbyaccount", "\"tabby\"")
);
- LOCK2(cs_main, pwalletMain->cs_wallet);
+ LOCK2(cs_main, pwallet->cs_wallet);
- string strAccount = AccountFromValue(params[0]);
+ std::string strAccount = LabelFromValue(request.params[0]);
// Find all addresses that have the given account
UniValue ret(UniValue::VARR);
- BOOST_FOREACH(const PAIRTYPE(CBitcoinAddress, CAddressBookData)& item, pwalletMain->mapAddressBook)
- {
- const CBitcoinAddress& address = item.first;
- const string& strName = item.second.name;
- if (strName == strAccount)
- ret.push_back(address.ToString());
+ for (const std::pair<const CTxDestination, CAddressBookData>& item : pwallet->mapAddressBook) {
+ const CTxDestination& dest = item.first;
+ const std::string& strName = item.second.name;
+ if (strName == strAccount) {
+ ret.push_back(EncodeDestination(dest));
+ }
}
return ret;
}
-static void SendMoney(const CTxDestination &address, CAmount nValue, bool fSubtractFeeFromAmount, CWalletTx& wtxNew)
+static CTransactionRef SendMoney(CWallet * const pwallet, const CTxDestination &address, CAmount nValue, bool fSubtractFeeFromAmount, const CCoinControl& coin_control, mapValue_t mapValue, std::string fromAccount)
{
- CAmount curBalance = pwalletMain->GetBalance();
+ CAmount curBalance = pwallet->GetBalance();
// Check amount
if (nValue <= 0)
@@ -346,48 +473,67 @@ static void SendMoney(const CTxDestination &address, CAmount nValue, bool fSubtr
if (nValue > curBalance)
throw JSONRPCError(RPC_WALLET_INSUFFICIENT_FUNDS, "Insufficient funds");
+ if (pwallet->GetBroadcastTransactions() && !g_connman) {
+ throw JSONRPCError(RPC_CLIENT_P2P_DISABLED, "Error: Peer-to-peer functionality missing or disabled");
+ }
+
// Parse Bitcoin address
CScript scriptPubKey = GetScriptForDestination(address);
// Create and send the transaction
- CReserveKey reservekey(pwalletMain);
+ CReserveKey reservekey(pwallet);
CAmount nFeeRequired;
std::string strError;
- vector<CRecipient> vecSend;
+ std::vector<CRecipient> vecSend;
int nChangePosRet = -1;
CRecipient recipient = {scriptPubKey, nValue, fSubtractFeeFromAmount};
vecSend.push_back(recipient);
- if (!pwalletMain->CreateTransaction(vecSend, wtxNew, reservekey, nFeeRequired, nChangePosRet, strError)) {
- if (!fSubtractFeeFromAmount && nValue + nFeeRequired > pwalletMain->GetBalance())
- strError = strprintf("Error: This transaction requires a transaction fee of at least %s because of its amount, complexity, or use of recently received funds!", FormatMoney(nFeeRequired));
+ CTransactionRef tx;
+ if (!pwallet->CreateTransaction(vecSend, tx, reservekey, nFeeRequired, nChangePosRet, strError, coin_control)) {
+ if (!fSubtractFeeFromAmount && nValue + nFeeRequired > curBalance)
+ strError = strprintf("Error: This transaction requires a transaction fee of at least %s", FormatMoney(nFeeRequired));
+ throw JSONRPCError(RPC_WALLET_ERROR, strError);
+ }
+ CValidationState state;
+ if (!pwallet->CommitTransaction(tx, std::move(mapValue), {} /* orderForm */, std::move(fromAccount), reservekey, g_connman.get(), state)) {
+ strError = strprintf("Error: The transaction was rejected! Reason given: %s", FormatStateMessage(state));
throw JSONRPCError(RPC_WALLET_ERROR, strError);
}
- if (!pwalletMain->CommitTransaction(wtxNew, reservekey))
- throw JSONRPCError(RPC_WALLET_ERROR, "Error: The transaction was rejected! This might happen if some of the coins in your wallet were already spent, such as if you used a copy of the wallet and coins were spent in the copy but not marked as spent here.");
+ return tx;
}
-UniValue sendtoaddress(const UniValue& params, bool fHelp)
+static UniValue sendtoaddress(const JSONRPCRequest& request)
{
- if (!EnsureWalletIsAvailable(fHelp))
+ std::shared_ptr<CWallet> const wallet = GetWalletForJSONRPCRequest(request);
+ CWallet* const pwallet = wallet.get();
+
+ if (!EnsureWalletIsAvailable(pwallet, request.fHelp)) {
return NullUniValue;
+ }
- if (fHelp || params.size() < 2 || params.size() > 5)
- throw runtime_error(
- "sendtoaddress \"bitcoinaddress\" amount ( \"comment\" \"comment-to\" subtractfeefromamount )\n"
+ if (request.fHelp || request.params.size() < 2 || request.params.size() > 8)
+ throw std::runtime_error(
+ "sendtoaddress \"address\" amount ( \"comment\" \"comment_to\" subtractfeefromamount replaceable conf_target \"estimate_mode\")\n"
"\nSend an amount to a given address.\n"
- + HelpRequiringPassphrase() +
+ + HelpRequiringPassphrase(pwallet) +
"\nArguments:\n"
- "1. \"bitcoinaddress\" (string, required) The bitcoin address to send to.\n"
- "2. \"amount\" (numeric or string, required) The amount in " + CURRENCY_UNIT + " to send. eg 0.1\n"
- "3. \"comment\" (string, optional) A comment used to store what the transaction is for. \n"
+ "1. \"address\" (string, required) The bitcoin address to send to.\n"
+ "2. \"amount\" (numeric or string, required) The amount in " + CURRENCY_UNIT + " to send. eg 0.1\n"
+ "3. \"comment\" (string, optional) A comment used to store what the transaction is for. \n"
" This is not part of the transaction, just kept in your wallet.\n"
- "4. \"comment-to\" (string, optional) A comment to store the name of the person or organization \n"
+ "4. \"comment_to\" (string, optional) A comment to store the name of the person or organization \n"
" to which you're sending the transaction. This is not part of the \n"
" transaction, just kept in your wallet.\n"
"5. subtractfeefromamount (boolean, optional, default=false) The fee will be deducted from the amount being sent.\n"
" The recipient will receive less bitcoins than you enter in the amount field.\n"
+ "6. replaceable (boolean, optional) Allow this transaction to be replaced by a transaction with higher fees via BIP 125\n"
+ "7. conf_target (numeric, optional) Confirmation target (in blocks)\n"
+ "8. \"estimate_mode\" (string, optional, default=UNSET) The fee estimate mode, must be one of:\n"
+ " \"UNSET\"\n"
+ " \"ECONOMICAL\"\n"
+ " \"CONSERVATIVE\"\n"
"\nResult:\n"
- "\"transactionid\" (string) The transaction id.\n"
+ "\"txid\" (string) The transaction id.\n"
"\nExamples:\n"
+ HelpExampleCli("sendtoaddress", "\"1M72Sfpbz1BPpXFHz9m3CdqATR44Jvaydd\" 0.1")
+ HelpExampleCli("sendtoaddress", "\"1M72Sfpbz1BPpXFHz9m3CdqATR44Jvaydd\" 0.1 \"donation\" \"seans outpost\"")
@@ -395,42 +541,67 @@ UniValue sendtoaddress(const UniValue& params, bool fHelp)
+ HelpExampleRpc("sendtoaddress", "\"1M72Sfpbz1BPpXFHz9m3CdqATR44Jvaydd\", 0.1, \"donation\", \"seans outpost\"")
);
- LOCK2(cs_main, pwalletMain->cs_wallet);
+ // Make sure the results are valid at least up to the most recent block
+ // the user could have gotten from another RPC command prior to now
+ pwallet->BlockUntilSyncedToCurrentChain();
- CBitcoinAddress address(params[0].get_str());
- if (!address.IsValid())
- throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Invalid Bitcoin address");
+ LOCK2(cs_main, pwallet->cs_wallet);
+
+ CTxDestination dest = DecodeDestination(request.params[0].get_str());
+ if (!IsValidDestination(dest)) {
+ throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Invalid address");
+ }
// Amount
- CAmount nAmount = AmountFromValue(params[1]);
+ CAmount nAmount = AmountFromValue(request.params[1]);
if (nAmount <= 0)
throw JSONRPCError(RPC_TYPE_ERROR, "Invalid amount for send");
// Wallet comments
- CWalletTx wtx;
- if (params.size() > 2 && !params[2].isNull() && !params[2].get_str().empty())
- wtx.mapValue["comment"] = params[2].get_str();
- if (params.size() > 3 && !params[3].isNull() && !params[3].get_str().empty())
- wtx.mapValue["to"] = params[3].get_str();
+ mapValue_t mapValue;
+ if (!request.params[2].isNull() && !request.params[2].get_str().empty())
+ mapValue["comment"] = request.params[2].get_str();
+ if (!request.params[3].isNull() && !request.params[3].get_str().empty())
+ mapValue["to"] = request.params[3].get_str();
bool fSubtractFeeFromAmount = false;
- if (params.size() > 4)
- fSubtractFeeFromAmount = params[4].get_bool();
+ if (!request.params[4].isNull()) {
+ fSubtractFeeFromAmount = request.params[4].get_bool();
+ }
+
+ CCoinControl coin_control;
+ if (!request.params[5].isNull()) {
+ coin_control.m_signal_bip125_rbf = request.params[5].get_bool();
+ }
- EnsureWalletIsUnlocked();
+ if (!request.params[6].isNull()) {
+ coin_control.m_confirm_target = ParseConfirmTarget(request.params[6]);
+ }
- SendMoney(address.Get(), nAmount, fSubtractFeeFromAmount, wtx);
+ if (!request.params[7].isNull()) {
+ if (!FeeModeFromString(request.params[7].get_str(), coin_control.m_fee_mode)) {
+ throw JSONRPCError(RPC_INVALID_PARAMETER, "Invalid estimate_mode parameter");
+ }
+ }
- return wtx.GetHash().GetHex();
+
+ EnsureWalletIsUnlocked(pwallet);
+
+ CTransactionRef tx = SendMoney(pwallet, dest, nAmount, fSubtractFeeFromAmount, coin_control, std::move(mapValue), {} /* fromAccount */);
+ return tx->GetHash().GetHex();
}
-UniValue listaddressgroupings(const UniValue& params, bool fHelp)
+static UniValue listaddressgroupings(const JSONRPCRequest& request)
{
- if (!EnsureWalletIsAvailable(fHelp))
+ std::shared_ptr<CWallet> const wallet = GetWalletForJSONRPCRequest(request);
+ CWallet* const pwallet = wallet.get();
+
+ if (!EnsureWalletIsAvailable(pwallet, request.fHelp)) {
return NullUniValue;
+ }
- if (fHelp)
- throw runtime_error(
+ if (request.fHelp || request.params.size() != 0)
+ throw std::runtime_error(
"listaddressgroupings\n"
"\nLists groups of addresses which have had their common ownership\n"
"made public by common use as inputs or as the resulting change\n"
@@ -439,9 +610,9 @@ UniValue listaddressgroupings(const UniValue& params, bool fHelp)
"[\n"
" [\n"
" [\n"
- " \"bitcoinaddress\", (string) The bitcoin address\n"
+ " \"address\", (string) The bitcoin address\n"
" amount, (numeric) The amount in " + CURRENCY_UNIT + "\n"
- " \"account\" (string, optional) The account (DEPRECATED)\n"
+ " \"label\" (string, optional) The label\n"
" ]\n"
" ,...\n"
" ]\n"
@@ -452,21 +623,25 @@ UniValue listaddressgroupings(const UniValue& params, bool fHelp)
+ HelpExampleRpc("listaddressgroupings", "")
);
- LOCK2(cs_main, pwalletMain->cs_wallet);
+ // Make sure the results are valid at least up to the most recent block
+ // the user could have gotten from another RPC command prior to now
+ pwallet->BlockUntilSyncedToCurrentChain();
+
+ LOCK2(cs_main, pwallet->cs_wallet);
UniValue jsonGroupings(UniValue::VARR);
- map<CTxDestination, CAmount> balances = pwalletMain->GetAddressBalances();
- BOOST_FOREACH(set<CTxDestination> grouping, pwalletMain->GetAddressGroupings())
- {
+ std::map<CTxDestination, CAmount> balances = pwallet->GetAddressBalances();
+ for (const std::set<CTxDestination>& grouping : pwallet->GetAddressGroupings()) {
UniValue jsonGrouping(UniValue::VARR);
- BOOST_FOREACH(CTxDestination address, grouping)
+ for (const CTxDestination& address : grouping)
{
UniValue addressInfo(UniValue::VARR);
- addressInfo.push_back(CBitcoinAddress(address).ToString());
+ addressInfo.push_back(EncodeDestination(address));
addressInfo.push_back(ValueFromAmount(balances[address]));
{
- if (pwalletMain->mapAddressBook.find(CBitcoinAddress(address).Get()) != pwalletMain->mapAddressBook.end())
- addressInfo.push_back(pwalletMain->mapAddressBook.find(CBitcoinAddress(address).Get())->second.name);
+ if (pwallet->mapAddressBook.find(address) != pwallet->mapAddressBook.end()) {
+ addressInfo.push_back(pwallet->mapAddressBook.find(address)->second.name);
+ }
}
jsonGrouping.push_back(addressInfo);
}
@@ -475,18 +650,22 @@ UniValue listaddressgroupings(const UniValue& params, bool fHelp)
return jsonGroupings;
}
-UniValue signmessage(const UniValue& params, bool fHelp)
+static UniValue signmessage(const JSONRPCRequest& request)
{
- if (!EnsureWalletIsAvailable(fHelp))
+ std::shared_ptr<CWallet> const wallet = GetWalletForJSONRPCRequest(request);
+ CWallet* const pwallet = wallet.get();
+
+ if (!EnsureWalletIsAvailable(pwallet, request.fHelp)) {
return NullUniValue;
+ }
- if (fHelp || params.size() != 2)
- throw runtime_error(
- "signmessage \"bitcoinaddress\" \"message\"\n"
+ if (request.fHelp || request.params.size() != 2)
+ throw std::runtime_error(
+ "signmessage \"address\" \"message\"\n"
"\nSign a message with the private key of an address"
- + HelpRequiringPassphrase() + "\n"
+ + HelpRequiringPassphrase(pwallet) + "\n"
"\nArguments:\n"
- "1. \"bitcoinaddress\" (string, required) The bitcoin address to use for the private key.\n"
+ "1. \"address\" (string, required) The bitcoin address to use for the private key.\n"
"2. \"message\" (string, required) The message to create a signature of.\n"
"\nResult:\n"
"\"signature\" (string) The signature of the message encoded in base 64\n"
@@ -494,92 +673,104 @@ UniValue signmessage(const UniValue& params, bool fHelp)
"\nUnlock the wallet for 30 seconds\n"
+ HelpExampleCli("walletpassphrase", "\"mypassphrase\" 30") +
"\nCreate the signature\n"
- + HelpExampleCli("signmessage", "\"1D1ZrZNe3JUo7ZycKEYQQiQAWd9y54F4XZ\" \"my message\"") +
+ + HelpExampleCli("signmessage", "\"1D1ZrZNe3JUo7ZycKEYQQiQAWd9y54F4XX\" \"my message\"") +
"\nVerify the signature\n"
- + HelpExampleCli("verifymessage", "\"1D1ZrZNe3JUo7ZycKEYQQiQAWd9y54F4XZ\" \"signature\" \"my message\"") +
+ + HelpExampleCli("verifymessage", "\"1D1ZrZNe3JUo7ZycKEYQQiQAWd9y54F4XX\" \"signature\" \"my message\"") +
"\nAs json rpc\n"
- + HelpExampleRpc("signmessage", "\"1D1ZrZNe3JUo7ZycKEYQQiQAWd9y54F4XZ\", \"my message\"")
+ + HelpExampleRpc("signmessage", "\"1D1ZrZNe3JUo7ZycKEYQQiQAWd9y54F4XX\", \"my message\"")
);
- LOCK2(cs_main, pwalletMain->cs_wallet);
+ LOCK2(cs_main, pwallet->cs_wallet);
- EnsureWalletIsUnlocked();
+ EnsureWalletIsUnlocked(pwallet);
- string strAddress = params[0].get_str();
- string strMessage = params[1].get_str();
+ std::string strAddress = request.params[0].get_str();
+ std::string strMessage = request.params[1].get_str();
- CBitcoinAddress addr(strAddress);
- if (!addr.IsValid())
+ CTxDestination dest = DecodeDestination(strAddress);
+ if (!IsValidDestination(dest)) {
throw JSONRPCError(RPC_TYPE_ERROR, "Invalid address");
+ }
- CKeyID keyID;
- if (!addr.GetKeyID(keyID))
+ const CKeyID *keyID = boost::get<CKeyID>(&dest);
+ if (!keyID) {
throw JSONRPCError(RPC_TYPE_ERROR, "Address does not refer to key");
+ }
CKey key;
- if (!pwalletMain->GetKey(keyID, key))
+ if (!pwallet->GetKey(*keyID, key)) {
throw JSONRPCError(RPC_WALLET_ERROR, "Private key not available");
+ }
CHashWriter ss(SER_GETHASH, 0);
ss << strMessageMagic;
ss << strMessage;
- vector<unsigned char> vchSig;
+ std::vector<unsigned char> vchSig;
if (!key.SignCompact(ss.GetHash(), vchSig))
throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Sign failed");
- return EncodeBase64(&vchSig[0], vchSig.size());
+ return EncodeBase64(vchSig.data(), vchSig.size());
}
-UniValue getreceivedbyaddress(const UniValue& params, bool fHelp)
+static UniValue getreceivedbyaddress(const JSONRPCRequest& request)
{
- if (!EnsureWalletIsAvailable(fHelp))
+ std::shared_ptr<CWallet> const wallet = GetWalletForJSONRPCRequest(request);
+ CWallet* const pwallet = wallet.get();
+
+ if (!EnsureWalletIsAvailable(pwallet, request.fHelp)) {
return NullUniValue;
+ }
- if (fHelp || params.size() < 1 || params.size() > 2)
- throw runtime_error(
- "getreceivedbyaddress \"bitcoinaddress\" ( minconf )\n"
- "\nReturns the total amount received by the given bitcoinaddress in transactions with at least minconf confirmations.\n"
+ if (request.fHelp || request.params.size() < 1 || request.params.size() > 2)
+ throw std::runtime_error(
+ "getreceivedbyaddress \"address\" ( minconf )\n"
+ "\nReturns the total amount received by the given address in transactions with at least minconf confirmations.\n"
"\nArguments:\n"
- "1. \"bitcoinaddress\" (string, required) The bitcoin address for transactions.\n"
+ "1. \"address\" (string, required) The bitcoin address for transactions.\n"
"2. minconf (numeric, optional, default=1) Only include transactions confirmed at least this many times.\n"
"\nResult:\n"
"amount (numeric) The total amount in " + CURRENCY_UNIT + " received at this address.\n"
"\nExamples:\n"
"\nThe amount from transactions with at least 1 confirmation\n"
- + HelpExampleCli("getreceivedbyaddress", "\"1D1ZrZNe3JUo7ZycKEYQQiQAWd9y54F4XZ\"") +
+ + HelpExampleCli("getreceivedbyaddress", "\"1D1ZrZNe3JUo7ZycKEYQQiQAWd9y54F4XX\"") +
"\nThe amount including unconfirmed transactions, zero confirmations\n"
- + HelpExampleCli("getreceivedbyaddress", "\"1D1ZrZNe3JUo7ZycKEYQQiQAWd9y54F4XZ\" 0") +
- "\nThe amount with at least 6 confirmation, very safe\n"
- + HelpExampleCli("getreceivedbyaddress", "\"1D1ZrZNe3JUo7ZycKEYQQiQAWd9y54F4XZ\" 6") +
+ + HelpExampleCli("getreceivedbyaddress", "\"1D1ZrZNe3JUo7ZycKEYQQiQAWd9y54F4XX\" 0") +
+ "\nThe amount with at least 6 confirmations\n"
+ + HelpExampleCli("getreceivedbyaddress", "\"1D1ZrZNe3JUo7ZycKEYQQiQAWd9y54F4XX\" 6") +
"\nAs a json rpc call\n"
- + HelpExampleRpc("getreceivedbyaddress", "\"1D1ZrZNe3JUo7ZycKEYQQiQAWd9y54F4XZ\", 6")
+ + HelpExampleRpc("getreceivedbyaddress", "\"1D1ZrZNe3JUo7ZycKEYQQiQAWd9y54F4XX\", 6")
);
- LOCK2(cs_main, pwalletMain->cs_wallet);
+ // Make sure the results are valid at least up to the most recent block
+ // the user could have gotten from another RPC command prior to now
+ pwallet->BlockUntilSyncedToCurrentChain();
+
+ LOCK2(cs_main, pwallet->cs_wallet);
// Bitcoin address
- CBitcoinAddress address = CBitcoinAddress(params[0].get_str());
- if (!address.IsValid())
+ CTxDestination dest = DecodeDestination(request.params[0].get_str());
+ if (!IsValidDestination(dest)) {
throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Invalid Bitcoin address");
- CScript scriptPubKey = GetScriptForDestination(address.Get());
- if (!IsMine(*pwalletMain, scriptPubKey))
- return ValueFromAmount(0);
+ }
+ CScript scriptPubKey = GetScriptForDestination(dest);
+ if (!IsMine(*pwallet, scriptPubKey)) {
+ throw JSONRPCError(RPC_WALLET_ERROR, "Address not found in wallet");
+ }
// Minimum confirmations
int nMinDepth = 1;
- if (params.size() > 1)
- nMinDepth = params[1].get_int();
+ if (!request.params[1].isNull())
+ nMinDepth = request.params[1].get_int();
// Tally
CAmount nAmount = 0;
- for (map<uint256, CWalletTx>::iterator it = pwalletMain->mapWallet.begin(); it != pwalletMain->mapWallet.end(); ++it)
- {
- const CWalletTx& wtx = (*it).second;
- if (wtx.IsCoinBase() || !CheckFinalTx(wtx))
+ for (const std::pair<const uint256, CWalletTx>& pairWtx : pwallet->mapWallet) {
+ const CWalletTx& wtx = pairWtx.second;
+ if (wtx.IsCoinBase() || !CheckFinalTx(*wtx.tx))
continue;
- BOOST_FOREACH(const CTxOut& txout, wtx.vout)
+ for (const CTxOut& txout : wtx.tx->vout)
if (txout.scriptPubKey == scriptPubKey)
if (wtx.GetDepthInMainChain() >= nMinDepth)
nAmount += txout.nValue;
@@ -589,56 +780,71 @@ UniValue getreceivedbyaddress(const UniValue& params, bool fHelp)
}
-UniValue getreceivedbyaccount(const UniValue& params, bool fHelp)
+static UniValue getreceivedbylabel(const JSONRPCRequest& request)
{
- if (!EnsureWalletIsAvailable(fHelp))
+ std::shared_ptr<CWallet> const wallet = GetWalletForJSONRPCRequest(request);
+ CWallet* const pwallet = wallet.get();
+
+ if (!EnsureWalletIsAvailable(pwallet, request.fHelp)) {
return NullUniValue;
+ }
+
+ if (!IsDeprecatedRPCEnabled("accounts") && request.strMethod == "getreceivedbyaccount") {
+ if (request.fHelp) {
+ throw std::runtime_error("getreceivedbyaccount (Deprecated, will be removed in V0.18. To use this command, start bitcoind with -deprecatedrpc=accounts)");
+ }
+ throw JSONRPCError(RPC_METHOD_DEPRECATED, "getreceivedbyaccount is deprecated and will be removed in V0.18. To use this command, start bitcoind with -deprecatedrpc=accounts.");
+ }
- if (fHelp || params.size() < 1 || params.size() > 2)
- throw runtime_error(
- "getreceivedbyaccount \"account\" ( minconf )\n"
- "\nDEPRECATED. Returns the total amount received by addresses with <account> in transactions with at least [minconf] confirmations.\n"
+ if (request.fHelp || request.params.size() < 1 || request.params.size() > 2)
+ throw std::runtime_error(
+ "getreceivedbylabel \"label\" ( minconf )\n"
+ "\nReturns the total amount received by addresses with <label> in transactions with at least [minconf] confirmations.\n"
"\nArguments:\n"
- "1. \"account\" (string, required) The selected account, may be the default account using \"\".\n"
+ "1. \"label\" (string, required) The selected label, may be the default label using \"\".\n"
"2. minconf (numeric, optional, default=1) Only include transactions confirmed at least this many times.\n"
"\nResult:\n"
- "amount (numeric) The total amount in " + CURRENCY_UNIT + " received for this account.\n"
+ "amount (numeric) The total amount in " + CURRENCY_UNIT + " received for this label.\n"
"\nExamples:\n"
- "\nAmount received by the default account with at least 1 confirmation\n"
- + HelpExampleCli("getreceivedbyaccount", "\"\"") +
- "\nAmount received at the tabby account including unconfirmed amounts with zero confirmations\n"
- + HelpExampleCli("getreceivedbyaccount", "\"tabby\" 0") +
- "\nThe amount with at least 6 confirmation, very safe\n"
- + HelpExampleCli("getreceivedbyaccount", "\"tabby\" 6") +
+ "\nAmount received by the default label with at least 1 confirmation\n"
+ + HelpExampleCli("getreceivedbylabel", "\"\"") +
+ "\nAmount received at the tabby label including unconfirmed amounts with zero confirmations\n"
+ + HelpExampleCli("getreceivedbylabel", "\"tabby\" 0") +
+ "\nThe amount with at least 6 confirmations\n"
+ + HelpExampleCli("getreceivedbylabel", "\"tabby\" 6") +
"\nAs a json rpc call\n"
- + HelpExampleRpc("getreceivedbyaccount", "\"tabby\", 6")
+ + HelpExampleRpc("getreceivedbylabel", "\"tabby\", 6")
);
- LOCK2(cs_main, pwalletMain->cs_wallet);
+ // Make sure the results are valid at least up to the most recent block
+ // the user could have gotten from another RPC command prior to now
+ pwallet->BlockUntilSyncedToCurrentChain();
+
+ LOCK2(cs_main, pwallet->cs_wallet);
// Minimum confirmations
int nMinDepth = 1;
- if (params.size() > 1)
- nMinDepth = params[1].get_int();
+ if (!request.params[1].isNull())
+ nMinDepth = request.params[1].get_int();
- // Get the set of pub keys assigned to account
- string strAccount = AccountFromValue(params[0]);
- set<CTxDestination> setAddress = pwalletMain->GetAccountAddresses(strAccount);
+ // Get the set of pub keys assigned to label
+ std::string label = LabelFromValue(request.params[0]);
+ std::set<CTxDestination> setAddress = pwallet->GetLabelAddresses(label);
// Tally
CAmount nAmount = 0;
- for (map<uint256, CWalletTx>::iterator it = pwalletMain->mapWallet.begin(); it != pwalletMain->mapWallet.end(); ++it)
- {
- const CWalletTx& wtx = (*it).second;
- if (wtx.IsCoinBase() || !CheckFinalTx(wtx))
+ for (const std::pair<const uint256, CWalletTx>& pairWtx : pwallet->mapWallet) {
+ const CWalletTx& wtx = pairWtx.second;
+ if (wtx.IsCoinBase() || !CheckFinalTx(*wtx.tx))
continue;
- BOOST_FOREACH(const CTxOut& txout, wtx.vout)
+ for (const CTxOut& txout : wtx.tx->vout)
{
CTxDestination address;
- if (ExtractDestination(txout.scriptPubKey, address) && IsMine(*pwalletMain, address) && setAddress.count(address))
+ if (ExtractDestination(txout.scriptPubKey, address) && IsMine(*pwallet, address) && setAddress.count(address)) {
if (wtx.GetDepthInMainChain() >= nMinDepth)
nAmount += txout.nValue;
+ }
}
}
@@ -646,111 +852,147 @@ UniValue getreceivedbyaccount(const UniValue& params, bool fHelp)
}
-UniValue getbalance(const UniValue& params, bool fHelp)
+static UniValue getbalance(const JSONRPCRequest& request)
{
- if (!EnsureWalletIsAvailable(fHelp))
+ std::shared_ptr<CWallet> const wallet = GetWalletForJSONRPCRequest(request);
+ CWallet* const pwallet = wallet.get();
+
+ if (!EnsureWalletIsAvailable(pwallet, request.fHelp)) {
return NullUniValue;
+ }
- if (fHelp || params.size() > 3)
- throw runtime_error(
- "getbalance ( \"account\" minconf includeWatchonly )\n"
+ if (request.fHelp || (request.params.size() > 3 ))
+ throw std::runtime_error(
+ (IsDeprecatedRPCEnabled("accounts") ? std::string(
+ "getbalance ( \"account\" minconf include_watchonly )\n"
"\nIf account is not specified, returns the server's total available balance.\n"
+ "The available balance is what the wallet considers currently spendable, and is\n"
+ "thus affected by options which limit spendability such as -spendzeroconfchange.\n"
"If account is specified (DEPRECATED), returns the balance in the account.\n"
"Note that the account \"\" is not the same as leaving the parameter out.\n"
"The server total may be different to the balance in the default \"\" account.\n"
"\nArguments:\n"
- "1. \"account\" (string, optional) DEPRECATED. The selected account, or \"*\" for entire wallet. It may be the default account using \"\".\n"
- "2. minconf (numeric, optional, default=1) Only include transactions confirmed at least this many times.\n"
- "3. includeWatchonly (bool, optional, default=false) Also include balance in watchonly addresses (see 'importaddress')\n"
+ "1. \"account\" (string, optional) DEPRECATED. This argument will be removed in V0.18. \n"
+ " To use this deprecated argument, start bitcoind with -deprecatedrpc=accounts. The account string may be given as a\n"
+ " specific account name to find the balance associated with wallet keys in\n"
+ " a named account, or as the empty string (\"\") to find the balance\n"
+ " associated with wallet keys not in any named account, or as \"*\" to find\n"
+ " the balance associated with all wallet keys regardless of account.\n"
+ " When this option is specified, it calculates the balance in a different\n"
+ " way than when it is not specified, and which can count spends twice when\n"
+ " there are conflicting pending transactions (such as those created by\n"
+ " the bumpfee command), temporarily resulting in low or even negative\n"
+ " balances. In general, account balance calculation is not considered\n"
+ " reliable and has resulted in confusing outcomes, so it is recommended to\n"
+ " avoid passing this argument.\n"
+ "2. minconf (numeric, optional) Only include transactions confirmed at least this many times. \n"
+ " The default is 1 if an account is provided or 0 if no account is provided\n")
+ : std::string(
+ "getbalance ( \"(dummy)\" minconf include_watchonly )\n"
+ "\nReturns the total available balance.\n"
+ "The available balance is what the wallet considers currently spendable, and is\n"
+ "thus affected by options which limit spendability such as -spendzeroconfchange.\n"
+ "\nArguments:\n"
+ "1. (dummy) (string, optional) Remains for backward compatibility. Must be excluded or set to \"*\".\n"
+ "2. minconf (numeric, optional, default=0) Only include transactions confirmed at least this many times.\n")) +
+ "3. include_watchonly (bool, optional, default=false) Also include balance in watch-only addresses (see 'importaddress')\n"
"\nResult:\n"
"amount (numeric) The total amount in " + CURRENCY_UNIT + " received for this account.\n"
"\nExamples:\n"
- "\nThe total amount in the wallet\n"
+ "\nThe total amount in the wallet with 1 or more confirmations\n"
+ HelpExampleCli("getbalance", "") +
- "\nThe total amount in the wallet at least 5 blocks confirmed\n"
+ "\nThe total amount in the wallet at least 6 blocks confirmed\n"
+ HelpExampleCli("getbalance", "\"*\" 6") +
"\nAs a json rpc call\n"
+ HelpExampleRpc("getbalance", "\"*\", 6")
);
- LOCK2(cs_main, pwalletMain->cs_wallet);
+ // Make sure the results are valid at least up to the most recent block
+ // the user could have gotten from another RPC command prior to now
+ pwallet->BlockUntilSyncedToCurrentChain();
- if (params.size() == 0)
- return ValueFromAmount(pwalletMain->GetBalance());
+ LOCK2(cs_main, pwallet->cs_wallet);
- int nMinDepth = 1;
- if (params.size() > 1)
- nMinDepth = params[1].get_int();
- isminefilter filter = ISMINE_SPENDABLE;
- if(params.size() > 2)
- if(params[2].get_bool())
- filter = filter | ISMINE_WATCH_ONLY;
+ const UniValue& account_value = request.params[0];
- if (params[0].get_str() == "*") {
- // Calculate total balance a different way from GetBalance()
- // (GetBalance() sums up all unspent TxOuts)
- // getbalance and "getbalance * 1 true" should return the same number
- CAmount nBalance = 0;
- for (map<uint256, CWalletTx>::iterator it = pwalletMain->mapWallet.begin(); it != pwalletMain->mapWallet.end(); ++it)
- {
- const CWalletTx& wtx = (*it).second;
- if (!CheckFinalTx(wtx) || wtx.GetBlocksToMaturity() > 0 || wtx.GetDepthInMainChain() < 0)
- continue;
+ int min_depth = 0;
+ if (IsDeprecatedRPCEnabled("accounts") && !account_value.isNull()) {
+ // Default min_depth to 1 when an account is provided.
+ min_depth = 1;
+ }
+ if (!request.params[1].isNull()) {
+ min_depth = request.params[1].get_int();
+ }
- CAmount allFee;
- string strSentAccount;
- list<COutputEntry> listReceived;
- list<COutputEntry> listSent;
- wtx.GetAmounts(listReceived, listSent, allFee, strSentAccount, filter);
- if (wtx.GetDepthInMainChain() >= nMinDepth)
- {
- BOOST_FOREACH(const COutputEntry& r, listReceived)
- nBalance += r.amount;
- }
- BOOST_FOREACH(const COutputEntry& s, listSent)
- nBalance -= s.amount;
- nBalance -= allFee;
- }
- return ValueFromAmount(nBalance);
+ isminefilter filter = ISMINE_SPENDABLE;
+ if (!request.params[2].isNull() && request.params[2].get_bool()) {
+ filter = filter | ISMINE_WATCH_ONLY;
}
- string strAccount = AccountFromValue(params[0]);
+ if (!account_value.isNull()) {
- CAmount nBalance = pwalletMain->GetAccountBalance(strAccount, nMinDepth, filter);
+ const std::string& account_param = account_value.get_str();
+ const std::string* account = account_param != "*" ? &account_param : nullptr;
+
+ if (!IsDeprecatedRPCEnabled("accounts") && account_param != "*") {
+ throw JSONRPCError(RPC_METHOD_DEPRECATED, "dummy first argument must be excluded or set to \"*\".");
+ } else if (IsDeprecatedRPCEnabled("accounts")) {
+ return ValueFromAmount(pwallet->GetLegacyBalance(filter, min_depth, account));
+ }
+ }
- return ValueFromAmount(nBalance);
+ return ValueFromAmount(pwallet->GetBalance(filter, min_depth));
}
-UniValue getunconfirmedbalance(const UniValue &params, bool fHelp)
+static UniValue getunconfirmedbalance(const JSONRPCRequest &request)
{
- if (!EnsureWalletIsAvailable(fHelp))
+ std::shared_ptr<CWallet> const wallet = GetWalletForJSONRPCRequest(request);
+ CWallet* const pwallet = wallet.get();
+
+ if (!EnsureWalletIsAvailable(pwallet, request.fHelp)) {
return NullUniValue;
+ }
- if (fHelp || params.size() > 0)
- throw runtime_error(
+ if (request.fHelp || request.params.size() > 0)
+ throw std::runtime_error(
"getunconfirmedbalance\n"
"Returns the server's total unconfirmed balance\n");
- LOCK2(cs_main, pwalletMain->cs_wallet);
+ // Make sure the results are valid at least up to the most recent block
+ // the user could have gotten from another RPC command prior to now
+ pwallet->BlockUntilSyncedToCurrentChain();
+
+ LOCK2(cs_main, pwallet->cs_wallet);
- return ValueFromAmount(pwalletMain->GetUnconfirmedBalance());
+ return ValueFromAmount(pwallet->GetUnconfirmedBalance());
}
-UniValue movecmd(const UniValue& params, bool fHelp)
+static UniValue movecmd(const JSONRPCRequest& request)
{
- if (!EnsureWalletIsAvailable(fHelp))
+ std::shared_ptr<CWallet> const wallet = GetWalletForJSONRPCRequest(request);
+ CWallet* const pwallet = wallet.get();
+
+ if (!EnsureWalletIsAvailable(pwallet, request.fHelp)) {
return NullUniValue;
+ }
- if (fHelp || params.size() < 3 || params.size() > 5)
- throw runtime_error(
+ if (!IsDeprecatedRPCEnabled("accounts")) {
+ if (request.fHelp) {
+ throw std::runtime_error("move (Deprecated, will be removed in V0.18. To use this command, start bitcoind with -deprecatedrpc=accounts)");
+ }
+ throw JSONRPCError(RPC_METHOD_DEPRECATED, "move is deprecated and will be removed in V0.18. To use this command, start bitcoind with -deprecatedrpc=accounts.");
+ }
+
+ if (request.fHelp || request.params.size() < 3 || request.params.size() > 5)
+ throw std::runtime_error(
"move \"fromaccount\" \"toaccount\" amount ( minconf \"comment\" )\n"
"\nDEPRECATED. Move a specified amount from one account in your wallet to another.\n"
"\nArguments:\n"
"1. \"fromaccount\" (string, required) The name of the account to move funds from. May be the default account using \"\".\n"
"2. \"toaccount\" (string, required) The name of the account to move funds to. May be the default account using \"\".\n"
"3. amount (numeric) Quantity of " + CURRENCY_UNIT + " to move between accounts.\n"
- "4. minconf (numeric, optional, default=1) Only use funds with at least this many confirmations.\n"
+ "4. (dummy) (numeric, optional) Ignored. Remains for backward compatibility.\n"
"5. \"comment\" (string, optional) An optional comment, stored in the wallet only.\n"
"\nResult:\n"
"true|false (boolean) true if successful.\n"
@@ -763,49 +1005,65 @@ UniValue movecmd(const UniValue& params, bool fHelp)
+ HelpExampleRpc("move", "\"timotei\", \"akiko\", 0.01, 6, \"happy birthday!\"")
);
- LOCK2(cs_main, pwalletMain->cs_wallet);
+ LOCK2(cs_main, pwallet->cs_wallet);
- string strFrom = AccountFromValue(params[0]);
- string strTo = AccountFromValue(params[1]);
- CAmount nAmount = AmountFromValue(params[2]);
+ std::string strFrom = LabelFromValue(request.params[0]);
+ std::string strTo = LabelFromValue(request.params[1]);
+ CAmount nAmount = AmountFromValue(request.params[2]);
if (nAmount <= 0)
throw JSONRPCError(RPC_TYPE_ERROR, "Invalid amount for send");
- if (params.size() > 3)
+ if (!request.params[3].isNull())
// unused parameter, used to be nMinDepth, keep type-checking it though
- (void)params[3].get_int();
- string strComment;
- if (params.size() > 4)
- strComment = params[4].get_str();
+ (void)request.params[3].get_int();
+ std::string strComment;
+ if (!request.params[4].isNull())
+ strComment = request.params[4].get_str();
- if (!pwalletMain->AccountMove(strFrom, strTo, nAmount, strComment))
+ if (!pwallet->AccountMove(strFrom, strTo, nAmount, strComment)) {
throw JSONRPCError(RPC_DATABASE_ERROR, "database error");
+ }
return true;
}
-UniValue sendfrom(const UniValue& params, bool fHelp)
+static UniValue sendfrom(const JSONRPCRequest& request)
{
- if (!EnsureWalletIsAvailable(fHelp))
+ std::shared_ptr<CWallet> const wallet = GetWalletForJSONRPCRequest(request);
+ CWallet* const pwallet = wallet.get();
+
+ if (!EnsureWalletIsAvailable(pwallet, request.fHelp)) {
return NullUniValue;
+ }
+
+ if (!IsDeprecatedRPCEnabled("accounts")) {
+ if (request.fHelp) {
+ throw std::runtime_error("sendfrom (Deprecated, will be removed in V0.18. To use this command, start bitcoind with -deprecatedrpc=accounts)");
+ }
+ throw JSONRPCError(RPC_METHOD_DEPRECATED, "sendfrom is deprecated and will be removed in V0.18. To use this command, start bitcoind with -deprecatedrpc=accounts.");
+ }
+
- if (fHelp || params.size() < 3 || params.size() > 6)
- throw runtime_error(
- "sendfrom \"fromaccount\" \"tobitcoinaddress\" amount ( minconf \"comment\" \"comment-to\" )\n"
+ if (request.fHelp || request.params.size() < 3 || request.params.size() > 6)
+ throw std::runtime_error(
+ "sendfrom \"fromaccount\" \"toaddress\" amount ( minconf \"comment\" \"comment_to\" )\n"
"\nDEPRECATED (use sendtoaddress). Sent an amount from an account to a bitcoin address."
- + HelpRequiringPassphrase() + "\n"
+ + HelpRequiringPassphrase(pwallet) + "\n"
"\nArguments:\n"
"1. \"fromaccount\" (string, required) The name of the account to send funds from. May be the default account using \"\".\n"
- "2. \"tobitcoinaddress\" (string, required) The bitcoin address to send funds to.\n"
+ " Specifying an account does not influence coin selection, but it does associate the newly created\n"
+ " transaction with the account, so the account's balance computation and transaction history can reflect\n"
+ " the spend.\n"
+ "2. \"toaddress\" (string, required) The bitcoin address to send funds to.\n"
"3. amount (numeric or string, required) The amount in " + CURRENCY_UNIT + " (transaction fee is added on top).\n"
"4. minconf (numeric, optional, default=1) Only use funds with at least this many confirmations.\n"
"5. \"comment\" (string, optional) A comment used to store what the transaction is for. \n"
" This is not part of the transaction, just kept in your wallet.\n"
- "6. \"comment-to\" (string, optional) An optional comment to store the name of the person or organization \n"
+ "6. \"comment_to\" (string, optional) An optional comment to store the name of the person or organization \n"
" to which you're sending the transaction. This is not part of the transaction, \n"
" it is just kept in your wallet.\n"
"\nResult:\n"
- "\"transactionid\" (string) The transaction id.\n"
+ "\"txid\" (string) The transaction id.\n"
"\nExamples:\n"
"\nSend 0.01 " + CURRENCY_UNIT + " from the default account to the address, must have at least 1 confirmation\n"
+ HelpExampleCli("sendfrom", "\"\" \"1M72Sfpbz1BPpXFHz9m3CdqATR44Jvaydd\" 0.01") +
@@ -815,49 +1073,98 @@ UniValue sendfrom(const UniValue& params, bool fHelp)
+ HelpExampleRpc("sendfrom", "\"tabby\", \"1M72Sfpbz1BPpXFHz9m3CdqATR44Jvaydd\", 0.01, 6, \"donation\", \"seans outpost\"")
);
- LOCK2(cs_main, pwalletMain->cs_wallet);
+ // Make sure the results are valid at least up to the most recent block
+ // the user could have gotten from another RPC command prior to now
+ pwallet->BlockUntilSyncedToCurrentChain();
- string strAccount = AccountFromValue(params[0]);
- CBitcoinAddress address(params[1].get_str());
- if (!address.IsValid())
+ LOCK2(cs_main, pwallet->cs_wallet);
+
+ std::string strAccount = LabelFromValue(request.params[0]);
+ CTxDestination dest = DecodeDestination(request.params[1].get_str());
+ if (!IsValidDestination(dest)) {
throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Invalid Bitcoin address");
- CAmount nAmount = AmountFromValue(params[2]);
+ }
+ CAmount nAmount = AmountFromValue(request.params[2]);
if (nAmount <= 0)
throw JSONRPCError(RPC_TYPE_ERROR, "Invalid amount for send");
int nMinDepth = 1;
- if (params.size() > 3)
- nMinDepth = params[3].get_int();
+ if (!request.params[3].isNull())
+ nMinDepth = request.params[3].get_int();
- CWalletTx wtx;
- wtx.strFromAccount = strAccount;
- if (params.size() > 4 && !params[4].isNull() && !params[4].get_str().empty())
- wtx.mapValue["comment"] = params[4].get_str();
- if (params.size() > 5 && !params[5].isNull() && !params[5].get_str().empty())
- wtx.mapValue["to"] = params[5].get_str();
+ mapValue_t mapValue;
+ if (!request.params[4].isNull() && !request.params[4].get_str().empty())
+ mapValue["comment"] = request.params[4].get_str();
+ if (!request.params[5].isNull() && !request.params[5].get_str().empty())
+ mapValue["to"] = request.params[5].get_str();
- EnsureWalletIsUnlocked();
+ EnsureWalletIsUnlocked(pwallet);
// Check funds
- CAmount nBalance = pwalletMain->GetAccountBalance(strAccount, nMinDepth, ISMINE_SPENDABLE);
+ CAmount nBalance = pwallet->GetLegacyBalance(ISMINE_SPENDABLE, nMinDepth, &strAccount);
if (nAmount > nBalance)
throw JSONRPCError(RPC_WALLET_INSUFFICIENT_FUNDS, "Account has insufficient funds");
- SendMoney(address.Get(), nAmount, false, wtx);
-
- return wtx.GetHash().GetHex();
+ CCoinControl no_coin_control; // This is a deprecated API
+ CTransactionRef tx = SendMoney(pwallet, dest, nAmount, false, no_coin_control, std::move(mapValue), std::move(strAccount));
+ return tx->GetHash().GetHex();
}
-UniValue sendmany(const UniValue& params, bool fHelp)
+static UniValue sendmany(const JSONRPCRequest& request)
{
- if (!EnsureWalletIsAvailable(fHelp))
+ std::shared_ptr<CWallet> const wallet = GetWalletForJSONRPCRequest(request);
+ CWallet* const pwallet = wallet.get();
+
+ if (!EnsureWalletIsAvailable(pwallet, request.fHelp)) {
return NullUniValue;
+ }
- if (fHelp || params.size() < 2 || params.size() > 5)
- throw runtime_error(
- "sendmany \"fromaccount\" {\"address\":amount,...} ( minconf \"comment\" [\"address\",...] )\n"
+ std::string help_text;
+ if (!IsDeprecatedRPCEnabled("accounts")) {
+ help_text = "sendmany \"\" {\"address\":amount,...} ( minconf \"comment\" [\"address\",...] replaceable conf_target \"estimate_mode\")\n"
+ "\nSend multiple times. Amounts are double-precision floating point numbers.\n"
+ "Note that the \"fromaccount\" argument has been removed in V0.17. To use this RPC with a \"fromaccount\" argument, restart\n"
+ "bitcoind with -deprecatedrpc=accounts\n"
+ + HelpRequiringPassphrase(pwallet) + "\n"
+ "\nArguments:\n"
+ "1. \"dummy\" (string, required) Must be set to \"\" for backwards compatibility.\n"
+ "2. \"amounts\" (string, required) A json object with addresses and amounts\n"
+ " {\n"
+ " \"address\":amount (numeric or string) The bitcoin address is the key, the numeric amount (can be string) in " + CURRENCY_UNIT + " is the value\n"
+ " ,...\n"
+ " }\n"
+ "3. minconf (numeric, optional, default=1) Only use the balance confirmed at least this many times.\n"
+ "4. \"comment\" (string, optional) A comment\n"
+ "5. subtractfeefrom (array, optional) A json array with addresses.\n"
+ " The fee will be equally deducted from the amount of each selected address.\n"
+ " Those recipients will receive less bitcoins than you enter in their corresponding amount field.\n"
+ " If no addresses are specified here, the sender pays the fee.\n"
+ " [\n"
+ " \"address\" (string) Subtract fee from this address\n"
+ " ,...\n"
+ " ]\n"
+ "6. replaceable (boolean, optional) Allow this transaction to be replaced by a transaction with higher fees via BIP 125\n"
+ "7. conf_target (numeric, optional) Confirmation target (in blocks)\n"
+ "8. \"estimate_mode\" (string, optional, default=UNSET) The fee estimate mode, must be one of:\n"
+ " \"UNSET\"\n"
+ " \"ECONOMICAL\"\n"
+ " \"CONSERVATIVE\"\n"
+ "\nResult:\n"
+ "\"txid\" (string) The transaction id for the send. Only 1 transaction is created regardless of \n"
+ " the number of addresses.\n"
+ "\nExamples:\n"
+ "\nSend two amounts to two different addresses:\n"
+ + HelpExampleCli("sendmany", "\"\" \"{\\\"1D1ZrZNe3JUo7ZycKEYQQiQAWd9y54F4XX\\\":0.01,\\\"1353tsE8YMTA4EuV7dgUXGjNFf9KpVvKHz\\\":0.02}\"") +
+ "\nSend two amounts to two different addresses setting the confirmation and comment:\n"
+ + HelpExampleCli("sendmany", "\"\" \"{\\\"1D1ZrZNe3JUo7ZycKEYQQiQAWd9y54F4XX\\\":0.01,\\\"1353tsE8YMTA4EuV7dgUXGjNFf9KpVvKHz\\\":0.02}\" 6 \"testing\"") +
+ "\nSend two amounts to two different addresses, subtract fee from amount:\n"
+ + HelpExampleCli("sendmany", "\"\" \"{\\\"1D1ZrZNe3JUo7ZycKEYQQiQAWd9y54F4XX\\\":0.01,\\\"1353tsE8YMTA4EuV7dgUXGjNFf9KpVvKHz\\\":0.02}\" 1 \"\" \"[\\\"1D1ZrZNe3JUo7ZycKEYQQiQAWd9y54F4XX\\\",\\\"1353tsE8YMTA4EuV7dgUXGjNFf9KpVvKHz\\\"]\"") +
+ "\nAs a json rpc call\n"
+ + HelpExampleRpc("sendmany", "\"\", {\"1D1ZrZNe3JUo7ZycKEYQQiQAWd9y54F4XX\":0.01,\"1353tsE8YMTA4EuV7dgUXGjNFf9KpVvKHz\":0.02}, 6, \"testing\"");
+ } else {
+ help_text = "sendmany \"\" \"fromaccount\" {\"address\":amount,...} ( minconf \"comment\" [\"address\",...] replaceable conf_target \"estimate_mode\")\n"
"\nSend multiple times. Amounts are double-precision floating point numbers."
- + HelpRequiringPassphrase() + "\n"
+ + HelpRequiringPassphrase(pwallet) + "\n"
"\nArguments:\n"
"1. \"fromaccount\" (string, required) DEPRECATED. The account to send the funds from. Should be \"\" for the default account\n"
"2. \"amounts\" (string, required) A json object with addresses and amounts\n"
@@ -867,61 +1174,95 @@ UniValue sendmany(const UniValue& params, bool fHelp)
" }\n"
"3. minconf (numeric, optional, default=1) Only use the balance confirmed at least this many times.\n"
"4. \"comment\" (string, optional) A comment\n"
- "5. subtractfeefromamount (string, optional) A json array with addresses.\n"
+ "5. subtractfeefrom (array, optional) A json array with addresses.\n"
" The fee will be equally deducted from the amount of each selected address.\n"
" Those recipients will receive less bitcoins than you enter in their corresponding amount field.\n"
" If no addresses are specified here, the sender pays the fee.\n"
" [\n"
- " \"address\" (string) Subtract fee from this address\n"
+ " \"address\" (string) Subtract fee from this address\n"
" ,...\n"
" ]\n"
- "\nResult:\n"
- "\"transactionid\" (string) The transaction id for the send. Only 1 transaction is created regardless of \n"
+ "6. replaceable (boolean, optional) Allow this transaction to be replaced by a transaction with higher fees via BIP 125\n"
+ "7. conf_target (numeric, optional) Confirmation target (in blocks)\n"
+ "8. \"estimate_mode\" (string, optional, default=UNSET) The fee estimate mode, must be one of:\n"
+ " \"UNSET\"\n"
+ " \"ECONOMICAL\"\n"
+ " \"CONSERVATIVE\"\n"
+ "\nResult:\n"
+ "\"txid\" (string) The transaction id for the send. Only 1 transaction is created regardless of \n"
" the number of addresses.\n"
"\nExamples:\n"
"\nSend two amounts to two different addresses:\n"
- + HelpExampleCli("sendmany", "\"\" \"{\\\"1D1ZrZNe3JUo7ZycKEYQQiQAWd9y54F4XZ\\\":0.01,\\\"1353tsE8YMTA4EuV7dgUXGjNFf9KpVvKHz\\\":0.02}\"") +
+ + HelpExampleCli("sendmany", "\"\" \"{\\\"1D1ZrZNe3JUo7ZycKEYQQiQAWd9y54F4XX\\\":0.01,\\\"1353tsE8YMTA4EuV7dgUXGjNFf9KpVvKHz\\\":0.02}\"") +
"\nSend two amounts to two different addresses setting the confirmation and comment:\n"
- + HelpExampleCli("sendmany", "\"\" \"{\\\"1D1ZrZNe3JUo7ZycKEYQQiQAWd9y54F4XZ\\\":0.01,\\\"1353tsE8YMTA4EuV7dgUXGjNFf9KpVvKHz\\\":0.02}\" 6 \"testing\"") +
+ + HelpExampleCli("sendmany", "\"\" \"{\\\"1D1ZrZNe3JUo7ZycKEYQQiQAWd9y54F4XX\\\":0.01,\\\"1353tsE8YMTA4EuV7dgUXGjNFf9KpVvKHz\\\":0.02}\" 6 \"testing\"") +
"\nSend two amounts to two different addresses, subtract fee from amount:\n"
- + HelpExampleCli("sendmany", "\"\" \"{\\\"1D1ZrZNe3JUo7ZycKEYQQiQAWd9y54F4XZ\\\":0.01,\\\"1353tsE8YMTA4EuV7dgUXGjNFf9KpVvKHz\\\":0.02}\" 1 \"\" \"[\\\"1D1ZrZNe3JUo7ZycKEYQQiQAWd9y54F4XZ\\\",\\\"1353tsE8YMTA4EuV7dgUXGjNFf9KpVvKHz\\\"]\"") +
+ + HelpExampleCli("sendmany", "\"\" \"{\\\"1D1ZrZNe3JUo7ZycKEYQQiQAWd9y54F4XX\\\":0.01,\\\"1353tsE8YMTA4EuV7dgUXGjNFf9KpVvKHz\\\":0.02}\" 1 \"\" \"[\\\"1D1ZrZNe3JUo7ZycKEYQQiQAWd9y54F4XX\\\",\\\"1353tsE8YMTA4EuV7dgUXGjNFf9KpVvKHz\\\"]\"") +
"\nAs a json rpc call\n"
- + HelpExampleRpc("sendmany", "\"\", \"{\\\"1D1ZrZNe3JUo7ZycKEYQQiQAWd9y54F4XZ\\\":0.01,\\\"1353tsE8YMTA4EuV7dgUXGjNFf9KpVvKHz\\\":0.02}\", 6, \"testing\"")
- );
+ + HelpExampleRpc("sendmany", "\"\", {\"1D1ZrZNe3JUo7ZycKEYQQiQAWd9y54F4XX\":0.01,\"1353tsE8YMTA4EuV7dgUXGjNFf9KpVvKHz\":0.02}, 6, \"testing\"");
+ }
+
+ if (request.fHelp || request.params.size() < 2 || request.params.size() > 8) throw std::runtime_error(help_text);
- LOCK2(cs_main, pwalletMain->cs_wallet);
+ // Make sure the results are valid at least up to the most recent block
+ // the user could have gotten from another RPC command prior to now
+ pwallet->BlockUntilSyncedToCurrentChain();
- string strAccount = AccountFromValue(params[0]);
- UniValue sendTo = params[1].get_obj();
+ LOCK2(cs_main, pwallet->cs_wallet);
+
+ if (pwallet->GetBroadcastTransactions() && !g_connman) {
+ throw JSONRPCError(RPC_CLIENT_P2P_DISABLED, "Error: Peer-to-peer functionality missing or disabled");
+ }
+
+ if (!IsDeprecatedRPCEnabled("accounts") && !request.params[0].get_str().empty()) {
+ throw JSONRPCError(RPC_INVALID_PARAMETER, "Dummy value must be set to \"\"");
+ }
+ std::string strAccount = LabelFromValue(request.params[0]);
+ UniValue sendTo = request.params[1].get_obj();
int nMinDepth = 1;
- if (params.size() > 2)
- nMinDepth = params[2].get_int();
+ if (!request.params[2].isNull())
+ nMinDepth = request.params[2].get_int();
- CWalletTx wtx;
- wtx.strFromAccount = strAccount;
- if (params.size() > 3 && !params[3].isNull() && !params[3].get_str().empty())
- wtx.mapValue["comment"] = params[3].get_str();
+ mapValue_t mapValue;
+ if (!request.params[3].isNull() && !request.params[3].get_str().empty())
+ mapValue["comment"] = request.params[3].get_str();
UniValue subtractFeeFromAmount(UniValue::VARR);
- if (params.size() > 4)
- subtractFeeFromAmount = params[4].get_array();
+ if (!request.params[4].isNull())
+ subtractFeeFromAmount = request.params[4].get_array();
+
+ CCoinControl coin_control;
+ if (!request.params[5].isNull()) {
+ coin_control.m_signal_bip125_rbf = request.params[5].get_bool();
+ }
+
+ if (!request.params[6].isNull()) {
+ coin_control.m_confirm_target = ParseConfirmTarget(request.params[6]);
+ }
+
+ if (!request.params[7].isNull()) {
+ if (!FeeModeFromString(request.params[7].get_str(), coin_control.m_fee_mode)) {
+ throw JSONRPCError(RPC_INVALID_PARAMETER, "Invalid estimate_mode parameter");
+ }
+ }
- set<CBitcoinAddress> setAddress;
- vector<CRecipient> vecSend;
+ std::set<CTxDestination> destinations;
+ std::vector<CRecipient> vecSend;
CAmount totalAmount = 0;
- vector<string> keys = sendTo.getKeys();
- BOOST_FOREACH(const string& name_, keys)
- {
- CBitcoinAddress address(name_);
- if (!address.IsValid())
- throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, string("Invalid Bitcoin address: ")+name_);
+ std::vector<std::string> keys = sendTo.getKeys();
+ for (const std::string& name_ : keys) {
+ CTxDestination dest = DecodeDestination(name_);
+ if (!IsValidDestination(dest)) {
+ throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, std::string("Invalid Bitcoin address: ") + name_);
+ }
- if (setAddress.count(address))
- throw JSONRPCError(RPC_INVALID_PARAMETER, string("Invalid parameter, duplicated address: ")+name_);
- setAddress.insert(address);
+ if (destinations.count(dest)) {
+ throw JSONRPCError(RPC_INVALID_PARAMETER, std::string("Invalid parameter, duplicated address: ") + name_);
+ }
+ destinations.insert(dest);
- CScript scriptPubKey = GetScriptForDestination(address.Get());
+ CScript scriptPubKey = GetScriptForDestination(dest);
CAmount nAmount = AmountFromValue(sendTo[name_]);
if (nAmount <= 0)
throw JSONRPCError(RPC_TYPE_ERROR, "Invalid amount for send");
@@ -938,163 +1279,244 @@ UniValue sendmany(const UniValue& params, bool fHelp)
vecSend.push_back(recipient);
}
- EnsureWalletIsUnlocked();
+ EnsureWalletIsUnlocked(pwallet);
// Check funds
- CAmount nBalance = pwalletMain->GetAccountBalance(strAccount, nMinDepth, ISMINE_SPENDABLE);
- if (totalAmount > nBalance)
+ if (IsDeprecatedRPCEnabled("accounts") && totalAmount > pwallet->GetLegacyBalance(ISMINE_SPENDABLE, nMinDepth, &strAccount)) {
throw JSONRPCError(RPC_WALLET_INSUFFICIENT_FUNDS, "Account has insufficient funds");
+ } else if (!IsDeprecatedRPCEnabled("accounts") && totalAmount > pwallet->GetLegacyBalance(ISMINE_SPENDABLE, nMinDepth, nullptr)) {
+ throw JSONRPCError(RPC_WALLET_INSUFFICIENT_FUNDS, "Wallet has insufficient funds");
+ }
+
+ // Shuffle recipient list
+ std::shuffle(vecSend.begin(), vecSend.end(), FastRandomContext());
// Send
- CReserveKey keyChange(pwalletMain);
+ CReserveKey keyChange(pwallet);
CAmount nFeeRequired = 0;
int nChangePosRet = -1;
- string strFailReason;
- bool fCreated = pwalletMain->CreateTransaction(vecSend, wtx, keyChange, nFeeRequired, nChangePosRet, strFailReason);
+ std::string strFailReason;
+ CTransactionRef tx;
+ bool fCreated = pwallet->CreateTransaction(vecSend, tx, keyChange, nFeeRequired, nChangePosRet, strFailReason, coin_control);
if (!fCreated)
throw JSONRPCError(RPC_WALLET_INSUFFICIENT_FUNDS, strFailReason);
- if (!pwalletMain->CommitTransaction(wtx, keyChange))
- throw JSONRPCError(RPC_WALLET_ERROR, "Transaction commit failed");
+ CValidationState state;
+ if (!pwallet->CommitTransaction(tx, std::move(mapValue), {} /* orderForm */, std::move(strAccount), keyChange, g_connman.get(), state)) {
+ strFailReason = strprintf("Transaction commit failed:: %s", FormatStateMessage(state));
+ throw JSONRPCError(RPC_WALLET_ERROR, strFailReason);
+ }
- return wtx.GetHash().GetHex();
+ return tx->GetHash().GetHex();
}
-// Defined in rpc/misc.cpp
-extern CScript _createmultisig_redeemScript(const UniValue& params);
-
-UniValue addmultisigaddress(const UniValue& params, bool fHelp)
+static UniValue addmultisigaddress(const JSONRPCRequest& request)
{
- if (!EnsureWalletIsAvailable(fHelp))
+ std::shared_ptr<CWallet> const wallet = GetWalletForJSONRPCRequest(request);
+ CWallet* const pwallet = wallet.get();
+
+ if (!EnsureWalletIsAvailable(pwallet, request.fHelp)) {
return NullUniValue;
+ }
- if (fHelp || params.size() < 2 || params.size() > 3)
- {
- string msg = "addmultisigaddress nrequired [\"key\",...] ( \"account\" )\n"
- "\nAdd a nrequired-to-sign multisignature address to the wallet.\n"
+ if (request.fHelp || request.params.size() < 2 || request.params.size() > 4) {
+ std::string msg = "addmultisigaddress nrequired [\"key\",...] ( \"label\" \"address_type\" )\n"
+ "\nAdd a nrequired-to-sign multisignature address to the wallet. Requires a new wallet backup.\n"
"Each key is a Bitcoin address or hex-encoded public key.\n"
- "If 'account' is specified (DEPRECATED), assign address to that account.\n"
+ "This functionality is only intended for use with non-watchonly addresses.\n"
+ "See `importaddress` for watchonly p2sh address support.\n"
+ "If 'label' is specified, assign address to that label.\n"
"\nArguments:\n"
- "1. nrequired (numeric, required) The number of required signatures out of the n keys or addresses.\n"
- "2. \"keysobject\" (string, required) A json array of bitcoin addresses or hex-encoded public keys\n"
+ "1. nrequired (numeric, required) The number of required signatures out of the n keys or addresses.\n"
+ "2. \"keys\" (string, required) A json array of bitcoin addresses or hex-encoded public keys\n"
" [\n"
- " \"address\" (string) bitcoin address or hex-encoded public key\n"
+ " \"address\" (string) bitcoin address or hex-encoded public key\n"
" ...,\n"
" ]\n"
- "3. \"account\" (string, optional) DEPRECATED. An account to assign the addresses to.\n"
+ "3. \"label\" (string, optional) A label to assign the addresses to.\n"
+ "4. \"address_type\" (string, optional) The address type to use. Options are \"legacy\", \"p2sh-segwit\", and \"bech32\". Default is set by -addresstype.\n"
"\nResult:\n"
- "\"bitcoinaddress\" (string) A bitcoin address associated with the keys.\n"
-
+ "{\n"
+ " \"address\":\"multisigaddress\", (string) The value of the new multisig address.\n"
+ " \"redeemScript\":\"script\" (string) The string value of the hex-encoded redemption script.\n"
+ "}\n"
"\nExamples:\n"
"\nAdd a multisig address from 2 addresses\n"
+ HelpExampleCli("addmultisigaddress", "2 \"[\\\"16sSauSf5pF2UkUwvKGq4qjNRzBZYqgEL5\\\",\\\"171sgjn4YtPu27adkKGrdDwzRTxnRkBfKV\\\"]\"") +
"\nAs json rpc call\n"
+ HelpExampleRpc("addmultisigaddress", "2, \"[\\\"16sSauSf5pF2UkUwvKGq4qjNRzBZYqgEL5\\\",\\\"171sgjn4YtPu27adkKGrdDwzRTxnRkBfKV\\\"]\"")
;
- throw runtime_error(msg);
+ throw std::runtime_error(msg);
}
- LOCK2(cs_main, pwalletMain->cs_wallet);
+ LOCK2(cs_main, pwallet->cs_wallet);
+
+ std::string label;
+ if (!request.params[2].isNull())
+ label = LabelFromValue(request.params[2]);
+
+ int required = request.params[0].get_int();
+
+ // Get the public keys
+ const UniValue& keys_or_addrs = request.params[1].get_array();
+ std::vector<CPubKey> pubkeys;
+ for (unsigned int i = 0; i < keys_or_addrs.size(); ++i) {
+ if (IsHex(keys_or_addrs[i].get_str()) && (keys_or_addrs[i].get_str().length() == 66 || keys_or_addrs[i].get_str().length() == 130)) {
+ pubkeys.push_back(HexToPubKey(keys_or_addrs[i].get_str()));
+ } else {
+ pubkeys.push_back(AddrToPubKey(pwallet, keys_or_addrs[i].get_str()));
+ }
+ }
- string strAccount;
- if (params.size() > 2)
- strAccount = AccountFromValue(params[2]);
+ OutputType output_type = pwallet->m_default_address_type;
+ if (!request.params[3].isNull()) {
+ if (!ParseOutputType(request.params[3].get_str(), output_type)) {
+ throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, strprintf("Unknown address type '%s'", request.params[3].get_str()));
+ }
+ }
// Construct using pay-to-script-hash:
- CScript inner = _createmultisig_redeemScript(params);
- CScriptID innerID(inner);
- pwalletMain->AddCScript(inner);
+ CScript inner = CreateMultisigRedeemscript(required, pubkeys);
+ CTxDestination dest = AddAndGetDestinationForScript(*pwallet, inner, output_type);
+ pwallet->SetAddressBook(dest, label, "send");
- pwalletMain->SetAddressBook(innerID, strAccount, "send");
- return CBitcoinAddress(innerID).ToString();
+ UniValue result(UniValue::VOBJ);
+ result.pushKV("address", EncodeDestination(dest));
+ result.pushKV("redeemScript", HexStr(inner.begin(), inner.end()));
+ return result;
}
class Witnessifier : public boost::static_visitor<bool>
{
public:
- CScriptID result;
+ CWallet * const pwallet;
+ CTxDestination result;
+ bool already_witness;
- bool operator()(const CNoDestination &dest) const { return false; }
+ explicit Witnessifier(CWallet *_pwallet) : pwallet(_pwallet), already_witness(false) {}
bool operator()(const CKeyID &keyID) {
- CPubKey pubkey;
- if (pwalletMain && pwalletMain->GetPubKey(keyID, pubkey)) {
- CScript basescript;
- basescript << ToByteVector(pubkey) << OP_CHECKSIG;
+ if (pwallet) {
+ CScript basescript = GetScriptForDestination(keyID);
CScript witscript = GetScriptForWitness(basescript);
- pwalletMain->AddCScript(witscript);
- result = CScriptID(witscript);
- return true;
+ if (!IsSolvable(*pwallet, witscript)) {
+ return false;
+ }
+ return ExtractDestination(witscript, result);
}
return false;
}
bool operator()(const CScriptID &scriptID) {
CScript subscript;
- if (pwalletMain && pwalletMain->GetCScript(scriptID, subscript)) {
+ if (pwallet && pwallet->GetCScript(scriptID, subscript)) {
int witnessversion;
std::vector<unsigned char> witprog;
if (subscript.IsWitnessProgram(witnessversion, witprog)) {
- result = scriptID;
+ ExtractDestination(subscript, result);
+ already_witness = true;
return true;
}
CScript witscript = GetScriptForWitness(subscript);
- pwalletMain->AddCScript(witscript);
- result = CScriptID(witscript);
- return true;
+ if (!IsSolvable(*pwallet, witscript)) {
+ return false;
+ }
+ return ExtractDestination(witscript, result);
}
return false;
}
+
+ bool operator()(const WitnessV0KeyHash& id)
+ {
+ already_witness = true;
+ result = id;
+ return true;
+ }
+
+ bool operator()(const WitnessV0ScriptHash& id)
+ {
+ already_witness = true;
+ result = id;
+ return true;
+ }
+
+ template<typename T>
+ bool operator()(const T& dest) { return false; }
};
-UniValue addwitnessaddress(const UniValue& params, bool fHelp)
+static UniValue addwitnessaddress(const JSONRPCRequest& request)
{
- if (!EnsureWalletIsAvailable(fHelp))
+ std::shared_ptr<CWallet> const wallet = GetWalletForJSONRPCRequest(request);
+ CWallet* const pwallet = wallet.get();
+
+ if (!EnsureWalletIsAvailable(pwallet, request.fHelp)) {
return NullUniValue;
+ }
- if (fHelp || params.size() < 1 || params.size() > 1)
+ if (request.fHelp || request.params.size() < 1 || request.params.size() > 2)
{
- string msg = "addwitnessaddress \"address\"\n"
- "\nAdd a witness address for a script (with pubkey or redeemscript known).\n"
+ std::string msg = "addwitnessaddress \"address\" ( p2sh )\n"
+ "\nDEPRECATED: set the address_type argument of getnewaddress, or option -addresstype=[bech32|p2sh-segwit] instead.\n"
+ "Add a witness address for a script (with pubkey or redeemscript known). Requires a new wallet backup.\n"
"It returns the witness script.\n"
"\nArguments:\n"
"1. \"address\" (string, required) An address known to the wallet\n"
+ "2. p2sh (bool, optional, default=true) Embed inside P2SH\n"
"\nResult:\n"
- "\"witnessaddress\", (string) The value of the new address (P2SH of witness script).\n"
+ "\"witnessaddress\", (string) The value of the new address (P2SH or BIP173).\n"
"}\n"
;
- throw runtime_error(msg);
+ throw std::runtime_error(msg);
}
- {
- LOCK(cs_main);
- if (!IsWitnessEnabled(chainActive.Tip(), Params().GetConsensus()) && !GetBoolArg("-walletprematurewitness", false)) {
- throw JSONRPCError(RPC_WALLET_ERROR, "Segregated witness not enabled on network");
- }
+ if (!IsDeprecatedRPCEnabled("addwitnessaddress")) {
+ throw JSONRPCError(RPC_METHOD_DEPRECATED, "addwitnessaddress is deprecated and will be fully removed in v0.17. "
+ "To use addwitnessaddress in v0.16, restart bitcoind with -deprecatedrpc=addwitnessaddress.\n"
+ "Projects should transition to using the address_type argument of getnewaddress, or option -addresstype=[bech32|p2sh-segwit] instead.\n");
}
- CBitcoinAddress address(params[0].get_str());
- if (!address.IsValid())
+ CTxDestination dest = DecodeDestination(request.params[0].get_str());
+ if (!IsValidDestination(dest)) {
throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Invalid Bitcoin address");
+ }
+
+ bool p2sh = true;
+ if (!request.params[1].isNull()) {
+ p2sh = request.params[1].get_bool();
+ }
- Witnessifier w;
- CTxDestination dest = address.Get();
+ Witnessifier w(pwallet);
bool ret = boost::apply_visitor(w, dest);
if (!ret) {
- throw JSONRPCError(RPC_WALLET_ERROR, "Public key or redeemscript not known to wallet");
+ throw JSONRPCError(RPC_WALLET_ERROR, "Public key or redeemscript not known to wallet, or the key is uncompressed");
}
- return CBitcoinAddress(w.result).ToString();
+ CScript witprogram = GetScriptForDestination(w.result);
+
+ if (p2sh) {
+ w.result = CScriptID(witprogram);
+ }
+
+ if (w.already_witness) {
+ if (!(dest == w.result)) {
+ throw JSONRPCError(RPC_WALLET_ERROR, "Cannot convert between witness address types");
+ }
+ } else {
+ pwallet->AddCScript(witprogram); // Implicit for single-key now, but necessary for multisig and for compatibility with older software
+ pwallet->SetAddressBook(w.result, "", "receive");
+ }
+
+ return EncodeDestination(w.result);
}
struct tallyitem
{
CAmount nAmount;
int nConf;
- vector<uint256> txids;
+ std::vector<uint256> txids;
bool fIsWatchonly;
tallyitem()
{
@@ -1104,49 +1526,62 @@ struct tallyitem
}
};
-UniValue ListReceived(const UniValue& params, bool fByAccounts)
+static UniValue ListReceived(CWallet * const pwallet, const UniValue& params, bool by_label)
{
// Minimum confirmations
int nMinDepth = 1;
- if (params.size() > 0)
+ if (!params[0].isNull())
nMinDepth = params[0].get_int();
- // Whether to include empty accounts
+ // Whether to include empty labels
bool fIncludeEmpty = false;
- if (params.size() > 1)
+ if (!params[1].isNull())
fIncludeEmpty = params[1].get_bool();
isminefilter filter = ISMINE_SPENDABLE;
- if(params.size() > 2)
+ if(!params[2].isNull())
if(params[2].get_bool())
filter = filter | ISMINE_WATCH_ONLY;
+ bool has_filtered_address = false;
+ CTxDestination filtered_address = CNoDestination();
+ if (!by_label && params.size() > 3) {
+ if (!IsValidDestinationString(params[3].get_str())) {
+ throw JSONRPCError(RPC_WALLET_ERROR, "address_filter parameter was invalid");
+ }
+ filtered_address = DecodeDestination(params[3].get_str());
+ has_filtered_address = true;
+ }
+
// Tally
- map<CBitcoinAddress, tallyitem> mapTally;
- for (map<uint256, CWalletTx>::iterator it = pwalletMain->mapWallet.begin(); it != pwalletMain->mapWallet.end(); ++it)
- {
- const CWalletTx& wtx = (*it).second;
+ std::map<CTxDestination, tallyitem> mapTally;
+ for (const std::pair<const uint256, CWalletTx>& pairWtx : pwallet->mapWallet) {
+ const CWalletTx& wtx = pairWtx.second;
- if (wtx.IsCoinBase() || !CheckFinalTx(wtx))
+ if (wtx.IsCoinBase() || !CheckFinalTx(*wtx.tx))
continue;
int nDepth = wtx.GetDepthInMainChain();
if (nDepth < nMinDepth)
continue;
- BOOST_FOREACH(const CTxOut& txout, wtx.vout)
+ for (const CTxOut& txout : wtx.tx->vout)
{
CTxDestination address;
if (!ExtractDestination(txout.scriptPubKey, address))
continue;
- isminefilter mine = IsMine(*pwalletMain, address);
+ if (has_filtered_address && !(filtered_address == address)) {
+ continue;
+ }
+
+ isminefilter mine = IsMine(*pwallet, address);
if(!(mine & filter))
continue;
tallyitem& item = mapTally[address];
item.nAmount += txout.nValue;
- item.nConf = min(item.nConf, nDepth);
+ item.nConf = std::min(item.nConf, nDepth);
item.txids.push_back(wtx.GetHash());
if (mine & ISMINE_WATCH_ONLY)
item.fIsWatchonly = true;
@@ -1155,12 +1590,25 @@ UniValue ListReceived(const UniValue& params, bool fByAccounts)
// Reply
UniValue ret(UniValue::VARR);
- map<string, tallyitem> mapAccountTally;
- BOOST_FOREACH(const PAIRTYPE(CBitcoinAddress, CAddressBookData)& item, pwalletMain->mapAddressBook)
+ std::map<std::string, tallyitem> label_tally;
+
+ // Create mapAddressBook iterator
+ // If we aren't filtering, go from begin() to end()
+ auto start = pwallet->mapAddressBook.begin();
+ auto end = pwallet->mapAddressBook.end();
+ // If we are filtering, find() the applicable entry
+ if (has_filtered_address) {
+ start = pwallet->mapAddressBook.find(filtered_address);
+ if (start != end) {
+ end = std::next(start);
+ }
+ }
+
+ for (auto item_it = start; item_it != end; ++item_it)
{
- const CBitcoinAddress& address = item.first;
- const string& strAccount = item.second.name;
- map<CBitcoinAddress, tallyitem>::iterator it = mapTally.find(address);
+ const CTxDestination& address = item_it->first;
+ const std::string& label = item_it->second.name;
+ auto it = mapTally.find(address);
if (it == mapTally.end() && !fIncludeEmpty)
continue;
@@ -1174,49 +1622,49 @@ UniValue ListReceived(const UniValue& params, bool fByAccounts)
fIsWatchonly = (*it).second.fIsWatchonly;
}
- if (fByAccounts)
+ if (by_label)
{
- tallyitem& item = mapAccountTally[strAccount];
- item.nAmount += nAmount;
- item.nConf = min(item.nConf, nConf);
- item.fIsWatchonly = fIsWatchonly;
+ tallyitem& _item = label_tally[label];
+ _item.nAmount += nAmount;
+ _item.nConf = std::min(_item.nConf, nConf);
+ _item.fIsWatchonly = fIsWatchonly;
}
else
{
UniValue obj(UniValue::VOBJ);
if(fIsWatchonly)
- obj.push_back(Pair("involvesWatchonly", true));
- obj.push_back(Pair("address", address.ToString()));
- obj.push_back(Pair("account", strAccount));
- obj.push_back(Pair("amount", ValueFromAmount(nAmount)));
- obj.push_back(Pair("confirmations", (nConf == std::numeric_limits<int>::max() ? 0 : nConf)));
- if (!fByAccounts)
- obj.push_back(Pair("label", strAccount));
+ obj.pushKV("involvesWatchonly", true);
+ obj.pushKV("address", EncodeDestination(address));
+ obj.pushKV("account", label);
+ obj.pushKV("amount", ValueFromAmount(nAmount));
+ obj.pushKV("confirmations", (nConf == std::numeric_limits<int>::max() ? 0 : nConf));
+ obj.pushKV("label", label);
UniValue transactions(UniValue::VARR);
if (it != mapTally.end())
{
- BOOST_FOREACH(const uint256& item, (*it).second.txids)
+ for (const uint256& _item : (*it).second.txids)
{
- transactions.push_back(item.GetHex());
+ transactions.push_back(_item.GetHex());
}
}
- obj.push_back(Pair("txids", transactions));
+ obj.pushKV("txids", transactions);
ret.push_back(obj);
}
}
- if (fByAccounts)
+ if (by_label)
{
- for (map<string, tallyitem>::iterator it = mapAccountTally.begin(); it != mapAccountTally.end(); ++it)
+ for (const auto& entry : label_tally)
{
- CAmount nAmount = (*it).second.nAmount;
- int nConf = (*it).second.nConf;
+ CAmount nAmount = entry.second.nAmount;
+ int nConf = entry.second.nConf;
UniValue obj(UniValue::VOBJ);
- if((*it).second.fIsWatchonly)
- obj.push_back(Pair("involvesWatchonly", true));
- obj.push_back(Pair("account", (*it).first));
- obj.push_back(Pair("amount", ValueFromAmount(nAmount)));
- obj.push_back(Pair("confirmations", (nConf == std::numeric_limits<int>::max() ? 0 : nConf)));
+ if (entry.second.fIsWatchonly)
+ obj.pushKV("involvesWatchonly", true);
+ obj.pushKV("account", entry.first);
+ obj.pushKV("amount", ValueFromAmount(nAmount));
+ obj.pushKV("confirmations", (nConf == std::numeric_limits<int>::max() ? 0 : nConf));
+ obj.pushKV("label", entry.first);
ret.push_back(obj);
}
}
@@ -1224,29 +1672,37 @@ UniValue ListReceived(const UniValue& params, bool fByAccounts)
return ret;
}
-UniValue listreceivedbyaddress(const UniValue& params, bool fHelp)
+static UniValue listreceivedbyaddress(const JSONRPCRequest& request)
{
- if (!EnsureWalletIsAvailable(fHelp))
+ std::shared_ptr<CWallet> const wallet = GetWalletForJSONRPCRequest(request);
+ CWallet* const pwallet = wallet.get();
+
+ if (!EnsureWalletIsAvailable(pwallet, request.fHelp)) {
return NullUniValue;
+ }
- if (fHelp || params.size() > 3)
- throw runtime_error(
- "listreceivedbyaddress ( minconf includeempty includeWatchonly)\n"
+ if (request.fHelp || request.params.size() > 4)
+ throw std::runtime_error(
+ "listreceivedbyaddress ( minconf include_empty include_watchonly address_filter )\n"
"\nList balances by receiving address.\n"
"\nArguments:\n"
- "1. minconf (numeric, optional, default=1) The minimum number of confirmations before payments are included.\n"
- "2. includeempty (bool, optional, default=false) Whether to include addresses that haven't received any payments.\n"
- "3. includeWatchonly (bool, optional, default=false) Whether to include watchonly addresses (see 'importaddress').\n"
-
+ "1. minconf (numeric, optional, default=1) The minimum number of confirmations before payments are included.\n"
+ "2. include_empty (bool, optional, default=false) Whether to include addresses that haven't received any payments.\n"
+ "3. include_watchonly (bool, optional, default=false) Whether to include watch-only addresses (see 'importaddress').\n"
+ "4. address_filter (string, optional) If present, only return information on this address.\n"
"\nResult:\n"
"[\n"
" {\n"
" \"involvesWatchonly\" : true, (bool) Only returned if imported addresses were involved in transaction\n"
" \"address\" : \"receivingaddress\", (string) The receiving address\n"
- " \"account\" : \"accountname\", (string) DEPRECATED. The account of the receiving address. The default account is \"\".\n"
+ " \"account\" : \"accountname\", (string) DEPRECATED. Backwards compatible alias for label.\n"
" \"amount\" : x.xxx, (numeric) The total amount in " + CURRENCY_UNIT + " received by the address\n"
" \"confirmations\" : n, (numeric) The number of confirmations of the most recent transaction included\n"
- " \"label\" : \"label\" (string) A comment for the address/transaction, if any\n"
+ " \"label\" : \"label\", (string) The label of the receiving address. The default label is \"\".\n"
+ " \"txids\": [\n"
+ " \"txid\", (string) The ids of transactions received with the address \n"
+ " ...\n"
+ " ]\n"
" }\n"
" ,...\n"
"]\n"
@@ -1255,88 +1711,121 @@ UniValue listreceivedbyaddress(const UniValue& params, bool fHelp)
+ HelpExampleCli("listreceivedbyaddress", "")
+ HelpExampleCli("listreceivedbyaddress", "6 true")
+ HelpExampleRpc("listreceivedbyaddress", "6, true, true")
+ + HelpExampleRpc("listreceivedbyaddress", "6, true, true, \"1M72Sfpbz1BPpXFHz9m3CdqATR44Jvaydd\"")
);
- LOCK2(cs_main, pwalletMain->cs_wallet);
+ // Make sure the results are valid at least up to the most recent block
+ // the user could have gotten from another RPC command prior to now
+ pwallet->BlockUntilSyncedToCurrentChain();
- return ListReceived(params, false);
+ LOCK2(cs_main, pwallet->cs_wallet);
+
+ return ListReceived(pwallet, request.params, false);
}
-UniValue listreceivedbyaccount(const UniValue& params, bool fHelp)
+static UniValue listreceivedbylabel(const JSONRPCRequest& request)
{
- if (!EnsureWalletIsAvailable(fHelp))
+ std::shared_ptr<CWallet> const wallet = GetWalletForJSONRPCRequest(request);
+ CWallet* const pwallet = wallet.get();
+
+ if (!EnsureWalletIsAvailable(pwallet, request.fHelp)) {
return NullUniValue;
+ }
+
+ if (!IsDeprecatedRPCEnabled("accounts") && request.strMethod == "listreceivedbyaccount") {
+ if (request.fHelp) {
+ throw std::runtime_error("listreceivedbyaccount (Deprecated, will be removed in V0.18. To use this command, start bitcoind with -deprecatedrpc=accounts)");
+ }
+ throw JSONRPCError(RPC_METHOD_DEPRECATED, "listreceivedbyaccount is deprecated and will be removed in V0.18. To use this command, start bitcoind with -deprecatedrpc=accounts.");
+ }
- if (fHelp || params.size() > 3)
- throw runtime_error(
- "listreceivedbyaccount ( minconf includeempty includeWatchonly)\n"
- "\nDEPRECATED. List balances by account.\n"
+ if (request.fHelp || request.params.size() > 3)
+ throw std::runtime_error(
+ "listreceivedbylabel ( minconf include_empty include_watchonly)\n"
+ "\nList received transactions by label.\n"
"\nArguments:\n"
- "1. minconf (numeric, optional, default=1) The minimum number of confirmations before payments are included.\n"
- "2. includeempty (bool, optional, default=false) Whether to include accounts that haven't received any payments.\n"
- "3. includeWatchonly (bool, optional, default=false) Whether to include watchonly addresses (see 'importaddress').\n"
+ "1. minconf (numeric, optional, default=1) The minimum number of confirmations before payments are included.\n"
+ "2. include_empty (bool, optional, default=false) Whether to include labels that haven't received any payments.\n"
+ "3. include_watchonly (bool, optional, default=false) Whether to include watch-only addresses (see 'importaddress').\n"
"\nResult:\n"
"[\n"
" {\n"
" \"involvesWatchonly\" : true, (bool) Only returned if imported addresses were involved in transaction\n"
- " \"account\" : \"accountname\", (string) The account name of the receiving account\n"
- " \"amount\" : x.xxx, (numeric) The total amount received by addresses with this account\n"
+ " \"account\" : \"accountname\", (string) DEPRECATED. Backwards compatible alias for label.\n"
+ " \"amount\" : x.xxx, (numeric) The total amount received by addresses with this label\n"
" \"confirmations\" : n, (numeric) The number of confirmations of the most recent transaction included\n"
- " \"label\" : \"label\" (string) A comment for the address/transaction, if any\n"
+ " \"label\" : \"label\" (string) The label of the receiving address. The default label is \"\".\n"
" }\n"
" ,...\n"
"]\n"
"\nExamples:\n"
- + HelpExampleCli("listreceivedbyaccount", "")
- + HelpExampleCli("listreceivedbyaccount", "6 true")
- + HelpExampleRpc("listreceivedbyaccount", "6, true, true")
+ + HelpExampleCli("listreceivedbylabel", "")
+ + HelpExampleCli("listreceivedbylabel", "6 true")
+ + HelpExampleRpc("listreceivedbylabel", "6, true, true")
);
- LOCK2(cs_main, pwalletMain->cs_wallet);
+ // Make sure the results are valid at least up to the most recent block
+ // the user could have gotten from another RPC command prior to now
+ pwallet->BlockUntilSyncedToCurrentChain();
- return ListReceived(params, true);
+ LOCK2(cs_main, pwallet->cs_wallet);
+
+ return ListReceived(pwallet, request.params, true);
}
static void MaybePushAddress(UniValue & entry, const CTxDestination &dest)
{
- CBitcoinAddress addr;
- if (addr.Set(dest))
- entry.push_back(Pair("address", addr.ToString()));
+ if (IsValidDestination(dest)) {
+ entry.pushKV("address", EncodeDestination(dest));
+ }
}
-void ListTransactions(const CWalletTx& wtx, const string& strAccount, int nMinDepth, bool fLong, UniValue& ret, const isminefilter& filter)
+/**
+ * List transactions based on the given criteria.
+ *
+ * @param pwallet The wallet.
+ * @param wtx The wallet transaction.
+ * @param strAccount The account, if any, or "*" for all.
+ * @param nMinDepth The minimum confirmation depth.
+ * @param fLong Whether to include the JSON version of the transaction.
+ * @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, const std::string& strAccount, int nMinDepth, bool fLong, UniValue& ret, const isminefilter& filter)
{
CAmount nFee;
- string strSentAccount;
- list<COutputEntry> listReceived;
- list<COutputEntry> listSent;
+ std::string strSentAccount;
+ std::list<COutputEntry> listReceived;
+ std::list<COutputEntry> listSent;
wtx.GetAmounts(listReceived, listSent, nFee, strSentAccount, filter);
- bool fAllAccounts = (strAccount == string("*"));
+ bool fAllAccounts = (strAccount == std::string("*"));
bool involvesWatchonly = wtx.IsFromMe(ISMINE_WATCH_ONLY);
// Sent
if ((!listSent.empty() || nFee != 0) && (fAllAccounts || strAccount == strSentAccount))
{
- BOOST_FOREACH(const COutputEntry& s, listSent)
+ for (const COutputEntry& s : listSent)
{
UniValue entry(UniValue::VOBJ);
- if(involvesWatchonly || (::IsMine(*pwalletMain, s.destination) & ISMINE_WATCH_ONLY))
- entry.push_back(Pair("involvesWatchonly", true));
- entry.push_back(Pair("account", strSentAccount));
+ if (involvesWatchonly || (::IsMine(*pwallet, s.destination) & ISMINE_WATCH_ONLY)) {
+ entry.pushKV("involvesWatchonly", true);
+ }
+ if (IsDeprecatedRPCEnabled("accounts")) entry.pushKV("account", strSentAccount);
MaybePushAddress(entry, s.destination);
- entry.push_back(Pair("category", "send"));
- entry.push_back(Pair("amount", ValueFromAmount(-s.amount)));
- if (pwalletMain->mapAddressBook.count(s.destination))
- entry.push_back(Pair("label", pwalletMain->mapAddressBook[s.destination].name));
- entry.push_back(Pair("vout", s.vout));
- entry.push_back(Pair("fee", ValueFromAmount(-nFee)));
+ entry.pushKV("category", "send");
+ entry.pushKV("amount", ValueFromAmount(-s.amount));
+ if (pwallet->mapAddressBook.count(s.destination)) {
+ entry.pushKV("label", pwallet->mapAddressBook[s.destination].name);
+ }
+ entry.pushKV("vout", s.vout);
+ entry.pushKV("fee", ValueFromAmount(-nFee));
if (fLong)
WalletTxToJSON(wtx, entry);
- entry.push_back(Pair("abandoned", wtx.isAbandoned()));
+ entry.pushKV("abandoned", wtx.isAbandoned());
ret.push_back(entry);
}
}
@@ -1344,35 +1833,38 @@ void ListTransactions(const CWalletTx& wtx, const string& strAccount, int nMinDe
// Received
if (listReceived.size() > 0 && wtx.GetDepthInMainChain() >= nMinDepth)
{
- BOOST_FOREACH(const COutputEntry& r, listReceived)
+ for (const COutputEntry& r : listReceived)
{
- string account;
- if (pwalletMain->mapAddressBook.count(r.destination))
- account = pwalletMain->mapAddressBook[r.destination].name;
+ std::string account;
+ if (pwallet->mapAddressBook.count(r.destination)) {
+ account = pwallet->mapAddressBook[r.destination].name;
+ }
if (fAllAccounts || (account == strAccount))
{
UniValue entry(UniValue::VOBJ);
- if(involvesWatchonly || (::IsMine(*pwalletMain, r.destination) & ISMINE_WATCH_ONLY))
- entry.push_back(Pair("involvesWatchonly", true));
- entry.push_back(Pair("account", account));
+ if (involvesWatchonly || (::IsMine(*pwallet, r.destination) & ISMINE_WATCH_ONLY)) {
+ entry.pushKV("involvesWatchonly", true);
+ }
+ if (IsDeprecatedRPCEnabled("accounts")) entry.pushKV("account", account);
MaybePushAddress(entry, r.destination);
if (wtx.IsCoinBase())
{
if (wtx.GetDepthInMainChain() < 1)
- entry.push_back(Pair("category", "orphan"));
+ entry.pushKV("category", "orphan");
else if (wtx.GetBlocksToMaturity() > 0)
- entry.push_back(Pair("category", "immature"));
+ entry.pushKV("category", "immature");
else
- entry.push_back(Pair("category", "generate"));
+ entry.pushKV("category", "generate");
}
else
{
- entry.push_back(Pair("category", "receive"));
+ entry.pushKV("category", "receive");
}
- entry.push_back(Pair("amount", ValueFromAmount(r.amount)));
- if (pwalletMain->mapAddressBook.count(r.destination))
- entry.push_back(Pair("label", account));
- entry.push_back(Pair("vout", r.vout));
+ entry.pushKV("amount", ValueFromAmount(r.amount));
+ if (pwallet->mapAddressBook.count(r.destination)) {
+ entry.pushKV("label", account);
+ }
+ entry.pushKV("vout", r.vout);
if (fLong)
WalletTxToJSON(wtx, entry);
ret.push_back(entry);
@@ -1381,43 +1873,92 @@ void ListTransactions(const CWalletTx& wtx, const string& strAccount, int nMinDe
}
}
-void AcentryToJSON(const CAccountingEntry& acentry, const string& strAccount, UniValue& ret)
+static void AcentryToJSON(const CAccountingEntry& acentry, const std::string& strAccount, UniValue& ret)
{
- bool fAllAccounts = (strAccount == string("*"));
+ bool fAllAccounts = (strAccount == std::string("*"));
if (fAllAccounts || acentry.strAccount == strAccount)
{
UniValue entry(UniValue::VOBJ);
- entry.push_back(Pair("account", acentry.strAccount));
- entry.push_back(Pair("category", "move"));
- entry.push_back(Pair("time", acentry.nTime));
- entry.push_back(Pair("amount", ValueFromAmount(acentry.nCreditDebit)));
- entry.push_back(Pair("otheraccount", acentry.strOtherAccount));
- entry.push_back(Pair("comment", acentry.strComment));
+ entry.pushKV("account", acentry.strAccount);
+ entry.pushKV("category", "move");
+ entry.pushKV("time", acentry.nTime);
+ entry.pushKV("amount", ValueFromAmount(acentry.nCreditDebit));
+ if (IsDeprecatedRPCEnabled("accounts")) entry.pushKV("otheraccount", acentry.strOtherAccount);
+ entry.pushKV("comment", acentry.strComment);
ret.push_back(entry);
}
}
-UniValue listtransactions(const UniValue& params, bool fHelp)
+UniValue listtransactions(const JSONRPCRequest& request)
{
- if (!EnsureWalletIsAvailable(fHelp))
+ std::shared_ptr<CWallet> const wallet = GetWalletForJSONRPCRequest(request);
+ CWallet* const pwallet = wallet.get();
+
+ if (!EnsureWalletIsAvailable(pwallet, request.fHelp)) {
return NullUniValue;
+ }
- if (fHelp || params.size() > 4)
- throw runtime_error(
- "listtransactions ( \"account\" count from includeWatchonly)\n"
+ std::string help_text {};
+ if (!IsDeprecatedRPCEnabled("accounts")) {
+ help_text = "listtransactions (dummy count skip include_watchonly)\n"
"\nReturns up to 'count' most recent transactions skipping the first 'from' transactions for account 'account'.\n"
+ "Note that the \"account\" argument and \"otheraccount\" return value have been removed in V0.17. To use this RPC with an \"account\" argument, restart\n"
+ "bitcoind with -deprecatedrpc=accounts\n"
"\nArguments:\n"
- "1. \"account\" (string, optional) DEPRECATED. The account name. Should be \"*\".\n"
+ "1. \"dummy\" (string, optional) If set, should be \"*\" for backwards compatibility.\n"
"2. count (numeric, optional, default=10) The number of transactions to return\n"
- "3. from (numeric, optional, default=0) The number of transactions to skip\n"
- "4. includeWatchonly (bool, optional, default=false) Include transactions to watchonly addresses (see 'importaddress')\n"
+ "3. skip (numeric, optional, default=0) The number of transactions to skip\n"
+ "4. include_watchonly (bool, optional, default=false) Include transactions to watch-only addresses (see 'importaddress')\n"
"\nResult:\n"
"[\n"
" {\n"
- " \"account\":\"accountname\", (string) DEPRECATED. The account name associated with the transaction. \n"
+ " \"address\":\"address\", (string) The bitcoin address of the transaction.\n"
+ " \"category\":\"send|receive\", (string) The transaction category.\n"
+ " \"amount\": x.xxx, (numeric) The amount in " + CURRENCY_UNIT + ". This is negative for the 'send' category, and is positive\n"
+ " for the 'receive' category,\n"
+ " \"label\": \"label\", (string) A comment for the address/transaction, if any\n"
+ " \"vout\": n, (numeric) the vout value\n"
+ " \"fee\": x.xxx, (numeric) The amount of the fee in " + CURRENCY_UNIT + ". This is negative and only available for the \n"
+ " 'send' category of transactions.\n"
+ " \"confirmations\": n, (numeric) The number of confirmations for the transaction. Negative confirmations indicate the\n"
+ " transaction conflicts with the block chain\n"
+ " \"trusted\": xxx, (bool) Whether we consider the outputs of this unconfirmed transaction safe to spend.\n"
+ " \"blockhash\": \"hashvalue\", (string) The block hash containing the transaction.\n"
+ " \"blockindex\": n, (numeric) The index of the transaction in the block that includes it.\n"
+ " \"blocktime\": xxx, (numeric) The block time in seconds since epoch (1 Jan 1970 GMT).\n"
+ " \"txid\": \"transactionid\", (string) The transaction id.\n"
+ " \"time\": xxx, (numeric) The transaction time in seconds since epoch (midnight Jan 1 1970 GMT).\n"
+ " \"timereceived\": xxx, (numeric) The time received in seconds since epoch (midnight Jan 1 1970 GMT).\n"
+ " \"comment\": \"...\", (string) If a comment is associated with the transaction.\n"
+ " \"bip125-replaceable\": \"yes|no|unknown\", (string) Whether this transaction could be replaced due to BIP125 (replace-by-fee);\n"
+ " may be unknown for unconfirmed transactions not in the mempool\n"
+ " \"abandoned\": xxx (bool) 'true' if the transaction has been abandoned (inputs are respendable). Only available for the \n"
+ " 'send' category of transactions.\n"
+ " }\n"
+ "]\n"
+
+ "\nExamples:\n"
+ "\nList the most recent 10 transactions in the systems\n"
+ + HelpExampleCli("listtransactions", "") +
+ "\nList transactions 100 to 120\n"
+ + HelpExampleCli("listtransactions", "\"*\" 20 100") +
+ "\nAs a json rpc call\n"
+ + HelpExampleRpc("listtransactions", "\"*\", 20, 100");
+ } else {
+ help_text = "listtransactions ( \"account\" count skip include_watchonly)\n"
+ "\nReturns up to 'count' most recent transactions skipping the first 'from' transactions for account 'account'.\n"
+ "\nArguments:\n"
+ "1. \"account\" (string, optional) DEPRECATED. This argument will be removed in V0.18. The account name. Should be \"*\".\n"
+ "2. count (numeric, optional, default=10) The number of transactions to return\n"
+ "3. skip (numeric, optional, default=0) The number of transactions to skip\n"
+ "4. include_watchonly (bool, optional, default=false) Include transactions to watch-only addresses (see 'importaddress')\n"
+ "\nResult:\n"
+ "[\n"
+ " {\n"
+ " \"account\":\"accountname\", (string) DEPRECATED. This field will be removed in V0.18. The account name associated with the transaction. \n"
" It will be \"\" for the default account.\n"
- " \"address\":\"bitcoinaddress\", (string) The bitcoin address of the transaction. Not present for \n"
+ " \"address\":\"address\", (string) The bitcoin address of the transaction. Not present for \n"
" move transactions (category = move).\n"
" \"category\":\"send|receive|move\", (string) The transaction category. 'move' is a local (off blockchain)\n"
" transaction between accounts, and not associated with an address,\n"
@@ -1426,14 +1967,14 @@ UniValue listtransactions(const UniValue& params, bool fHelp)
" \"amount\": x.xxx, (numeric) The amount in " + CURRENCY_UNIT + ". This is negative for the 'send' category, and for the\n"
" 'move' category for moves outbound. It is positive for the 'receive' category,\n"
" and for the 'move' category for inbound funds.\n"
+ " \"label\": \"label\", (string) A comment for the address/transaction, if any\n"
" \"vout\": n, (numeric) the vout value\n"
" \"fee\": x.xxx, (numeric) The amount of the fee in " + CURRENCY_UNIT + ". This is negative and only available for the \n"
" 'send' category of transactions.\n"
- " \"abandoned\": xxx (bool) 'true' if the transaction has been abandoned (inputs are respendable).\n"
" \"confirmations\": n, (numeric) The number of confirmations for the transaction. Available for 'send' and \n"
" 'receive' category of transactions. Negative confirmations indicate the\n"
" transaction conflicts with the block chain\n"
- " \"trusted\": xxx (bool) Whether we consider the outputs of this unconfirmed transaction safe to spend.\n"
+ " \"trusted\": xxx, (bool) Whether we consider the outputs of this unconfirmed transaction safe to spend.\n"
" \"blockhash\": \"hashvalue\", (string) The block hash containing the transaction. Available for 'send' and 'receive'\n"
" category of transactions.\n"
" \"blockindex\": n, (numeric) The index of the transaction in the block that includes it. Available for 'send' and 'receive'\n"
@@ -1444,12 +1985,13 @@ UniValue listtransactions(const UniValue& params, bool fHelp)
" \"timereceived\": xxx, (numeric) The time received in seconds since epoch (midnight Jan 1 1970 GMT). Available \n"
" for 'send' and 'receive' category of transactions.\n"
" \"comment\": \"...\", (string) If a comment is associated with the transaction.\n"
- " \"label\": \"label\" (string) A comment for the address/transaction, if any\n"
- " \"otheraccount\": \"accountname\", (string) For the 'move' category of transactions, the account the funds came \n"
+ " \"otheraccount\": \"accountname\", (string) DEPRECATED. This field will be removed in V0.18. For the 'move' category of transactions, the account the funds came \n"
" from (for receiving funds, positive amounts), or went to (for sending funds,\n"
" negative amounts).\n"
- " \"bip125-replaceable\": \"yes|no|unknown\" (string) Whether this transaction could be replaced due to BIP125 (replace-by-fee);\n"
+ " \"bip125-replaceable\": \"yes|no|unknown\", (string) Whether this transaction could be replaced due to BIP125 (replace-by-fee);\n"
" may be unknown for unconfirmed transactions not in the mempool\n"
+ " \"abandoned\": xxx (bool) 'true' if the transaction has been abandoned (inputs are respendable). Only available for the \n"
+ " 'send' category of transactions.\n"
" }\n"
"]\n"
@@ -1459,23 +2001,30 @@ UniValue listtransactions(const UniValue& params, bool fHelp)
"\nList transactions 100 to 120\n"
+ HelpExampleCli("listtransactions", "\"*\" 20 100") +
"\nAs a json rpc call\n"
- + HelpExampleRpc("listtransactions", "\"*\", 20, 100")
- );
+ + HelpExampleRpc("listtransactions", "\"*\", 20, 100");
+ }
+ if (request.fHelp || request.params.size() > 4) throw std::runtime_error(help_text);
- LOCK2(cs_main, pwalletMain->cs_wallet);
+ // Make sure the results are valid at least up to the most recent block
+ // the user could have gotten from another RPC command prior to now
+ pwallet->BlockUntilSyncedToCurrentChain();
- string strAccount = "*";
- if (params.size() > 0)
- strAccount = params[0].get_str();
+ std::string strAccount = "*";
+ if (!request.params[0].isNull()) {
+ strAccount = request.params[0].get_str();
+ if (!IsDeprecatedRPCEnabled("accounts") && strAccount != "*") {
+ throw JSONRPCError(RPC_INVALID_PARAMETER, "Dummy value must be set to \"*\"");
+ }
+ }
int nCount = 10;
- if (params.size() > 1)
- nCount = params[1].get_int();
+ if (!request.params[1].isNull())
+ nCount = request.params[1].get_int();
int nFrom = 0;
- if (params.size() > 2)
- nFrom = params[2].get_int();
+ if (!request.params[2].isNull())
+ nFrom = request.params[2].get_int();
isminefilter filter = ISMINE_SPENDABLE;
- if(params.size() > 3)
- if(params[3].get_bool())
+ if(!request.params[3].isNull())
+ if(request.params[3].get_bool())
filter = filter | ISMINE_WATCH_ONLY;
if (nCount < 0)
@@ -1485,20 +2034,26 @@ UniValue listtransactions(const UniValue& params, bool fHelp)
UniValue ret(UniValue::VARR);
- const CWallet::TxItems & txOrdered = pwalletMain->wtxOrdered;
-
- // iterate backwards until we have nCount items to return:
- for (CWallet::TxItems::const_reverse_iterator it = txOrdered.rbegin(); it != txOrdered.rend(); ++it)
{
- CWalletTx *const pwtx = (*it).second.first;
- if (pwtx != 0)
- ListTransactions(*pwtx, strAccount, 0, true, ret, filter);
- CAccountingEntry *const pacentry = (*it).second.second;
- if (pacentry != 0)
- AcentryToJSON(*pacentry, strAccount, ret);
+ LOCK2(cs_main, pwallet->cs_wallet);
- if ((int)ret.size() >= (nCount+nFrom)) break;
+ const CWallet::TxItems & txOrdered = pwallet->wtxOrdered;
+
+ // iterate backwards until we have nCount items to return:
+ for (CWallet::TxItems::const_reverse_iterator it = txOrdered.rbegin(); it != txOrdered.rend(); ++it)
+ {
+ CWalletTx *const pwtx = (*it).second.first;
+ if (pwtx != nullptr)
+ ListTransactions(pwallet, *pwtx, strAccount, 0, true, ret, filter);
+ if (IsDeprecatedRPCEnabled("accounts")) {
+ CAccountingEntry *const pacentry = (*it).second.second;
+ if (pacentry != nullptr) AcentryToJSON(*pacentry, strAccount, ret);
+ }
+
+ if ((int)ret.size() >= (nCount+nFrom)) break;
+ }
}
+
// ret is newest to oldest
if (nFrom > (int)ret.size())
@@ -1506,11 +2061,11 @@ UniValue listtransactions(const UniValue& params, bool fHelp)
if ((nFrom + nCount) > (int)ret.size())
nCount = ret.size() - nFrom;
- vector<UniValue> arrTmp = ret.getValues();
+ std::vector<UniValue> arrTmp = ret.getValues();
- vector<UniValue>::iterator first = arrTmp.begin();
+ std::vector<UniValue>::iterator first = arrTmp.begin();
std::advance(first, nFrom);
- vector<UniValue>::iterator last = arrTmp.begin();
+ std::vector<UniValue>::iterator last = arrTmp.begin();
std::advance(last, nFrom+nCount);
if (last != arrTmp.end()) arrTmp.erase(last, arrTmp.end());
@@ -1525,18 +2080,29 @@ UniValue listtransactions(const UniValue& params, bool fHelp)
return ret;
}
-UniValue listaccounts(const UniValue& params, bool fHelp)
+static UniValue listaccounts(const JSONRPCRequest& request)
{
- if (!EnsureWalletIsAvailable(fHelp))
+ std::shared_ptr<CWallet> const wallet = GetWalletForJSONRPCRequest(request);
+ CWallet* const pwallet = wallet.get();
+
+ if (!EnsureWalletIsAvailable(pwallet, request.fHelp)) {
return NullUniValue;
+ }
+
+ if (!IsDeprecatedRPCEnabled("accounts")) {
+ if (request.fHelp) {
+ throw std::runtime_error("listaccounts (Deprecated, will be removed in V0.18. To use this command, start bitcoind with -deprecatedrpc=accounts)");
+ }
+ throw JSONRPCError(RPC_METHOD_DEPRECATED, "listaccounts is deprecated and will be removed in V0.18. To use this command, start bitcoind with -deprecatedrpc=accounts.");
+ }
- if (fHelp || params.size() > 2)
- throw runtime_error(
- "listaccounts ( minconf includeWatchonly)\n"
+ if (request.fHelp || request.params.size() > 2)
+ throw std::runtime_error(
+ "listaccounts ( minconf include_watchonly)\n"
"\nDEPRECATED. Returns Object that has account names as keys, account balances as values.\n"
"\nArguments:\n"
- "1. minconf (numeric, optional, default=1) Only include transactions with at least this many confirmations\n"
- "2. includeWatchonly (bool, optional, default=false) Include balances in watchonly addresses (see 'importaddress')\n"
+ "1. minconf (numeric, optional, default=1) Only include transactions with at least this many confirmations\n"
+ "2. include_watchonly (bool, optional, default=false) Include balances in watch-only addresses (see 'importaddress')\n"
"\nResult:\n"
"{ (json object where keys are account names, and values are numeric balances\n"
" \"account\": x.xxx, (numeric) The property name is the account name, and the value is the total balance for the account.\n"
@@ -1553,92 +2119,113 @@ UniValue listaccounts(const UniValue& params, bool fHelp)
+ HelpExampleRpc("listaccounts", "6")
);
- LOCK2(cs_main, pwalletMain->cs_wallet);
+ // Make sure the results are valid at least up to the most recent block
+ // the user could have gotten from another RPC command prior to now
+ pwallet->BlockUntilSyncedToCurrentChain();
+
+ LOCK2(cs_main, pwallet->cs_wallet);
int nMinDepth = 1;
- if (params.size() > 0)
- nMinDepth = params[0].get_int();
+ if (!request.params[0].isNull())
+ nMinDepth = request.params[0].get_int();
isminefilter includeWatchonly = ISMINE_SPENDABLE;
- if(params.size() > 1)
- if(params[1].get_bool())
+ if(!request.params[1].isNull())
+ if(request.params[1].get_bool())
includeWatchonly = includeWatchonly | ISMINE_WATCH_ONLY;
- map<string, CAmount> mapAccountBalances;
- BOOST_FOREACH(const PAIRTYPE(CTxDestination, CAddressBookData)& entry, pwalletMain->mapAddressBook) {
- if (IsMine(*pwalletMain, entry.first) & includeWatchonly) // This address belongs to me
+ std::map<std::string, CAmount> mapAccountBalances;
+ for (const std::pair<const CTxDestination, CAddressBookData>& entry : pwallet->mapAddressBook) {
+ if (IsMine(*pwallet, entry.first) & includeWatchonly) { // This address belongs to me
mapAccountBalances[entry.second.name] = 0;
+ }
}
- for (map<uint256, CWalletTx>::iterator it = pwalletMain->mapWallet.begin(); it != pwalletMain->mapWallet.end(); ++it)
- {
- const CWalletTx& wtx = (*it).second;
+ for (const std::pair<const uint256, CWalletTx>& pairWtx : pwallet->mapWallet) {
+ const CWalletTx& wtx = pairWtx.second;
CAmount nFee;
- string strSentAccount;
- list<COutputEntry> listReceived;
- list<COutputEntry> listSent;
+ std::string strSentAccount;
+ std::list<COutputEntry> listReceived;
+ std::list<COutputEntry> listSent;
int nDepth = wtx.GetDepthInMainChain();
if (wtx.GetBlocksToMaturity() > 0 || nDepth < 0)
continue;
wtx.GetAmounts(listReceived, listSent, nFee, strSentAccount, includeWatchonly);
mapAccountBalances[strSentAccount] -= nFee;
- BOOST_FOREACH(const COutputEntry& s, listSent)
+ for (const COutputEntry& s : listSent)
mapAccountBalances[strSentAccount] -= s.amount;
if (nDepth >= nMinDepth)
{
- BOOST_FOREACH(const COutputEntry& r, listReceived)
- if (pwalletMain->mapAddressBook.count(r.destination))
- mapAccountBalances[pwalletMain->mapAddressBook[r.destination].name] += r.amount;
+ for (const COutputEntry& r : listReceived)
+ if (pwallet->mapAddressBook.count(r.destination)) {
+ mapAccountBalances[pwallet->mapAddressBook[r.destination].name] += r.amount;
+ }
else
mapAccountBalances[""] += r.amount;
}
}
- const list<CAccountingEntry> & acentries = pwalletMain->laccentries;
- BOOST_FOREACH(const CAccountingEntry& entry, acentries)
+ const std::list<CAccountingEntry>& acentries = pwallet->laccentries;
+ for (const CAccountingEntry& entry : acentries)
mapAccountBalances[entry.strAccount] += entry.nCreditDebit;
UniValue ret(UniValue::VOBJ);
- BOOST_FOREACH(const PAIRTYPE(string, CAmount)& accountBalance, mapAccountBalances) {
- ret.push_back(Pair(accountBalance.first, ValueFromAmount(accountBalance.second)));
+ for (const std::pair<const std::string, CAmount>& accountBalance : mapAccountBalances) {
+ ret.pushKV(accountBalance.first, ValueFromAmount(accountBalance.second));
}
return ret;
}
-UniValue listsinceblock(const UniValue& params, bool fHelp)
+static UniValue listsinceblock(const JSONRPCRequest& request)
{
- if (!EnsureWalletIsAvailable(fHelp))
+ std::shared_ptr<CWallet> const wallet = GetWalletForJSONRPCRequest(request);
+ CWallet* const pwallet = wallet.get();
+
+ if (!EnsureWalletIsAvailable(pwallet, request.fHelp)) {
return NullUniValue;
+ }
- if (fHelp)
- throw runtime_error(
- "listsinceblock ( \"blockhash\" target-confirmations includeWatchonly)\n"
- "\nGet all transactions in blocks since block [blockhash], or all transactions if omitted\n"
+ if (request.fHelp || request.params.size() > 4)
+ throw std::runtime_error(
+ "listsinceblock ( \"blockhash\" target_confirmations include_watchonly include_removed )\n"
+ "\nGet all transactions in blocks since block [blockhash], or all transactions if omitted.\n"
+ "If \"blockhash\" is no longer a part of the main chain, transactions from the fork point onward are included.\n"
+ "Additionally, if include_removed is set, transactions affecting the wallet which were removed are returned in the \"removed\" array.\n"
"\nArguments:\n"
- "1. \"blockhash\" (string, optional) The block hash to list transactions since\n"
- "2. target-confirmations: (numeric, optional) The confirmations required, must be 1 or more\n"
- "3. includeWatchonly: (bool, optional, default=false) Include transactions to watchonly addresses (see 'importaddress')"
+ "1. \"blockhash\" (string, optional) The block hash to list transactions since\n"
+ "2. target_confirmations: (numeric, optional, default=1) Return the nth block hash from the main chain. e.g. 1 would mean the best block hash. Note: this is not used as a filter, but only affects [lastblock] in the return value\n"
+ "3. include_watchonly: (bool, optional, default=false) Include transactions to watch-only addresses (see 'importaddress')\n"
+ "4. include_removed: (bool, optional, default=true) Show transactions that were removed due to a reorg in the \"removed\" array\n"
+ " (not guaranteed to work on pruned nodes)\n"
"\nResult:\n"
"{\n"
" \"transactions\": [\n"
- " \"account\":\"accountname\", (string) DEPRECATED. The account name associated with the transaction. Will be \"\" for the default account.\n"
- " \"address\":\"bitcoinaddress\", (string) The bitcoin address of the transaction. Not present for move transactions (category = move).\n"
+ " \"account\":\"accountname\", (string) DEPRECATED. This field will be removed in V0.18. To see this deprecated field, start bitcoind with -deprecatedrpc=accounts. The account name associated with the transaction. Will be \"\" for the default account.\n"
+ " \"address\":\"address\", (string) The bitcoin address of the transaction. Not present for move transactions (category = move).\n"
" \"category\":\"send|receive\", (string) The transaction category. 'send' has negative amounts, 'receive' has positive amounts.\n"
" \"amount\": x.xxx, (numeric) The amount in " + CURRENCY_UNIT + ". This is negative for the 'send' category, and for the 'move' category for moves \n"
" outbound. It is positive for the 'receive' category, and for the 'move' category for inbound funds.\n"
" \"vout\" : n, (numeric) the vout value\n"
" \"fee\": x.xxx, (numeric) The amount of the fee in " + CURRENCY_UNIT + ". This is negative and only available for the 'send' category of transactions.\n"
" \"confirmations\": n, (numeric) The number of confirmations for the transaction. Available for 'send' and 'receive' category of transactions.\n"
+ " When it's < 0, it means the transaction conflicted that many blocks ago.\n"
" \"blockhash\": \"hashvalue\", (string) The block hash containing the transaction. Available for 'send' and 'receive' category of transactions.\n"
" \"blockindex\": n, (numeric) The index of the transaction in the block that includes it. Available for 'send' and 'receive' category of transactions.\n"
" \"blocktime\": xxx, (numeric) The block time in seconds since epoch (1 Jan 1970 GMT).\n"
" \"txid\": \"transactionid\", (string) The transaction id. Available for 'send' and 'receive' category of transactions.\n"
" \"time\": xxx, (numeric) The transaction time in seconds since epoch (Jan 1 1970 GMT).\n"
" \"timereceived\": xxx, (numeric) The time received in seconds since epoch (Jan 1 1970 GMT). Available for 'send' and 'receive' category of transactions.\n"
+ " \"bip125-replaceable\": \"yes|no|unknown\", (string) Whether this transaction could be replaced due to BIP125 (replace-by-fee);\n"
+ " may be unknown for unconfirmed transactions not in the mempool\n"
+ " \"abandoned\": xxx, (bool) 'true' if the transaction has been abandoned (inputs are respendable). Only available for the 'send' category of transactions.\n"
" \"comment\": \"...\", (string) If a comment is associated with the transaction.\n"
" \"label\" : \"label\" (string) A comment for the address/transaction, if any\n"
" \"to\": \"...\", (string) If a comment to is associated with the transaction.\n"
- " ],\n"
- " \"lastblock\": \"lastblockhash\" (string) The hash of the last block\n"
+ " ],\n"
+ " \"removed\": [\n"
+ " <structure is the same as \"transactions\" above, only present if include_removed=true>\n"
+ " Note: transactions that were re-added in the active chain will appear as-is in this array, and may thus have a positive confirmation count.\n"
+ " ],\n"
+ " \"lastblock\": \"lastblockhash\" (string) The hash of the block (target_confirmations-1) from the best block on the main chain. This is typically used to feed back into listsinceblock the next time you call it. So you would generally use a target_confirmations of say 6, so you will be continually re-notified of transactions until they've reached 6 confirmations plus any new ones\n"
"}\n"
"\nExamples:\n"
+ HelpExampleCli("listsinceblock", "")
@@ -1646,71 +2233,110 @@ UniValue listsinceblock(const UniValue& params, bool fHelp)
+ HelpExampleRpc("listsinceblock", "\"000000000000000bacf66f7497b7dc45ef753ee9a7d38571037cdb1a57f663ad\", 6")
);
- LOCK2(cs_main, pwalletMain->cs_wallet);
+ // Make sure the results are valid at least up to the most recent block
+ // the user could have gotten from another RPC command prior to now
+ pwallet->BlockUntilSyncedToCurrentChain();
- CBlockIndex *pindex = NULL;
+ LOCK2(cs_main, pwallet->cs_wallet);
+
+ const CBlockIndex* pindex = nullptr; // Block index of the specified block or the common ancestor, if the block provided was in a deactivated chain.
+ const CBlockIndex* paltindex = nullptr; // Block index of the specified block, even if it's in a deactivated chain.
int target_confirms = 1;
isminefilter filter = ISMINE_SPENDABLE;
- if (params.size() > 0)
- {
+ if (!request.params[0].isNull() && !request.params[0].get_str().empty()) {
uint256 blockId;
- blockId.SetHex(params[0].get_str());
- BlockMap::iterator it = mapBlockIndex.find(blockId);
- if (it != mapBlockIndex.end())
- pindex = it->second;
+ blockId.SetHex(request.params[0].get_str());
+ paltindex = pindex = LookupBlockIndex(blockId);
+ if (!pindex) {
+ throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Block not found");
+ }
+ if (chainActive[pindex->nHeight] != pindex) {
+ // the block being asked for is a part of a deactivated chain;
+ // we don't want to depend on its perceived height in the block
+ // chain, we want to instead use the last common ancestor
+ pindex = chainActive.FindFork(pindex);
+ }
}
- if (params.size() > 1)
- {
- target_confirms = params[1].get_int();
+ if (!request.params[1].isNull()) {
+ target_confirms = request.params[1].get_int();
- if (target_confirms < 1)
+ if (target_confirms < 1) {
throw JSONRPCError(RPC_INVALID_PARAMETER, "Invalid parameter");
+ }
}
- if(params.size() > 2)
- if(params[2].get_bool())
- filter = filter | ISMINE_WATCH_ONLY;
+ if (!request.params[2].isNull() && request.params[2].get_bool()) {
+ filter = filter | ISMINE_WATCH_ONLY;
+ }
+
+ bool include_removed = (request.params[3].isNull() || request.params[3].get_bool());
int depth = pindex ? (1 + chainActive.Height() - pindex->nHeight) : -1;
UniValue transactions(UniValue::VARR);
- for (map<uint256, CWalletTx>::iterator it = pwalletMain->mapWallet.begin(); it != pwalletMain->mapWallet.end(); it++)
- {
- CWalletTx tx = (*it).second;
+ for (const std::pair<const uint256, CWalletTx>& pairWtx : pwallet->mapWallet) {
+ CWalletTx tx = pairWtx.second;
- if (depth == -1 || tx.GetDepthInMainChain() < depth)
- ListTransactions(tx, "*", 0, true, transactions, filter);
+ if (depth == -1 || tx.GetDepthInMainChain() < depth) {
+ ListTransactions(pwallet, tx, "*", 0, true, transactions, filter);
+ }
+ }
+
+ // when a reorg'd block is requested, we also list any relevant transactions
+ // in the blocks of the chain that was detached
+ UniValue removed(UniValue::VARR);
+ while (include_removed && paltindex && paltindex != pindex) {
+ CBlock block;
+ if (!ReadBlockFromDisk(block, paltindex, Params().GetConsensus())) {
+ throw JSONRPCError(RPC_INTERNAL_ERROR, "Can't read block from disk");
+ }
+ for (const CTransactionRef& tx : block.vtx) {
+ auto it = pwallet->mapWallet.find(tx->GetHash());
+ if (it != pwallet->mapWallet.end()) {
+ // 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);
+ }
+ }
+ paltindex = paltindex->pprev;
}
CBlockIndex *pblockLast = chainActive[chainActive.Height() + 1 - target_confirms];
uint256 lastblock = pblockLast ? pblockLast->GetBlockHash() : uint256();
UniValue ret(UniValue::VOBJ);
- ret.push_back(Pair("transactions", transactions));
- ret.push_back(Pair("lastblock", lastblock.GetHex()));
+ ret.pushKV("transactions", transactions);
+ if (include_removed) ret.pushKV("removed", removed);
+ ret.pushKV("lastblock", lastblock.GetHex());
return ret;
}
-UniValue gettransaction(const UniValue& params, bool fHelp)
+static UniValue gettransaction(const JSONRPCRequest& request)
{
- if (!EnsureWalletIsAvailable(fHelp))
+ std::shared_ptr<CWallet> const wallet = GetWalletForJSONRPCRequest(request);
+ CWallet* const pwallet = wallet.get();
+
+ if (!EnsureWalletIsAvailable(pwallet, request.fHelp)) {
return NullUniValue;
+ }
- if (fHelp || params.size() < 1 || params.size() > 2)
- throw runtime_error(
- "gettransaction \"txid\" ( includeWatchonly )\n"
+ if (request.fHelp || request.params.size() < 1 || request.params.size() > 2)
+ throw std::runtime_error(
+ "gettransaction \"txid\" ( include_watchonly )\n"
"\nGet detailed information about in-wallet transaction <txid>\n"
"\nArguments:\n"
- "1. \"txid\" (string, required) The transaction id\n"
- "2. \"includeWatchonly\" (bool, optional, default=false) Whether to include watchonly addresses in balance calculation and details[]\n"
+ "1. \"txid\" (string, required) The transaction id\n"
+ "2. \"include_watchonly\" (bool, optional, default=false) Whether to include watch-only addresses in balance calculation and details[]\n"
"\nResult:\n"
"{\n"
" \"amount\" : x.xxx, (numeric) The transaction amount in " + CURRENCY_UNIT + "\n"
+ " \"fee\": x.xxx, (numeric) The amount of the fee in " + CURRENCY_UNIT + ". This is negative and only available for the \n"
+ " 'send' category of transactions.\n"
" \"confirmations\" : n, (numeric) The number of confirmations\n"
" \"blockhash\" : \"hash\", (string) The block hash\n"
" \"blockindex\" : xx, (numeric) The index of the transaction in the block that includes it\n"
@@ -1718,16 +2344,20 @@ UniValue gettransaction(const UniValue& params, bool fHelp)
" \"txid\" : \"transactionid\", (string) The transaction id.\n"
" \"time\" : ttt, (numeric) The transaction time in seconds since epoch (1 Jan 1970 GMT)\n"
" \"timereceived\" : ttt, (numeric) The time received in seconds since epoch (1 Jan 1970 GMT)\n"
- " \"bip125-replaceable\": \"yes|no|unknown\" (string) Whether this transaction could be replaced due to BIP125 (replace-by-fee);\n"
+ " \"bip125-replaceable\": \"yes|no|unknown\", (string) Whether this transaction could be replaced due to BIP125 (replace-by-fee);\n"
" may be unknown for unconfirmed transactions not in the mempool\n"
" \"details\" : [\n"
" {\n"
- " \"account\" : \"accountname\", (string) DEPRECATED. The account name involved in the transaction, can be \"\" for the default account.\n"
- " \"address\" : \"bitcoinaddress\", (string) The bitcoin address involved in the transaction\n"
+ " \"account\" : \"accountname\", (string) DEPRECATED. This field will be removed in a V0.18. To see this deprecated field, start bitcoind with -deprecatedrpc=accounts. The account name involved in the transaction, can be \"\" for the default account.\n"
+ " \"address\" : \"address\", (string) The bitcoin address involved in the transaction\n"
" \"category\" : \"send|receive\", (string) The category, either 'send' or 'receive'\n"
" \"amount\" : x.xxx, (numeric) The amount in " + CURRENCY_UNIT + "\n"
" \"label\" : \"label\", (string) A comment for the address/transaction, if any\n"
" \"vout\" : n, (numeric) the vout value\n"
+ " \"fee\": x.xxx, (numeric) The amount of the fee in " + CURRENCY_UNIT + ". This is negative and only available for the \n"
+ " 'send' category of transactions.\n"
+ " \"abandoned\": xxx (bool) 'true' if the transaction has been abandoned (inputs are respendable). Only available for the \n"
+ " 'send' category of transactions.\n"
" }\n"
" ,...\n"
" ],\n"
@@ -1740,55 +2370,65 @@ UniValue gettransaction(const UniValue& params, bool fHelp)
+ HelpExampleRpc("gettransaction", "\"1075db55d416d3ca199f55b6084e2115b9345e16c5cf302fc80e9d5fbf5d48d\"")
);
- LOCK2(cs_main, pwalletMain->cs_wallet);
+ // Make sure the results are valid at least up to the most recent block
+ // the user could have gotten from another RPC command prior to now
+ pwallet->BlockUntilSyncedToCurrentChain();
+
+ LOCK2(cs_main, pwallet->cs_wallet);
uint256 hash;
- hash.SetHex(params[0].get_str());
+ hash.SetHex(request.params[0].get_str());
isminefilter filter = ISMINE_SPENDABLE;
- if(params.size() > 1)
- if(params[1].get_bool())
+ if(!request.params[1].isNull())
+ if(request.params[1].get_bool())
filter = filter | ISMINE_WATCH_ONLY;
UniValue entry(UniValue::VOBJ);
- if (!pwalletMain->mapWallet.count(hash))
+ auto it = pwallet->mapWallet.find(hash);
+ if (it == pwallet->mapWallet.end()) {
throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Invalid or non-wallet transaction id");
- const CWalletTx& wtx = pwalletMain->mapWallet[hash];
+ }
+ const CWalletTx& wtx = it->second;
CAmount nCredit = wtx.GetCredit(filter);
CAmount nDebit = wtx.GetDebit(filter);
CAmount nNet = nCredit - nDebit;
- CAmount nFee = (wtx.IsFromMe(filter) ? wtx.GetValueOut() - nDebit : 0);
+ CAmount nFee = (wtx.IsFromMe(filter) ? wtx.tx->GetValueOut() - nDebit : 0);
- entry.push_back(Pair("amount", ValueFromAmount(nNet - nFee)));
+ entry.pushKV("amount", ValueFromAmount(nNet - nFee));
if (wtx.IsFromMe(filter))
- entry.push_back(Pair("fee", ValueFromAmount(nFee)));
+ entry.pushKV("fee", ValueFromAmount(nFee));
WalletTxToJSON(wtx, entry);
UniValue details(UniValue::VARR);
- ListTransactions(wtx, "*", 0, false, details, filter);
- entry.push_back(Pair("details", details));
+ ListTransactions(pwallet, wtx, "*", 0, false, details, filter);
+ entry.pushKV("details", details);
- string strHex = EncodeHexTx(static_cast<CTransaction>(wtx));
- entry.push_back(Pair("hex", strHex));
+ std::string strHex = EncodeHexTx(*wtx.tx, RPCSerializationFlags());
+ entry.pushKV("hex", strHex);
return entry;
}
-UniValue abandontransaction(const UniValue& params, bool fHelp)
+static UniValue abandontransaction(const JSONRPCRequest& request)
{
- if (!EnsureWalletIsAvailable(fHelp))
+ std::shared_ptr<CWallet> const wallet = GetWalletForJSONRPCRequest(request);
+ CWallet* const pwallet = wallet.get();
+
+ if (!EnsureWalletIsAvailable(pwallet, request.fHelp)) {
return NullUniValue;
+ }
- if (fHelp || params.size() != 1)
- throw runtime_error(
+ if (request.fHelp || request.params.size() != 1) {
+ throw std::runtime_error(
"abandontransaction \"txid\"\n"
"\nMark in-wallet transaction <txid> as abandoned\n"
"This will mark this transaction and all its in-wallet descendants as abandoned which will allow\n"
"for their inputs to be respent. It can be used to replace \"stuck\" or evicted transactions.\n"
"It only works on transactions which are not included in a block and are not currently in the mempool.\n"
- "It has no effect on transactions which are already conflicted or abandoned.\n"
+ "It has no effect on transactions which are already abandoned.\n"
"\nArguments:\n"
"1. \"txid\" (string, required) The transaction id\n"
"\nResult:\n"
@@ -1796,28 +2436,39 @@ UniValue abandontransaction(const UniValue& params, bool fHelp)
+ HelpExampleCli("abandontransaction", "\"1075db55d416d3ca199f55b6084e2115b9345e16c5cf302fc80e9d5fbf5d48d\"")
+ HelpExampleRpc("abandontransaction", "\"1075db55d416d3ca199f55b6084e2115b9345e16c5cf302fc80e9d5fbf5d48d\"")
);
+ }
- LOCK2(cs_main, pwalletMain->cs_wallet);
+ // Make sure the results are valid at least up to the most recent block
+ // the user could have gotten from another RPC command prior to now
+ pwallet->BlockUntilSyncedToCurrentChain();
+
+ LOCK2(cs_main, pwallet->cs_wallet);
uint256 hash;
- hash.SetHex(params[0].get_str());
+ hash.SetHex(request.params[0].get_str());
- if (!pwalletMain->mapWallet.count(hash))
+ if (!pwallet->mapWallet.count(hash)) {
throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Invalid or non-wallet transaction id");
- if (!pwalletMain->AbandonTransaction(hash))
+ }
+ if (!pwallet->AbandonTransaction(hash)) {
throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Transaction not eligible for abandonment");
+ }
return NullUniValue;
}
-UniValue backupwallet(const UniValue& params, bool fHelp)
+static UniValue backupwallet(const JSONRPCRequest& request)
{
- if (!EnsureWalletIsAvailable(fHelp))
+ std::shared_ptr<CWallet> const wallet = GetWalletForJSONRPCRequest(request);
+ CWallet* const pwallet = wallet.get();
+
+ if (!EnsureWalletIsAvailable(pwallet, request.fHelp)) {
return NullUniValue;
+ }
- if (fHelp || params.size() != 1)
- throw runtime_error(
+ if (request.fHelp || request.params.size() != 1)
+ throw std::runtime_error(
"backupwallet \"destination\"\n"
"\nSafely copies current wallet file to destination, which can be a directory or a path with filename.\n"
"\nArguments:\n"
@@ -1827,26 +2478,35 @@ UniValue backupwallet(const UniValue& params, bool fHelp)
+ HelpExampleRpc("backupwallet", "\"backup.dat\"")
);
- LOCK2(cs_main, pwalletMain->cs_wallet);
+ // Make sure the results are valid at least up to the most recent block
+ // the user could have gotten from another RPC command prior to now
+ pwallet->BlockUntilSyncedToCurrentChain();
+
+ LOCK2(cs_main, pwallet->cs_wallet);
- string strDest = params[0].get_str();
- if (!pwalletMain->BackupWallet(strDest))
+ std::string strDest = request.params[0].get_str();
+ if (!pwallet->BackupWallet(strDest)) {
throw JSONRPCError(RPC_WALLET_ERROR, "Error: Wallet backup failed!");
+ }
return NullUniValue;
}
-UniValue keypoolrefill(const UniValue& params, bool fHelp)
+static UniValue keypoolrefill(const JSONRPCRequest& request)
{
- if (!EnsureWalletIsAvailable(fHelp))
+ std::shared_ptr<CWallet> const wallet = GetWalletForJSONRPCRequest(request);
+ CWallet* const pwallet = wallet.get();
+
+ if (!EnsureWalletIsAvailable(pwallet, request.fHelp)) {
return NullUniValue;
+ }
- if (fHelp || params.size() > 1)
- throw runtime_error(
+ if (request.fHelp || request.params.size() > 1)
+ throw std::runtime_error(
"keypoolrefill ( newsize )\n"
"\nFills the keypool."
- + HelpRequiringPassphrase() + "\n"
+ + HelpRequiringPassphrase(pwallet) + "\n"
"\nArguments\n"
"1. newsize (numeric, optional, default=100) The new keypool size\n"
"\nExamples:\n"
@@ -1854,21 +2514,26 @@ UniValue keypoolrefill(const UniValue& params, bool fHelp)
+ HelpExampleRpc("keypoolrefill", "")
);
- LOCK2(cs_main, pwalletMain->cs_wallet);
+ if (pwallet->IsWalletFlagSet(WALLET_FLAG_DISABLE_PRIVATE_KEYS)) {
+ throw JSONRPCError(RPC_WALLET_ERROR, "Error: Private keys are disabled for this wallet");
+ }
+
+ LOCK2(cs_main, pwallet->cs_wallet);
// 0 is interpreted by TopUpKeyPool() as the default keypool size given by -keypool
unsigned int kpSize = 0;
- if (params.size() > 0) {
- if (params[0].get_int() < 0)
+ if (!request.params[0].isNull()) {
+ if (request.params[0].get_int() < 0)
throw JSONRPCError(RPC_INVALID_PARAMETER, "Invalid parameter, expected valid size.");
- kpSize = (unsigned int)params[0].get_int();
+ kpSize = (unsigned int)request.params[0].get_int();
}
- EnsureWalletIsUnlocked();
- pwalletMain->TopUpKeyPool(kpSize);
+ EnsureWalletIsUnlocked(pwallet);
+ pwallet->TopUpKeyPool(kpSize);
- if (pwalletMain->GetKeyPoolSize() < kpSize)
+ if (pwallet->GetKeyPoolSize() < kpSize) {
throw JSONRPCError(RPC_WALLET_ERROR, "Error refreshing keypool.");
+ }
return NullUniValue;
}
@@ -1876,78 +2541,97 @@ UniValue keypoolrefill(const UniValue& params, bool fHelp)
static void LockWallet(CWallet* pWallet)
{
- LOCK(cs_nWalletUnlockTime);
- nWalletUnlockTime = 0;
+ LOCK(pWallet->cs_wallet);
+ pWallet->nRelockTime = 0;
pWallet->Lock();
}
-UniValue walletpassphrase(const UniValue& params, bool fHelp)
+static UniValue walletpassphrase(const JSONRPCRequest& request)
{
- if (!EnsureWalletIsAvailable(fHelp))
+ std::shared_ptr<CWallet> const wallet = GetWalletForJSONRPCRequest(request);
+ CWallet* const pwallet = wallet.get();
+
+ if (!EnsureWalletIsAvailable(pwallet, request.fHelp)) {
return NullUniValue;
+ }
- if (pwalletMain->IsCrypted() && (fHelp || params.size() != 2))
- throw runtime_error(
+ if (request.fHelp || request.params.size() != 2) {
+ throw std::runtime_error(
"walletpassphrase \"passphrase\" timeout\n"
"\nStores the wallet decryption key in memory for 'timeout' seconds.\n"
"This is needed prior to performing transactions related to private keys such as sending bitcoins\n"
"\nArguments:\n"
"1. \"passphrase\" (string, required) The wallet passphrase\n"
- "2. timeout (numeric, required) The time to keep the decryption key in seconds.\n"
+ "2. timeout (numeric, required) The time to keep the decryption key in seconds; capped at 100000000 (~3 years).\n"
"\nNote:\n"
"Issuing the walletpassphrase command while the wallet is already unlocked will set a new unlock\n"
"time that overrides the old one.\n"
"\nExamples:\n"
- "\nunlock the wallet for 60 seconds\n"
+ "\nUnlock the wallet for 60 seconds\n"
+ HelpExampleCli("walletpassphrase", "\"my pass phrase\" 60") +
"\nLock the wallet again (before 60 seconds)\n"
+ HelpExampleCli("walletlock", "") +
"\nAs json rpc call\n"
+ HelpExampleRpc("walletpassphrase", "\"my pass phrase\", 60")
);
+ }
- LOCK2(cs_main, pwalletMain->cs_wallet);
+ LOCK2(cs_main, pwallet->cs_wallet);
- if (fHelp)
- return true;
- if (!pwalletMain->IsCrypted())
+ if (!pwallet->IsCrypted()) {
throw JSONRPCError(RPC_WALLET_WRONG_ENC_STATE, "Error: running with an unencrypted wallet, but walletpassphrase was called.");
+ }
- // Note that the walletpassphrase is stored in params[0] which is not mlock()ed
+ // Note that the walletpassphrase is stored in request.params[0] which is not mlock()ed
SecureString strWalletPass;
strWalletPass.reserve(100);
// TODO: get rid of this .c_str() by implementing SecureString::operator=(std::string)
- // Alternately, find a way to make params[0] mlock()'d to begin with.
- strWalletPass = params[0].get_str().c_str();
+ // Alternately, find a way to make request.params[0] mlock()'d to begin with.
+ strWalletPass = request.params[0].get_str().c_str();
+
+ // Get the timeout
+ int64_t nSleepTime = request.params[1].get_int64();
+ // Timeout cannot be negative, otherwise it will relock immediately
+ if (nSleepTime < 0) {
+ throw JSONRPCError(RPC_INVALID_PARAMETER, "Timeout cannot be negative.");
+ }
+ // Clamp timeout
+ constexpr int64_t MAX_SLEEP_TIME = 100000000; // larger values trigger a macos/libevent bug?
+ if (nSleepTime > MAX_SLEEP_TIME) {
+ nSleepTime = MAX_SLEEP_TIME;
+ }
if (strWalletPass.length() > 0)
{
- if (!pwalletMain->Unlock(strWalletPass))
+ if (!pwallet->Unlock(strWalletPass)) {
throw JSONRPCError(RPC_WALLET_PASSPHRASE_INCORRECT, "Error: The wallet passphrase entered was incorrect.");
+ }
}
else
- throw runtime_error(
+ throw std::runtime_error(
"walletpassphrase <passphrase> <timeout>\n"
"Stores the wallet decryption key in memory for <timeout> seconds.");
- pwalletMain->TopUpKeyPool();
+ pwallet->TopUpKeyPool();
- int64_t nSleepTime = params[1].get_int64();
- LOCK(cs_nWalletUnlockTime);
- nWalletUnlockTime = GetTime() + nSleepTime;
- RPCRunLater("lockwallet", boost::bind(LockWallet, pwalletMain), nSleepTime);
+ pwallet->nRelockTime = GetTime() + nSleepTime;
+ RPCRunLater(strprintf("lockwallet(%s)", pwallet->GetName()), std::bind(LockWallet, pwallet), nSleepTime);
return NullUniValue;
}
-UniValue walletpassphrasechange(const UniValue& params, bool fHelp)
+static UniValue walletpassphrasechange(const JSONRPCRequest& request)
{
- if (!EnsureWalletIsAvailable(fHelp))
+ std::shared_ptr<CWallet> const wallet = GetWalletForJSONRPCRequest(request);
+ CWallet* const pwallet = wallet.get();
+
+ if (!EnsureWalletIsAvailable(pwallet, request.fHelp)) {
return NullUniValue;
+ }
- if (pwalletMain->IsCrypted() && (fHelp || params.size() != 2))
- throw runtime_error(
+ if (request.fHelp || request.params.size() != 2) {
+ throw std::runtime_error(
"walletpassphrasechange \"oldpassphrase\" \"newpassphrase\"\n"
"\nChanges the wallet passphrase from 'oldpassphrase' to 'newpassphrase'.\n"
"\nArguments:\n"
@@ -1957,43 +2641,48 @@ UniValue walletpassphrasechange(const UniValue& params, bool fHelp)
+ HelpExampleCli("walletpassphrasechange", "\"old one\" \"new one\"")
+ HelpExampleRpc("walletpassphrasechange", "\"old one\", \"new one\"")
);
+ }
- LOCK2(cs_main, pwalletMain->cs_wallet);
+ LOCK2(cs_main, pwallet->cs_wallet);
- if (fHelp)
- return true;
- if (!pwalletMain->IsCrypted())
+ if (!pwallet->IsCrypted()) {
throw JSONRPCError(RPC_WALLET_WRONG_ENC_STATE, "Error: running with an unencrypted wallet, but walletpassphrasechange was called.");
+ }
// TODO: get rid of these .c_str() calls by implementing SecureString::operator=(std::string)
- // Alternately, find a way to make params[0] mlock()'d to begin with.
+ // Alternately, find a way to make request.params[0] mlock()'d to begin with.
SecureString strOldWalletPass;
strOldWalletPass.reserve(100);
- strOldWalletPass = params[0].get_str().c_str();
+ strOldWalletPass = request.params[0].get_str().c_str();
SecureString strNewWalletPass;
strNewWalletPass.reserve(100);
- strNewWalletPass = params[1].get_str().c_str();
+ strNewWalletPass = request.params[1].get_str().c_str();
if (strOldWalletPass.length() < 1 || strNewWalletPass.length() < 1)
- throw runtime_error(
+ throw std::runtime_error(
"walletpassphrasechange <oldpassphrase> <newpassphrase>\n"
"Changes the wallet passphrase from <oldpassphrase> to <newpassphrase>.");
- if (!pwalletMain->ChangeWalletPassphrase(strOldWalletPass, strNewWalletPass))
+ if (!pwallet->ChangeWalletPassphrase(strOldWalletPass, strNewWalletPass)) {
throw JSONRPCError(RPC_WALLET_PASSPHRASE_INCORRECT, "Error: The wallet passphrase entered was incorrect.");
+ }
return NullUniValue;
}
-UniValue walletlock(const UniValue& params, bool fHelp)
+static UniValue walletlock(const JSONRPCRequest& request)
{
- if (!EnsureWalletIsAvailable(fHelp))
+ std::shared_ptr<CWallet> const wallet = GetWalletForJSONRPCRequest(request);
+ CWallet* const pwallet = wallet.get();
+
+ if (!EnsureWalletIsAvailable(pwallet, request.fHelp)) {
return NullUniValue;
+ }
- if (pwalletMain->IsCrypted() && (fHelp || params.size() != 0))
- throw runtime_error(
+ if (request.fHelp || request.params.size() != 0) {
+ throw std::runtime_error(
"walletlock\n"
"\nRemoves the wallet encryption key from memory, locking the wallet.\n"
"After calling this method, you will need to call walletpassphrase again\n"
@@ -2008,31 +2697,32 @@ UniValue walletlock(const UniValue& params, bool fHelp)
"\nAs json rpc call\n"
+ HelpExampleRpc("walletlock", "")
);
+ }
- LOCK2(cs_main, pwalletMain->cs_wallet);
+ LOCK2(cs_main, pwallet->cs_wallet);
- if (fHelp)
- return true;
- if (!pwalletMain->IsCrypted())
+ if (!pwallet->IsCrypted()) {
throw JSONRPCError(RPC_WALLET_WRONG_ENC_STATE, "Error: running with an unencrypted wallet, but walletlock was called.");
-
- {
- LOCK(cs_nWalletUnlockTime);
- pwalletMain->Lock();
- nWalletUnlockTime = 0;
}
+ pwallet->Lock();
+ pwallet->nRelockTime = 0;
+
return NullUniValue;
}
-UniValue encryptwallet(const UniValue& params, bool fHelp)
+static UniValue encryptwallet(const JSONRPCRequest& request)
{
- if (!EnsureWalletIsAvailable(fHelp))
+ std::shared_ptr<CWallet> const wallet = GetWalletForJSONRPCRequest(request);
+ CWallet* const pwallet = wallet.get();
+
+ if (!EnsureWalletIsAvailable(pwallet, request.fHelp)) {
return NullUniValue;
+ }
- if (!pwalletMain->IsCrypted() && (fHelp || params.size() != 1))
- throw runtime_error(
+ if (request.fHelp || request.params.size() != 1) {
+ throw std::runtime_error(
"encryptwallet \"passphrase\"\n"
"\nEncrypts the wallet with 'passphrase'. This is for first time encryption.\n"
"After this, any calls that interact with private keys such as sending or signing \n"
@@ -2043,38 +2733,39 @@ UniValue encryptwallet(const UniValue& params, bool fHelp)
"\nArguments:\n"
"1. \"passphrase\" (string) The pass phrase to encrypt the wallet with. It must be at least 1 character, but should be long.\n"
"\nExamples:\n"
- "\nEncrypt you wallet\n"
+ "\nEncrypt your wallet\n"
+ HelpExampleCli("encryptwallet", "\"my pass phrase\"") +
"\nNow set the passphrase to use the wallet, such as for signing or sending bitcoin\n"
+ HelpExampleCli("walletpassphrase", "\"my pass phrase\"") +
- "\nNow we can so something like sign\n"
- + HelpExampleCli("signmessage", "\"bitcoinaddress\" \"test message\"") +
+ "\nNow we can do something like sign\n"
+ + HelpExampleCli("signmessage", "\"address\" \"test message\"") +
"\nNow lock the wallet again by removing the passphrase\n"
+ HelpExampleCli("walletlock", "") +
"\nAs a json rpc call\n"
+ HelpExampleRpc("encryptwallet", "\"my pass phrase\"")
);
+ }
- LOCK2(cs_main, pwalletMain->cs_wallet);
+ LOCK2(cs_main, pwallet->cs_wallet);
- if (fHelp)
- return true;
- if (pwalletMain->IsCrypted())
+ if (pwallet->IsCrypted()) {
throw JSONRPCError(RPC_WALLET_WRONG_ENC_STATE, "Error: running with an encrypted wallet, but encryptwallet was called.");
+ }
// TODO: get rid of this .c_str() by implementing SecureString::operator=(std::string)
- // Alternately, find a way to make params[0] mlock()'d to begin with.
+ // Alternately, find a way to make request.params[0] mlock()'d to begin with.
SecureString strWalletPass;
strWalletPass.reserve(100);
- strWalletPass = params[0].get_str().c_str();
+ strWalletPass = request.params[0].get_str().c_str();
if (strWalletPass.length() < 1)
- throw runtime_error(
+ throw std::runtime_error(
"encryptwallet <passphrase>\n"
"Encrypts the wallet with <passphrase>.");
- if (!pwalletMain->EncryptWallet(strWalletPass))
+ if (!pwallet->EncryptWallet(strWalletPass)) {
throw JSONRPCError(RPC_WALLET_ENCRYPTION_FAILED, "Error: Failed to encrypt the wallet.");
+ }
// BDB seems to have a bad habit of writing old data into
// slack space in .dat files; that is bad if the old data is
@@ -2083,13 +2774,17 @@ UniValue encryptwallet(const UniValue& params, bool fHelp)
return "wallet encrypted; Bitcoin server stopping, restart to run with encrypted wallet. The keypool has been flushed and a new HD seed was generated (if you are using HD). You need to make a new backup.";
}
-UniValue lockunspent(const UniValue& params, bool fHelp)
+static UniValue lockunspent(const JSONRPCRequest& request)
{
- if (!EnsureWalletIsAvailable(fHelp))
+ std::shared_ptr<CWallet> const wallet = GetWalletForJSONRPCRequest(request);
+ CWallet* const pwallet = wallet.get();
+
+ if (!EnsureWalletIsAvailable(pwallet, request.fHelp)) {
return NullUniValue;
+ }
- if (fHelp || params.size() < 1 || params.size() > 2)
- throw runtime_error(
+ if (request.fHelp || request.params.size() < 1 || request.params.size() > 2)
+ throw std::runtime_error(
"lockunspent unlock ([{\"txid\":\"txid\",\"vout\":n},...])\n"
"\nUpdates list of temporarily unspendable outputs.\n"
"Temporarily lock (unlock=false) or unlock (unlock=true) specified transaction outputs.\n"
@@ -2125,27 +2820,33 @@ UniValue lockunspent(const UniValue& params, bool fHelp)
+ HelpExampleRpc("lockunspent", "false, \"[{\\\"txid\\\":\\\"a08e6907dbbd3d809776dbfc5d82e371b764ed838b5655e72f463568df1aadf0\\\",\\\"vout\\\":1}]\"")
);
- LOCK2(cs_main, pwalletMain->cs_wallet);
+ // Make sure the results are valid at least up to the most recent block
+ // the user could have gotten from another RPC command prior to now
+ pwallet->BlockUntilSyncedToCurrentChain();
- if (params.size() == 1)
- RPCTypeCheck(params, boost::assign::list_of(UniValue::VBOOL));
- else
- RPCTypeCheck(params, boost::assign::list_of(UniValue::VBOOL)(UniValue::VARR));
+ LOCK2(cs_main, pwallet->cs_wallet);
- bool fUnlock = params[0].get_bool();
+ RPCTypeCheckArgument(request.params[0], UniValue::VBOOL);
- if (params.size() == 1) {
+ bool fUnlock = request.params[0].get_bool();
+
+ if (request.params[1].isNull()) {
if (fUnlock)
- pwalletMain->UnlockAllCoins();
+ pwallet->UnlockAllCoins();
return true;
}
- UniValue outputs = params[1].get_array();
- for (unsigned int idx = 0; idx < outputs.size(); idx++) {
- const UniValue& output = outputs[idx];
- if (!output.isObject())
- throw JSONRPCError(RPC_INVALID_PARAMETER, "Invalid parameter, expected object");
- const UniValue& o = output.get_obj();
+ RPCTypeCheckArgument(request.params[1], UniValue::VARR);
+
+ const UniValue& output_params = request.params[1];
+
+ // Create and validate the COutPoints first.
+
+ std::vector<COutPoint> outputs;
+ outputs.reserve(output_params.size());
+
+ for (unsigned int idx = 0; idx < output_params.size(); idx++) {
+ const UniValue& o = output_params[idx].get_obj();
RPCTypeCheckObj(o,
{
@@ -2153,32 +2854,66 @@ UniValue lockunspent(const UniValue& params, bool fHelp)
{"vout", UniValueType(UniValue::VNUM)},
});
- string txid = find_value(o, "txid").get_str();
- if (!IsHex(txid))
+ const std::string& txid = find_value(o, "txid").get_str();
+ if (!IsHex(txid)) {
throw JSONRPCError(RPC_INVALID_PARAMETER, "Invalid parameter, expected hex txid");
+ }
- int nOutput = find_value(o, "vout").get_int();
- if (nOutput < 0)
+ const int nOutput = find_value(o, "vout").get_int();
+ if (nOutput < 0) {
throw JSONRPCError(RPC_INVALID_PARAMETER, "Invalid parameter, vout must be positive");
+ }
- COutPoint outpt(uint256S(txid), nOutput);
+ const COutPoint outpt(uint256S(txid), nOutput);
- if (fUnlock)
- pwalletMain->UnlockCoin(outpt);
- else
- pwalletMain->LockCoin(outpt);
+ const auto it = pwallet->mapWallet.find(outpt.hash);
+ if (it == pwallet->mapWallet.end()) {
+ throw JSONRPCError(RPC_INVALID_PARAMETER, "Invalid parameter, unknown transaction");
+ }
+
+ const CWalletTx& trans = it->second;
+
+ if (outpt.n >= trans.tx->vout.size()) {
+ throw JSONRPCError(RPC_INVALID_PARAMETER, "Invalid parameter, vout index out of bounds");
+ }
+
+ if (pwallet->IsSpent(outpt.hash, outpt.n)) {
+ throw JSONRPCError(RPC_INVALID_PARAMETER, "Invalid parameter, expected unspent output");
+ }
+
+ const bool is_locked = pwallet->IsLockedCoin(outpt.hash, outpt.n);
+
+ if (fUnlock && !is_locked) {
+ throw JSONRPCError(RPC_INVALID_PARAMETER, "Invalid parameter, expected locked output");
+ }
+
+ if (!fUnlock && is_locked) {
+ throw JSONRPCError(RPC_INVALID_PARAMETER, "Invalid parameter, output already locked");
+ }
+
+ outputs.push_back(outpt);
+ }
+
+ // Atomically set (un)locked status for the outputs.
+ for (const COutPoint& outpt : outputs) {
+ if (fUnlock) pwallet->UnlockCoin(outpt);
+ else pwallet->LockCoin(outpt);
}
return true;
}
-UniValue listlockunspent(const UniValue& params, bool fHelp)
+static UniValue listlockunspent(const JSONRPCRequest& request)
{
- if (!EnsureWalletIsAvailable(fHelp))
+ std::shared_ptr<CWallet> const wallet = GetWalletForJSONRPCRequest(request);
+ CWallet* const pwallet = wallet.get();
+
+ if (!EnsureWalletIsAvailable(pwallet, request.fHelp)) {
return NullUniValue;
+ }
- if (fHelp || params.size() > 0)
- throw runtime_error(
+ if (request.fHelp || request.params.size() > 0)
+ throw std::runtime_error(
"listlockunspent\n"
"\nReturns list of temporarily unspendable outputs.\n"
"See the lockunspent call to lock and unlock transactions for spending.\n"
@@ -2203,33 +2938,37 @@ UniValue listlockunspent(const UniValue& params, bool fHelp)
+ HelpExampleRpc("listlockunspent", "")
);
- LOCK2(cs_main, pwalletMain->cs_wallet);
+ LOCK2(cs_main, pwallet->cs_wallet);
- vector<COutPoint> vOutpts;
- pwalletMain->ListLockedCoins(vOutpts);
+ std::vector<COutPoint> vOutpts;
+ pwallet->ListLockedCoins(vOutpts);
UniValue ret(UniValue::VARR);
- BOOST_FOREACH(COutPoint &outpt, vOutpts) {
+ for (COutPoint &outpt : vOutpts) {
UniValue o(UniValue::VOBJ);
- o.push_back(Pair("txid", outpt.hash.GetHex()));
- o.push_back(Pair("vout", (int)outpt.n));
+ o.pushKV("txid", outpt.hash.GetHex());
+ o.pushKV("vout", (int)outpt.n);
ret.push_back(o);
}
return ret;
}
-UniValue settxfee(const UniValue& params, bool fHelp)
+static UniValue settxfee(const JSONRPCRequest& request)
{
- if (!EnsureWalletIsAvailable(fHelp))
+ std::shared_ptr<CWallet> const wallet = GetWalletForJSONRPCRequest(request);
+ CWallet* const pwallet = wallet.get();
+
+ if (!EnsureWalletIsAvailable(pwallet, request.fHelp)) {
return NullUniValue;
+ }
- if (fHelp || params.size() < 1 || params.size() > 1)
- throw runtime_error(
+ if (request.fHelp || request.params.size() < 1 || request.params.size() > 1) {
+ throw std::runtime_error(
"settxfee amount\n"
- "\nSet the transaction fee per kB. Overwrites the paytxfee parameter.\n"
+ "\nSet the transaction fee per kB for this wallet. Overrides the global -paytxfee command line parameter.\n"
"\nArguments:\n"
"1. amount (numeric or string, required) The transaction fee in " + CURRENCY_UNIT + "/kB\n"
"\nResult\n"
@@ -2238,119 +2977,365 @@ UniValue settxfee(const UniValue& params, bool fHelp)
+ HelpExampleCli("settxfee", "0.00001")
+ HelpExampleRpc("settxfee", "0.00001")
);
+ }
- LOCK2(cs_main, pwalletMain->cs_wallet);
+ LOCK2(cs_main, pwallet->cs_wallet);
- // Amount
- CAmount nAmount = AmountFromValue(params[0]);
+ CAmount nAmount = AmountFromValue(request.params[0]);
+ CFeeRate tx_fee_rate(nAmount, 1000);
+ if (tx_fee_rate == 0) {
+ // automatic selection
+ } else if (tx_fee_rate < ::minRelayTxFee) {
+ throw JSONRPCError(RPC_INVALID_PARAMETER, strprintf("txfee cannot be less than min relay tx fee (%s)", ::minRelayTxFee.ToString()));
+ } else if (tx_fee_rate < pwallet->m_min_fee) {
+ throw JSONRPCError(RPC_INVALID_PARAMETER, strprintf("txfee cannot be less than wallet min fee (%s)", pwallet->m_min_fee.ToString()));
+ }
- payTxFee = CFeeRate(nAmount, 1000);
+ pwallet->m_pay_tx_fee = tx_fee_rate;
return true;
}
-UniValue getwalletinfo(const UniValue& params, bool fHelp)
+static UniValue getwalletinfo(const JSONRPCRequest& request)
{
- if (!EnsureWalletIsAvailable(fHelp))
+ std::shared_ptr<CWallet> const wallet = GetWalletForJSONRPCRequest(request);
+ CWallet* const pwallet = wallet.get();
+
+ if (!EnsureWalletIsAvailable(pwallet, request.fHelp)) {
return NullUniValue;
+ }
- if (fHelp || params.size() != 0)
- throw runtime_error(
+ if (request.fHelp || request.params.size() != 0)
+ throw std::runtime_error(
"getwalletinfo\n"
"Returns an object containing various wallet state info.\n"
"\nResult:\n"
"{\n"
- " \"walletversion\": xxxxx, (numeric) the wallet version\n"
- " \"balance\": xxxxxxx, (numeric) the total confirmed balance of the wallet in " + CURRENCY_UNIT + "\n"
- " \"unconfirmed_balance\": xxx, (numeric) the total unconfirmed balance of the wallet in " + CURRENCY_UNIT + "\n"
- " \"immature_balance\": xxxxxx, (numeric) the total immature balance of the wallet in " + CURRENCY_UNIT + "\n"
- " \"txcount\": xxxxxxx, (numeric) the total number of transactions in the wallet\n"
- " \"keypoololdest\": xxxxxx, (numeric) the timestamp (seconds since GMT epoch) of the oldest pre-generated key in the key pool\n"
- " \"keypoolsize\": xxxx, (numeric) how many new keys are pre-generated\n"
- " \"unlocked_until\": ttt, (numeric) the timestamp in seconds since epoch (midnight Jan 1 1970 GMT) that the wallet is unlocked for transfers, or 0 if the wallet is locked\n"
- " \"paytxfee\": x.xxxx, (numeric) the transaction fee configuration, set in " + CURRENCY_UNIT + "/kB\n"
- " \"hdmasterkeyid\": \"<hash160>\", (string) the Hash160 of the HD master pubkey\n"
+ " \"walletname\": xxxxx, (string) the wallet name\n"
+ " \"walletversion\": xxxxx, (numeric) the wallet version\n"
+ " \"balance\": xxxxxxx, (numeric) the total confirmed balance of the wallet in " + CURRENCY_UNIT + "\n"
+ " \"unconfirmed_balance\": xxx, (numeric) the total unconfirmed balance of the wallet in " + CURRENCY_UNIT + "\n"
+ " \"immature_balance\": xxxxxx, (numeric) the total immature balance of the wallet in " + CURRENCY_UNIT + "\n"
+ " \"txcount\": xxxxxxx, (numeric) the total number of transactions in the wallet\n"
+ " \"keypoololdest\": xxxxxx, (numeric) the timestamp (seconds since Unix epoch) of the oldest pre-generated key in the key pool\n"
+ " \"keypoolsize\": xxxx, (numeric) how many new keys are pre-generated (only counts external keys)\n"
+ " \"keypoolsize_hd_internal\": xxxx, (numeric) how many new keys are pre-generated for internal use (used for change outputs, only appears if the wallet is using this feature, otherwise external keys are used)\n"
+ " \"unlocked_until\": ttt, (numeric) the timestamp in seconds since epoch (midnight Jan 1 1970 GMT) that the wallet is unlocked for transfers, or 0 if the wallet is locked\n"
+ " \"paytxfee\": x.xxxx, (numeric) the transaction fee configuration, set in " + CURRENCY_UNIT + "/kB\n"
+ " \"hdseedid\": \"<hash160>\" (string, optional) the Hash160 of the HD seed (only present when HD is enabled)\n"
+ " \"hdmasterkeyid\": \"<hash160>\" (string, optional) alias for hdseedid retained for backwards-compatibility. Will be removed in V0.18.\n"
+ " \"private_keys_enabled\": true|false (boolean) false if privatekeys are disabled for this wallet (enforced watch-only wallet)\n"
"}\n"
"\nExamples:\n"
+ HelpExampleCli("getwalletinfo", "")
+ HelpExampleRpc("getwalletinfo", "")
);
- LOCK2(cs_main, pwalletMain->cs_wallet);
+ // Make sure the results are valid at least up to the most recent block
+ // the user could have gotten from another RPC command prior to now
+ pwallet->BlockUntilSyncedToCurrentChain();
+
+ LOCK2(cs_main, pwallet->cs_wallet);
+
+ UniValue obj(UniValue::VOBJ);
+
+ size_t kpExternalSize = pwallet->KeypoolCountExternalKeys();
+ obj.pushKV("walletname", pwallet->GetName());
+ obj.pushKV("walletversion", pwallet->GetVersion());
+ obj.pushKV("balance", ValueFromAmount(pwallet->GetBalance()));
+ obj.pushKV("unconfirmed_balance", ValueFromAmount(pwallet->GetUnconfirmedBalance()));
+ obj.pushKV("immature_balance", ValueFromAmount(pwallet->GetImmatureBalance()));
+ obj.pushKV("txcount", (int)pwallet->mapWallet.size());
+ obj.pushKV("keypoololdest", pwallet->GetOldestKeyPoolTime());
+ obj.pushKV("keypoolsize", (int64_t)kpExternalSize);
+ CKeyID seed_id = pwallet->GetHDChain().seed_id;
+ if (!seed_id.IsNull() && pwallet->CanSupportFeature(FEATURE_HD_SPLIT)) {
+ obj.pushKV("keypoolsize_hd_internal", (int64_t)(pwallet->GetKeyPoolSize() - kpExternalSize));
+ }
+ if (pwallet->IsCrypted()) {
+ obj.pushKV("unlocked_until", pwallet->nRelockTime);
+ }
+ obj.pushKV("paytxfee", ValueFromAmount(pwallet->m_pay_tx_fee.GetFeePerK()));
+ if (!seed_id.IsNull()) {
+ obj.pushKV("hdseedid", seed_id.GetHex());
+ obj.pushKV("hdmasterkeyid", seed_id.GetHex());
+ }
+ obj.pushKV("private_keys_enabled", !pwallet->IsWalletFlagSet(WALLET_FLAG_DISABLE_PRIVATE_KEYS));
+ return obj;
+}
+
+static UniValue listwallets(const JSONRPCRequest& request)
+{
+ if (request.fHelp || request.params.size() != 0)
+ throw std::runtime_error(
+ "listwallets\n"
+ "Returns a list of currently loaded wallets.\n"
+ "For full information on the wallet, use \"getwalletinfo\"\n"
+ "\nResult:\n"
+ "[ (json array of strings)\n"
+ " \"walletname\" (string) the wallet name\n"
+ " ...\n"
+ "]\n"
+ "\nExamples:\n"
+ + HelpExampleCli("listwallets", "")
+ + HelpExampleRpc("listwallets", "")
+ );
+
+ UniValue obj(UniValue::VARR);
+
+ for (const std::shared_ptr<CWallet>& wallet : GetWallets()) {
+ if (!EnsureWalletIsAvailable(wallet.get(), request.fHelp)) {
+ return NullUniValue;
+ }
+
+ LOCK(wallet->cs_wallet);
+
+ obj.push_back(wallet->GetName());
+ }
+
+ return obj;
+}
+
+static UniValue loadwallet(const JSONRPCRequest& request)
+{
+ if (request.fHelp || request.params.size() != 1)
+ throw std::runtime_error(
+ "loadwallet \"filename\"\n"
+ "\nLoads a wallet from a wallet file or directory."
+ "\nNote that all wallet command-line options used when starting bitcoind will be"
+ "\napplied to the new wallet (eg -zapwallettxes, upgradewallet, rescan, etc).\n"
+ "\nArguments:\n"
+ "1. \"filename\" (string, required) The wallet directory or .dat file.\n"
+ "\nResult:\n"
+ "{\n"
+ " \"name\" : <wallet_name>, (string) The wallet name if loaded successfully.\n"
+ " \"warning\" : <warning>, (string) Warning message if wallet was not loaded cleanly.\n"
+ "}\n"
+ "\nExamples:\n"
+ + HelpExampleCli("loadwallet", "\"test.dat\"")
+ + HelpExampleRpc("loadwallet", "\"test.dat\"")
+ );
+ std::string wallet_file = request.params[0].get_str();
+ std::string error;
+
+ fs::path wallet_path = fs::absolute(wallet_file, GetWalletDir());
+ if (fs::symlink_status(wallet_path).type() == fs::file_not_found) {
+ throw JSONRPCError(RPC_WALLET_NOT_FOUND, "Wallet " + wallet_file + " not found.");
+ } else if (fs::is_directory(wallet_path)) {
+ // The given filename is a directory. Check that there's a wallet.dat file.
+ fs::path wallet_dat_file = wallet_path / "wallet.dat";
+ if (fs::symlink_status(wallet_dat_file).type() == fs::file_not_found) {
+ throw JSONRPCError(RPC_WALLET_NOT_FOUND, "Directory " + wallet_file + " does not contain a wallet.dat file.");
+ }
+ }
+
+ std::string warning;
+ if (!CWallet::Verify(wallet_file, false, error, warning)) {
+ throw JSONRPCError(RPC_WALLET_ERROR, "Wallet file verification failed: " + error);
+ }
+
+ std::shared_ptr<CWallet> const wallet = CWallet::CreateWalletFromFile(wallet_file, fs::absolute(wallet_file, GetWalletDir()));
+ if (!wallet) {
+ throw JSONRPCError(RPC_WALLET_ERROR, "Wallet loading failed.");
+ }
+ AddWallet(wallet);
+
+ wallet->postInitProcess();
UniValue obj(UniValue::VOBJ);
- obj.push_back(Pair("walletversion", pwalletMain->GetVersion()));
- obj.push_back(Pair("balance", ValueFromAmount(pwalletMain->GetBalance())));
- obj.push_back(Pair("unconfirmed_balance", ValueFromAmount(pwalletMain->GetUnconfirmedBalance())));
- obj.push_back(Pair("immature_balance", ValueFromAmount(pwalletMain->GetImmatureBalance())));
- obj.push_back(Pair("txcount", (int)pwalletMain->mapWallet.size()));
- obj.push_back(Pair("keypoololdest", pwalletMain->GetOldestKeyPoolTime()));
- obj.push_back(Pair("keypoolsize", (int)pwalletMain->GetKeyPoolSize()));
- if (pwalletMain->IsCrypted())
- obj.push_back(Pair("unlocked_until", nWalletUnlockTime));
- obj.push_back(Pair("paytxfee", ValueFromAmount(payTxFee.GetFeePerK())));
- CKeyID masterKeyID = pwalletMain->GetHDChain().masterKeyID;
- if (!masterKeyID.IsNull())
- obj.push_back(Pair("hdmasterkeyid", masterKeyID.GetHex()));
+ obj.pushKV("name", wallet->GetName());
+ obj.pushKV("warning", warning);
+
return obj;
}
-UniValue resendwallettransactions(const UniValue& params, bool fHelp)
+static UniValue createwallet(const JSONRPCRequest& request)
{
- if (!EnsureWalletIsAvailable(fHelp))
+ if (request.fHelp || request.params.size() < 1 || request.params.size() > 2) {
+ throw std::runtime_error(
+ "createwallet \"wallet_name\" ( disable_private_keys )\n"
+ "\nCreates and loads a new wallet.\n"
+ "\nArguments:\n"
+ "1. \"wallet_name\" (string, required) The name for the new wallet. If this is a path, the wallet will be created at the path location.\n"
+ "2. disable_private_keys (boolean, optional, default: false) Disable the possibility of private keys (only watchonlys are possible in this mode).\n"
+ "\nResult:\n"
+ "{\n"
+ " \"name\" : <wallet_name>, (string) The wallet name if created successfully. If the wallet was created using a full path, the wallet_name will be the full path.\n"
+ " \"warning\" : <warning>, (string) Warning message if wallet was not loaded cleanly.\n"
+ "}\n"
+ "\nExamples:\n"
+ + HelpExampleCli("createwallet", "\"testwallet\"")
+ + HelpExampleRpc("createwallet", "\"testwallet\"")
+ );
+ }
+ std::string wallet_name = request.params[0].get_str();
+ std::string error;
+ std::string warning;
+
+ bool disable_privatekeys = false;
+ if (!request.params[1].isNull()) {
+ disable_privatekeys = request.params[1].get_bool();
+ }
+
+ fs::path wallet_path = fs::absolute(wallet_name, GetWalletDir());
+ if (fs::symlink_status(wallet_path).type() != fs::file_not_found) {
+ throw JSONRPCError(RPC_WALLET_ERROR, "Wallet " + wallet_name + " already exists.");
+ }
+
+ // Wallet::Verify will check if we're trying to create a wallet with a duplication name.
+ if (!CWallet::Verify(wallet_name, false, error, warning)) {
+ throw JSONRPCError(RPC_WALLET_ERROR, "Wallet file verification failed: " + error);
+ }
+
+ std::shared_ptr<CWallet> const wallet = CWallet::CreateWalletFromFile(wallet_name, fs::absolute(wallet_name, GetWalletDir()), (disable_privatekeys ? (uint64_t)WALLET_FLAG_DISABLE_PRIVATE_KEYS : 0));
+ if (!wallet) {
+ throw JSONRPCError(RPC_WALLET_ERROR, "Wallet creation failed.");
+ }
+ AddWallet(wallet);
+
+ wallet->postInitProcess();
+
+ UniValue obj(UniValue::VOBJ);
+ obj.pushKV("name", wallet->GetName());
+ obj.pushKV("warning", warning);
+
+ return obj;
+}
+
+static UniValue unloadwallet(const JSONRPCRequest& request)
+{
+ if (request.fHelp || request.params.size() > 1) {
+ throw std::runtime_error(
+ "unloadwallet ( \"wallet_name\" )\n"
+ "Unloads the wallet referenced by the request endpoint otherwise unloads the wallet specified in the argument.\n"
+ "Specifying the wallet name on a wallet endpoint is invalid."
+ "\nArguments:\n"
+ "1. \"wallet_name\" (string, optional) The name of the wallet to unload.\n"
+ "\nExamples:\n"
+ + HelpExampleCli("unloadwallet", "wallet_name")
+ + HelpExampleRpc("unloadwallet", "wallet_name")
+ );
+ }
+
+ std::string wallet_name;
+ if (GetWalletNameFromJSONRPCRequest(request, wallet_name)) {
+ if (!request.params[0].isNull()) {
+ throw JSONRPCError(RPC_INVALID_PARAMETER, "Cannot unload the requested wallet");
+ }
+ } else {
+ wallet_name = request.params[0].get_str();
+ }
+
+ std::shared_ptr<CWallet> wallet = GetWallet(wallet_name);
+ if (!wallet) {
+ throw JSONRPCError(RPC_WALLET_NOT_FOUND, "Requested wallet does not exist or is not loaded");
+ }
+
+ // Release the "main" shared pointer and prevent further notifications.
+ // Note that any attempt to load the same wallet would fail until the wallet
+ // is destroyed (see CheckUniqueFileid).
+ if (!RemoveWallet(wallet)) {
+ throw JSONRPCError(RPC_MISC_ERROR, "Requested wallet already unloaded");
+ }
+ UnregisterValidationInterface(wallet.get());
+
+ // The wallet can be in use so it's not possible to explicitly unload here.
+ // Just notify the unload intent so that all shared pointers are released.
+ // The wallet will be destroyed once the last shared pointer is released.
+ wallet->NotifyUnload();
+
+ // There's no point in waiting for the wallet to unload.
+ // At this point this method should never fail. The unloading could only
+ // fail due to an unexpected error which would cause a process termination.
+
+ return NullUniValue;
+}
+
+static UniValue resendwallettransactions(const JSONRPCRequest& request)
+{
+ std::shared_ptr<CWallet> const wallet = GetWalletForJSONRPCRequest(request);
+ CWallet* const pwallet = wallet.get();
+
+ if (!EnsureWalletIsAvailable(pwallet, request.fHelp)) {
return NullUniValue;
+ }
- if (fHelp || params.size() != 0)
- throw runtime_error(
+ if (request.fHelp || request.params.size() != 0)
+ throw std::runtime_error(
"resendwallettransactions\n"
"Immediately re-broadcast unconfirmed wallet transactions to all peers.\n"
"Intended only for testing; the wallet code periodically re-broadcasts\n"
"automatically.\n"
+ "Returns an RPC error if -walletbroadcast is set to false.\n"
"Returns array of transaction ids that were re-broadcast.\n"
);
- LOCK2(cs_main, pwalletMain->cs_wallet);
+ if (!g_connman)
+ throw JSONRPCError(RPC_CLIENT_P2P_DISABLED, "Error: Peer-to-peer functionality missing or disabled");
- std::vector<uint256> txids = pwalletMain->ResendWalletTransactionsBefore(GetTime());
+ LOCK2(cs_main, pwallet->cs_wallet);
+
+ if (!pwallet->GetBroadcastTransactions()) {
+ throw JSONRPCError(RPC_WALLET_ERROR, "Error: Wallet transaction broadcasting is disabled with -walletbroadcast");
+ }
+
+ std::vector<uint256> txids = pwallet->ResendWalletTransactionsBefore(GetTime(), g_connman.get());
UniValue result(UniValue::VARR);
- BOOST_FOREACH(const uint256& txid, txids)
+ for (const uint256& txid : txids)
{
result.push_back(txid.ToString());
}
return result;
}
-UniValue listunspent(const UniValue& params, bool fHelp)
+static UniValue listunspent(const JSONRPCRequest& request)
{
- if (!EnsureWalletIsAvailable(fHelp))
+ std::shared_ptr<CWallet> const wallet = GetWalletForJSONRPCRequest(request);
+ CWallet* const pwallet = wallet.get();
+
+ if (!EnsureWalletIsAvailable(pwallet, request.fHelp)) {
return NullUniValue;
+ }
- if (fHelp || params.size() > 3)
- throw runtime_error(
- "listunspent ( minconf maxconf [\"address\",...] )\n"
+ if (request.fHelp || request.params.size() > 5)
+ throw std::runtime_error(
+ "listunspent ( minconf maxconf [\"addresses\",...] [include_unsafe] [query_options])\n"
"\nReturns array of unspent transaction outputs\n"
"with between minconf and maxconf (inclusive) confirmations.\n"
"Optionally filter to only include txouts paid to specified addresses.\n"
"\nArguments:\n"
"1. minconf (numeric, optional, default=1) The minimum confirmations to filter\n"
"2. maxconf (numeric, optional, default=9999999) The maximum confirmations to filter\n"
- "3. \"addresses\" (string) A json array of bitcoin addresses to filter\n"
+ "3. \"addresses\" (string) A json array of bitcoin addresses to filter\n"
" [\n"
- " \"address\" (string) bitcoin address\n"
+ " \"address\" (string) bitcoin address\n"
" ,...\n"
" ]\n"
+ "4. include_unsafe (bool, optional, default=true) Include outputs that are not safe to spend\n"
+ " See description of \"safe\" attribute below.\n"
+ "5. query_options (json, optional) JSON with query options\n"
+ " {\n"
+ " \"minimumAmount\" (numeric or string, default=0) Minimum value of each UTXO in " + CURRENCY_UNIT + "\n"
+ " \"maximumAmount\" (numeric or string, default=unlimited) Maximum value of each UTXO in " + CURRENCY_UNIT + "\n"
+ " \"maximumCount\" (numeric or string, default=unlimited) Maximum number of UTXOs\n"
+ " \"minimumSumAmount\" (numeric or string, default=unlimited) Minimum sum value of all UTXOs in " + CURRENCY_UNIT + "\n"
+ " }\n"
"\nResult\n"
"[ (array of json object)\n"
" {\n"
" \"txid\" : \"txid\", (string) the transaction id \n"
" \"vout\" : n, (numeric) the vout value\n"
" \"address\" : \"address\", (string) the bitcoin address\n"
- " \"account\" : \"account\", (string) DEPRECATED. The associated account, or \"\" for the default account\n"
+ " \"label\" : \"label\", (string) The associated label, or \"\" for the default label\n"
+ " \"account\" : \"account\", (string) DEPRECATED. This field will be removed in V0.18. To see this deprecated field, start bitcoind with -deprecatedrpc=accounts. The associated account, or \"\" for the default account\n"
" \"scriptPubKey\" : \"key\", (string) the script key\n"
- " \"amount\" : x.xxx, (numeric) the transaction amount in " + CURRENCY_UNIT + "\n"
+ " \"amount\" : x.xxx, (numeric) the transaction output amount in " + CURRENCY_UNIT + "\n"
" \"confirmations\" : n, (numeric) The number of confirmations\n"
" \"redeemScript\" : n (string) The redeemScript if scriptPubKey is P2SH\n"
" \"spendable\" : xxx, (bool) Whether we have the private keys to spend this output\n"
- " \"solvable\" : xxx (bool) Whether we know how to spend this output, ignoring the lack of keys\n"
+ " \"solvable\" : xxx, (bool) Whether we know how to spend this output, ignoring the lack of keys\n"
+ " \"safe\" : xxx (bool) Whether this output is considered safe to spend. Unconfirmed transactions\n"
+ " from outside keys and unconfirmed replacement transactions are considered unsafe\n"
+ " and are not eligible for spending by fundrawtransaction and sendtoaddress.\n"
" }\n"
" ,...\n"
"]\n"
@@ -2359,87 +3344,253 @@ UniValue listunspent(const UniValue& params, bool fHelp)
+ HelpExampleCli("listunspent", "")
+ HelpExampleCli("listunspent", "6 9999999 \"[\\\"1PGFqEzfmQch1gKD3ra4k18PNj3tTUUSqg\\\",\\\"1LtvqCaApEdUGFkpKMM4MstjcaL4dKg8SP\\\"]\"")
+ HelpExampleRpc("listunspent", "6, 9999999 \"[\\\"1PGFqEzfmQch1gKD3ra4k18PNj3tTUUSqg\\\",\\\"1LtvqCaApEdUGFkpKMM4MstjcaL4dKg8SP\\\"]\"")
+ + HelpExampleCli("listunspent", "6 9999999 '[]' true '{ \"minimumAmount\": 0.005 }'")
+ + HelpExampleRpc("listunspent", "6, 9999999, [] , true, { \"minimumAmount\": 0.005 } ")
);
- RPCTypeCheck(params, boost::assign::list_of(UniValue::VNUM)(UniValue::VNUM)(UniValue::VARR));
-
int nMinDepth = 1;
- if (params.size() > 0)
- nMinDepth = params[0].get_int();
+ if (!request.params[0].isNull()) {
+ RPCTypeCheckArgument(request.params[0], UniValue::VNUM);
+ nMinDepth = request.params[0].get_int();
+ }
int nMaxDepth = 9999999;
- if (params.size() > 1)
- nMaxDepth = params[1].get_int();
+ if (!request.params[1].isNull()) {
+ RPCTypeCheckArgument(request.params[1], UniValue::VNUM);
+ nMaxDepth = request.params[1].get_int();
+ }
- set<CBitcoinAddress> setAddress;
- if (params.size() > 2) {
- UniValue inputs = params[2].get_array();
+ std::set<CTxDestination> destinations;
+ if (!request.params[2].isNull()) {
+ RPCTypeCheckArgument(request.params[2], UniValue::VARR);
+ UniValue inputs = request.params[2].get_array();
for (unsigned int idx = 0; idx < inputs.size(); idx++) {
const UniValue& input = inputs[idx];
- CBitcoinAddress address(input.get_str());
- if (!address.IsValid())
- throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, string("Invalid Bitcoin address: ")+input.get_str());
- if (setAddress.count(address))
- throw JSONRPCError(RPC_INVALID_PARAMETER, string("Invalid parameter, duplicated address: ")+input.get_str());
- setAddress.insert(address);
+ CTxDestination dest = DecodeDestination(input.get_str());
+ if (!IsValidDestination(dest)) {
+ throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, std::string("Invalid Bitcoin address: ") + input.get_str());
+ }
+ if (!destinations.insert(dest).second) {
+ throw JSONRPCError(RPC_INVALID_PARAMETER, std::string("Invalid parameter, duplicated address: ") + input.get_str());
+ }
}
}
+ bool include_unsafe = true;
+ if (!request.params[3].isNull()) {
+ RPCTypeCheckArgument(request.params[3], UniValue::VBOOL);
+ include_unsafe = request.params[3].get_bool();
+ }
+
+ CAmount nMinimumAmount = 0;
+ CAmount nMaximumAmount = MAX_MONEY;
+ CAmount nMinimumSumAmount = MAX_MONEY;
+ uint64_t nMaximumCount = 0;
+
+ if (!request.params[4].isNull()) {
+ const UniValue& options = request.params[4].get_obj();
+
+ if (options.exists("minimumAmount"))
+ nMinimumAmount = AmountFromValue(options["minimumAmount"]);
+
+ if (options.exists("maximumAmount"))
+ nMaximumAmount = AmountFromValue(options["maximumAmount"]);
+
+ if (options.exists("minimumSumAmount"))
+ nMinimumSumAmount = AmountFromValue(options["minimumSumAmount"]);
+
+ if (options.exists("maximumCount"))
+ nMaximumCount = options["maximumCount"].get_int64();
+ }
+
+ // Make sure the results are valid at least up to the most recent block
+ // the user could have gotten from another RPC command prior to now
+ pwallet->BlockUntilSyncedToCurrentChain();
+
UniValue results(UniValue::VARR);
- vector<COutput> vecOutputs;
- assert(pwalletMain != NULL);
- LOCK2(cs_main, pwalletMain->cs_wallet);
- pwalletMain->AvailableCoins(vecOutputs, false, NULL, true);
- BOOST_FOREACH(const COutput& out, vecOutputs) {
- if (out.nDepth < nMinDepth || out.nDepth > nMaxDepth)
- continue;
+ std::vector<COutput> vecOutputs;
+ {
+ LOCK2(cs_main, pwallet->cs_wallet);
+ pwallet->AvailableCoins(vecOutputs, !include_unsafe, nullptr, nMinimumAmount, nMaximumAmount, nMinimumSumAmount, nMaximumCount, nMinDepth, nMaxDepth);
+ }
+
+ LOCK(pwallet->cs_wallet);
+ for (const COutput& out : vecOutputs) {
CTxDestination address;
- const CScript& scriptPubKey = out.tx->vout[out.i].scriptPubKey;
+ const CScript& scriptPubKey = out.tx->tx->vout[out.i].scriptPubKey;
bool fValidAddress = ExtractDestination(scriptPubKey, address);
- if (setAddress.size() && (!fValidAddress || !setAddress.count(address)))
+ if (destinations.size() && (!fValidAddress || !destinations.count(address)))
continue;
UniValue entry(UniValue::VOBJ);
- entry.push_back(Pair("txid", out.tx->GetHash().GetHex()));
- entry.push_back(Pair("vout", out.i));
+ entry.pushKV("txid", out.tx->GetHash().GetHex());
+ entry.pushKV("vout", out.i);
if (fValidAddress) {
- entry.push_back(Pair("address", CBitcoinAddress(address).ToString()));
+ entry.pushKV("address", EncodeDestination(address));
- if (pwalletMain->mapAddressBook.count(address))
- entry.push_back(Pair("account", pwalletMain->mapAddressBook[address].name));
+ auto i = pwallet->mapAddressBook.find(address);
+ if (i != pwallet->mapAddressBook.end()) {
+ entry.pushKV("label", i->second.name);
+ if (IsDeprecatedRPCEnabled("accounts")) {
+ entry.pushKV("account", i->second.name);
+ }
+ }
if (scriptPubKey.IsPayToScriptHash()) {
const CScriptID& hash = boost::get<CScriptID>(address);
CScript redeemScript;
- if (pwalletMain->GetCScript(hash, redeemScript))
- entry.push_back(Pair("redeemScript", HexStr(redeemScript.begin(), redeemScript.end())));
+ if (pwallet->GetCScript(hash, redeemScript)) {
+ entry.pushKV("redeemScript", HexStr(redeemScript.begin(), redeemScript.end()));
+ }
}
}
- entry.push_back(Pair("scriptPubKey", HexStr(scriptPubKey.begin(), scriptPubKey.end())));
- entry.push_back(Pair("amount", ValueFromAmount(out.tx->vout[out.i].nValue)));
- entry.push_back(Pair("confirmations", out.nDepth));
- entry.push_back(Pair("spendable", out.fSpendable));
- entry.push_back(Pair("solvable", out.fSolvable));
+ entry.pushKV("scriptPubKey", HexStr(scriptPubKey.begin(), scriptPubKey.end()));
+ entry.pushKV("amount", ValueFromAmount(out.tx->tx->vout[out.i].nValue));
+ entry.pushKV("confirmations", out.nDepth);
+ entry.pushKV("spendable", out.fSpendable);
+ entry.pushKV("solvable", out.fSolvable);
+ entry.pushKV("safe", out.fSafe);
results.push_back(entry);
}
return results;
}
-UniValue fundrawtransaction(const UniValue& params, bool fHelp)
+void FundTransaction(CWallet* const pwallet, CMutableTransaction& tx, CAmount& fee_out, int& change_position, UniValue options)
+{
+ // Make sure the results are valid at least up to the most recent block
+ // the user could have gotten from another RPC command prior to now
+ pwallet->BlockUntilSyncedToCurrentChain();
+
+ CCoinControl coinControl;
+ change_position = -1;
+ bool lockUnspents = false;
+ UniValue subtractFeeFromOutputs;
+ std::set<int> setSubtractFeeFromOutputs;
+
+ if (!options.isNull()) {
+ if (options.type() == UniValue::VBOOL) {
+ // backward compatibility bool only fallback
+ coinControl.fAllowWatchOnly = options.get_bool();
+ }
+ else {
+ RPCTypeCheckArgument(options, UniValue::VOBJ);
+ RPCTypeCheckObj(options,
+ {
+ {"changeAddress", UniValueType(UniValue::VSTR)},
+ {"changePosition", UniValueType(UniValue::VNUM)},
+ {"change_type", UniValueType(UniValue::VSTR)},
+ {"includeWatching", UniValueType(UniValue::VBOOL)},
+ {"lockUnspents", UniValueType(UniValue::VBOOL)},
+ {"feeRate", UniValueType()}, // will be checked below
+ {"subtractFeeFromOutputs", UniValueType(UniValue::VARR)},
+ {"replaceable", UniValueType(UniValue::VBOOL)},
+ {"conf_target", UniValueType(UniValue::VNUM)},
+ {"estimate_mode", UniValueType(UniValue::VSTR)},
+ },
+ true, true);
+
+ if (options.exists("changeAddress")) {
+ CTxDestination dest = DecodeDestination(options["changeAddress"].get_str());
+
+ if (!IsValidDestination(dest)) {
+ throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "changeAddress must be a valid bitcoin address");
+ }
+
+ coinControl.destChange = dest;
+ }
+
+ if (options.exists("changePosition"))
+ change_position = options["changePosition"].get_int();
+
+ if (options.exists("change_type")) {
+ if (options.exists("changeAddress")) {
+ throw JSONRPCError(RPC_INVALID_PARAMETER, "Cannot specify both changeAddress and address_type options");
+ }
+ coinControl.m_change_type = pwallet->m_default_change_type;
+ if (!ParseOutputType(options["change_type"].get_str(), *coinControl.m_change_type)) {
+ throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, strprintf("Unknown change type '%s'", options["change_type"].get_str()));
+ }
+ }
+
+ if (options.exists("includeWatching"))
+ coinControl.fAllowWatchOnly = options["includeWatching"].get_bool();
+
+ if (options.exists("lockUnspents"))
+ lockUnspents = options["lockUnspents"].get_bool();
+
+ if (options.exists("feeRate"))
+ {
+ coinControl.m_feerate = CFeeRate(AmountFromValue(options["feeRate"]));
+ coinControl.fOverrideFeeRate = true;
+ }
+
+ if (options.exists("subtractFeeFromOutputs"))
+ subtractFeeFromOutputs = options["subtractFeeFromOutputs"].get_array();
+
+ if (options.exists("replaceable")) {
+ coinControl.m_signal_bip125_rbf = options["replaceable"].get_bool();
+ }
+ if (options.exists("conf_target")) {
+ if (options.exists("feeRate")) {
+ throw JSONRPCError(RPC_INVALID_PARAMETER, "Cannot specify both conf_target and feeRate");
+ }
+ coinControl.m_confirm_target = ParseConfirmTarget(options["conf_target"]);
+ }
+ if (options.exists("estimate_mode")) {
+ if (options.exists("feeRate")) {
+ throw JSONRPCError(RPC_INVALID_PARAMETER, "Cannot specify both estimate_mode and feeRate");
+ }
+ if (!FeeModeFromString(options["estimate_mode"].get_str(), coinControl.m_fee_mode)) {
+ throw JSONRPCError(RPC_INVALID_PARAMETER, "Invalid estimate_mode parameter");
+ }
+ }
+ }
+ }
+
+ if (tx.vout.size() == 0)
+ throw JSONRPCError(RPC_INVALID_PARAMETER, "TX must have at least one output");
+
+ if (change_position != -1 && (change_position < 0 || (unsigned int)change_position > tx.vout.size()))
+ throw JSONRPCError(RPC_INVALID_PARAMETER, "changePosition out of bounds");
+
+ for (unsigned int idx = 0; idx < subtractFeeFromOutputs.size(); idx++) {
+ int pos = subtractFeeFromOutputs[idx].get_int();
+ if (setSubtractFeeFromOutputs.count(pos))
+ throw JSONRPCError(RPC_INVALID_PARAMETER, strprintf("Invalid parameter, duplicated position: %d", pos));
+ if (pos < 0)
+ throw JSONRPCError(RPC_INVALID_PARAMETER, strprintf("Invalid parameter, negative position: %d", pos));
+ if (pos >= int(tx.vout.size()))
+ throw JSONRPCError(RPC_INVALID_PARAMETER, strprintf("Invalid parameter, position too large: %d", pos));
+ setSubtractFeeFromOutputs.insert(pos);
+ }
+
+ std::string strFailReason;
+
+ if (!pwallet->FundTransaction(tx, fee_out, change_position, strFailReason, lockUnspents, setSubtractFeeFromOutputs, coinControl)) {
+ throw JSONRPCError(RPC_WALLET_ERROR, strFailReason);
+ }
+}
+
+static UniValue fundrawtransaction(const JSONRPCRequest& request)
{
- if (!EnsureWalletIsAvailable(fHelp))
+ std::shared_ptr<CWallet> const wallet = GetWalletForJSONRPCRequest(request);
+ CWallet* const pwallet = wallet.get();
+
+ if (!EnsureWalletIsAvailable(pwallet, request.fHelp)) {
return NullUniValue;
+ }
- if (fHelp || params.size() < 1 || params.size() > 2)
- throw runtime_error(
- "fundrawtransaction \"hexstring\" ( options )\n"
+ if (request.fHelp || request.params.size() < 1 || request.params.size() > 3)
+ throw std::runtime_error(
+ "fundrawtransaction \"hexstring\" ( options iswitness )\n"
"\nAdd inputs to a transaction until it has enough in value to meet its out value.\n"
- "This will not modify existing inputs, and will add one change output to the outputs.\n"
+ "This will not modify existing inputs, and will add at most one change output to the outputs.\n"
+ "No existing outputs will be modified unless \"subtractFeeFromOutputs\" is specified.\n"
"Note that inputs which were signed may need to be resigned after completion since in/outputs have been added.\n"
"The inputs added will not be signed, use signrawtransaction for that.\n"
"Note that all existing inputs must have their previous output transaction be in the wallet.\n"
@@ -2449,22 +3600,38 @@ UniValue fundrawtransaction(const UniValue& params, bool fHelp)
"Only pay-to-pubkey, multisig, and P2SH versions thereof are currently supported for watch-only\n"
"\nArguments:\n"
"1. \"hexstring\" (string, required) The hex string of the raw transaction\n"
- "2. options (object, optional)\n"
+ "2. options (object, optional)\n"
" {\n"
- " \"changeAddress\" (string, optional, default pool address) The bitcoin address to receive the change\n"
- " \"changePosition\" (numeric, optional, default random) The index of the change output\n"
- " \"includeWatching\" (boolean, optional, default false) Also select inputs which are watch only\n"
- " \"lockUnspents\" (boolean, optional, default false) Lock selected unspent outputs\n"
- " \"feeRate\" (numeric, optional, default not set: makes wallet determine the fee) Set a specific feerate (" + CURRENCY_UNIT + " per KB)\n"
+ " \"changeAddress\" (string, optional, default pool address) The bitcoin address to receive the change\n"
+ " \"changePosition\" (numeric, optional, default random) The index of the change output\n"
+ " \"change_type\" (string, optional) The output type to use. Only valid if changeAddress is not specified. Options are \"legacy\", \"p2sh-segwit\", and \"bech32\". Default is set by -changetype.\n"
+ " \"includeWatching\" (boolean, optional, default false) Also select inputs which are watch only\n"
+ " \"lockUnspents\" (boolean, optional, default false) Lock selected unspent outputs\n"
+ " \"feeRate\" (numeric, optional, default not set: makes wallet determine the fee) Set a specific fee rate in " + CURRENCY_UNIT + "/kB\n"
+ " \"subtractFeeFromOutputs\" (array, optional) A json array of integers.\n"
+ " The fee will be equally deducted from the amount of each specified output.\n"
+ " The outputs are specified by their zero-based index, before any change output is added.\n"
+ " Those recipients will receive less bitcoins than you enter in their corresponding amount field.\n"
+ " If no outputs are specified here, the sender pays the fee.\n"
+ " [vout_index,...]\n"
+ " \"replaceable\" (boolean, optional) Marks this transaction as BIP125 replaceable.\n"
+ " Allows this transaction to be replaced by a transaction with higher fees\n"
+ " \"conf_target\" (numeric, optional) Confirmation target (in blocks)\n"
+ " \"estimate_mode\" (string, optional, default=UNSET) The fee estimate mode, must be one of:\n"
+ " \"UNSET\"\n"
+ " \"ECONOMICAL\"\n"
+ " \"CONSERVATIVE\"\n"
" }\n"
" for backward compatibility: passing in a true instead of an object will result in {\"includeWatching\":true}\n"
+ "3. iswitness (boolean, optional) Whether the transaction hex is a serialized witness transaction \n"
+ " If iswitness is not present, heuristic tests will be used in decoding\n"
+
"\nResult:\n"
"{\n"
" \"hex\": \"value\", (string) The resulting raw transaction (hex-encoded string)\n"
" \"fee\": n, (numeric) Fee in " + CURRENCY_UNIT + " the resulting transaction pays\n"
" \"changepos\": n (numeric) The position of the added change output, or -1\n"
"}\n"
- "\"hex\" \n"
"\nExamples:\n"
"\nCreate a transaction with no inputs\n"
+ HelpExampleCli("createrawtransaction", "\"[]\" \"{\\\"myaddress\\\":0.01}\"") +
@@ -2476,149 +3643,1207 @@ UniValue fundrawtransaction(const UniValue& params, bool fHelp)
+ HelpExampleCli("sendrawtransaction", "\"signedtransactionhex\"")
);
- RPCTypeCheck(params, boost::assign::list_of(UniValue::VSTR));
+ RPCTypeCheck(request.params, {UniValue::VSTR, UniValueType(), UniValue::VBOOL});
- CTxDestination changeAddress = CNoDestination();
- int changePosition = -1;
- bool includeWatching = false;
- bool lockUnspents = false;
- CFeeRate feeRate = CFeeRate(0);
- bool overrideEstimatedFeerate = false;
+ // parse hex string from parameter
+ CMutableTransaction tx;
+ bool try_witness = request.params[2].isNull() ? true : request.params[2].get_bool();
+ bool try_no_witness = request.params[2].isNull() ? true : !request.params[2].get_bool();
+ if (!DecodeHexTx(tx, request.params[0].get_str(), try_no_witness, try_witness)) {
+ throw JSONRPCError(RPC_DESERIALIZATION_ERROR, "TX decode failed");
+ }
- if (params.size() > 1) {
- if (params[1].type() == UniValue::VBOOL) {
- // backward compatibility bool only fallback
- includeWatching = params[1].get_bool();
- }
- else {
- RPCTypeCheck(params, boost::assign::list_of(UniValue::VSTR)(UniValue::VOBJ));
+ CAmount fee;
+ int change_position;
+ FundTransaction(pwallet, tx, fee, change_position, request.params[1]);
+
+ UniValue result(UniValue::VOBJ);
+ result.pushKV("hex", EncodeHexTx(tx));
+ result.pushKV("fee", ValueFromAmount(fee));
+ result.pushKV("changepos", change_position);
+
+ return result;
+}
+
+UniValue signrawtransactionwithwallet(const JSONRPCRequest& request)
+{
+ std::shared_ptr<CWallet> const wallet = GetWalletForJSONRPCRequest(request);
+ CWallet* const pwallet = wallet.get();
+
+ if (!EnsureWalletIsAvailable(pwallet, request.fHelp)) {
+ return NullUniValue;
+ }
+
+ if (request.fHelp || request.params.size() < 1 || request.params.size() > 3)
+ throw std::runtime_error(
+ "signrawtransactionwithwallet \"hexstring\" ( [{\"txid\":\"id\",\"vout\":n,\"scriptPubKey\":\"hex\",\"redeemScript\":\"hex\"},...] sighashtype )\n"
+ "\nSign inputs for raw transaction (serialized, hex-encoded).\n"
+ "The second optional argument (may be null) is an array of previous transaction outputs that\n"
+ "this transaction depends on but may not yet be in the block chain.\n"
+ + HelpRequiringPassphrase(pwallet) + "\n"
+
+ "\nArguments:\n"
+ "1. \"hexstring\" (string, required) The transaction hex string\n"
+ "2. \"prevtxs\" (string, optional) An json array of previous dependent transaction outputs\n"
+ " [ (json array of json objects, or 'null' if none provided)\n"
+ " {\n"
+ " \"txid\":\"id\", (string, required) The transaction id\n"
+ " \"vout\":n, (numeric, required) The output number\n"
+ " \"scriptPubKey\": \"hex\", (string, required) script key\n"
+ " \"redeemScript\": \"hex\", (string, required for P2SH or P2WSH) redeem script\n"
+ " \"amount\": value (numeric, required) The amount spent\n"
+ " }\n"
+ " ,...\n"
+ " ]\n"
+ "3. \"sighashtype\" (string, optional, default=ALL) The signature hash type. Must be one of\n"
+ " \"ALL\"\n"
+ " \"NONE\"\n"
+ " \"SINGLE\"\n"
+ " \"ALL|ANYONECANPAY\"\n"
+ " \"NONE|ANYONECANPAY\"\n"
+ " \"SINGLE|ANYONECANPAY\"\n"
+
+ "\nResult:\n"
+ "{\n"
+ " \"hex\" : \"value\", (string) The hex-encoded raw transaction with signature(s)\n"
+ " \"complete\" : true|false, (boolean) If the transaction has a complete set of signatures\n"
+ " \"errors\" : [ (json array of objects) Script verification errors (if there are any)\n"
+ " {\n"
+ " \"txid\" : \"hash\", (string) The hash of the referenced, previous transaction\n"
+ " \"vout\" : n, (numeric) The index of the output to spent and used as input\n"
+ " \"scriptSig\" : \"hex\", (string) The hex-encoded signature script\n"
+ " \"sequence\" : n, (numeric) Script sequence number\n"
+ " \"error\" : \"text\" (string) Verification or signing error related to the input\n"
+ " }\n"
+ " ,...\n"
+ " ]\n"
+ "}\n"
+
+ "\nExamples:\n"
+ + HelpExampleCli("signrawtransactionwithwallet", "\"myhex\"")
+ + HelpExampleRpc("signrawtransactionwithwallet", "\"myhex\"")
+ );
- UniValue options = params[1];
+ RPCTypeCheck(request.params, {UniValue::VSTR, UniValue::VARR, UniValue::VSTR}, true);
+ CMutableTransaction mtx;
+ if (!DecodeHexTx(mtx, request.params[0].get_str(), true)) {
+ throw JSONRPCError(RPC_DESERIALIZATION_ERROR, "TX decode failed");
+ }
+
+ // Sign the transaction
+ LOCK2(cs_main, pwallet->cs_wallet);
+ return SignTransaction(mtx, request.params[1], pwallet, false, request.params[2]);
+}
+
+static UniValue bumpfee(const JSONRPCRequest& request)
+{
+ std::shared_ptr<CWallet> const wallet = GetWalletForJSONRPCRequest(request);
+ CWallet* const pwallet = wallet.get();
+
+
+ if (!EnsureWalletIsAvailable(pwallet, request.fHelp))
+ return NullUniValue;
+
+ if (request.fHelp || request.params.size() < 1 || request.params.size() > 2) {
+ throw std::runtime_error(
+ "bumpfee \"txid\" ( options ) \n"
+ "\nBumps the fee of an opt-in-RBF transaction T, replacing it with a new transaction B.\n"
+ "An opt-in RBF transaction with the given txid must be in the wallet.\n"
+ "The command will pay the additional fee by decreasing (or perhaps removing) its change output.\n"
+ "If the change output is not big enough to cover the increased fee, the command will currently fail\n"
+ "instead of adding new inputs to compensate. (A future implementation could improve this.)\n"
+ "The command will fail if the wallet or mempool contains a transaction that spends one of T's outputs.\n"
+ "By default, the new fee will be calculated automatically using estimatesmartfee.\n"
+ "The user can specify a confirmation target for estimatesmartfee.\n"
+ "Alternatively, the user can specify totalFee, or use RPC settxfee to set a higher fee rate.\n"
+ "At a minimum, the new fee rate must be high enough to pay an additional new relay fee (incrementalfee\n"
+ "returned by getnetworkinfo) to enter the node's mempool.\n"
+ "\nArguments:\n"
+ "1. txid (string, required) The txid to be bumped\n"
+ "2. options (object, optional)\n"
+ " {\n"
+ " \"confTarget\" (numeric, optional) Confirmation target (in blocks)\n"
+ " \"totalFee\" (numeric, optional) Total fee (NOT feerate) to pay, in satoshis.\n"
+ " In rare cases, the actual fee paid might be slightly higher than the specified\n"
+ " totalFee if the tx change output has to be removed because it is too close to\n"
+ " the dust threshold.\n"
+ " \"replaceable\" (boolean, optional, default true) Whether the new transaction should still be\n"
+ " marked bip-125 replaceable. If true, the sequence numbers in the transaction will\n"
+ " be left unchanged from the original. If false, any input sequence numbers in the\n"
+ " original transaction that were less than 0xfffffffe will be increased to 0xfffffffe\n"
+ " so the new transaction will not be explicitly bip-125 replaceable (though it may\n"
+ " still be replaceable in practice, for example if it has unconfirmed ancestors which\n"
+ " are replaceable).\n"
+ " \"estimate_mode\" (string, optional, default=UNSET) The fee estimate mode, must be one of:\n"
+ " \"UNSET\"\n"
+ " \"ECONOMICAL\"\n"
+ " \"CONSERVATIVE\"\n"
+ " }\n"
+ "\nResult:\n"
+ "{\n"
+ " \"txid\": \"value\", (string) The id of the new transaction\n"
+ " \"origfee\": n, (numeric) Fee of the replaced transaction\n"
+ " \"fee\": n, (numeric) Fee of the new transaction\n"
+ " \"errors\": [ str... ] (json array of strings) Errors encountered during processing (may be empty)\n"
+ "}\n"
+ "\nExamples:\n"
+ "\nBump the fee, get the new transaction\'s txid\n" +
+ HelpExampleCli("bumpfee", "<txid>"));
+ }
+
+ RPCTypeCheck(request.params, {UniValue::VSTR, UniValue::VOBJ});
+ uint256 hash;
+ hash.SetHex(request.params[0].get_str());
+
+ // optional parameters
+ CAmount totalFee = 0;
+ CCoinControl coin_control;
+ coin_control.m_signal_bip125_rbf = true;
+ if (!request.params[1].isNull()) {
+ UniValue options = request.params[1];
RPCTypeCheckObj(options,
{
- {"changeAddress", UniValueType(UniValue::VSTR)},
- {"changePosition", UniValueType(UniValue::VNUM)},
- {"includeWatching", UniValueType(UniValue::VBOOL)},
- {"lockUnspents", UniValueType(UniValue::VBOOL)},
- {"feeRate", UniValueType()}, // will be checked below
+ {"confTarget", UniValueType(UniValue::VNUM)},
+ {"totalFee", UniValueType(UniValue::VNUM)},
+ {"replaceable", UniValueType(UniValue::VBOOL)},
+ {"estimate_mode", UniValueType(UniValue::VSTR)},
},
true, true);
- if (options.exists("changeAddress")) {
- CBitcoinAddress address(options["changeAddress"].get_str());
+ if (options.exists("confTarget") && options.exists("totalFee")) {
+ throw JSONRPCError(RPC_INVALID_PARAMETER, "confTarget and totalFee options should not both be set. Please provide either a confirmation target for fee estimation or an explicit total fee for the transaction.");
+ } else if (options.exists("confTarget")) { // TODO: alias this to conf_target
+ coin_control.m_confirm_target = ParseConfirmTarget(options["confTarget"]);
+ } else if (options.exists("totalFee")) {
+ totalFee = options["totalFee"].get_int64();
+ if (totalFee <= 0) {
+ throw JSONRPCError(RPC_INVALID_PARAMETER, strprintf("Invalid totalFee %s (must be greater than 0)", FormatMoney(totalFee)));
+ }
+ }
- if (!address.IsValid())
- throw JSONRPCError(RPC_INVALID_PARAMETER, "changeAddress must be a valid bitcoin address");
+ if (options.exists("replaceable")) {
+ coin_control.m_signal_bip125_rbf = options["replaceable"].get_bool();
+ }
+ if (options.exists("estimate_mode")) {
+ if (!FeeModeFromString(options["estimate_mode"].get_str(), coin_control.m_fee_mode)) {
+ throw JSONRPCError(RPC_INVALID_PARAMETER, "Invalid estimate_mode parameter");
+ }
+ }
+ }
- changeAddress = address.Get();
+ // Make sure the results are valid at least up to the most recent block
+ // the user could have gotten from another RPC command prior to now
+ pwallet->BlockUntilSyncedToCurrentChain();
+
+ LOCK2(cs_main, pwallet->cs_wallet);
+ EnsureWalletIsUnlocked(pwallet);
+
+
+ std::vector<std::string> errors;
+ CAmount old_fee;
+ CAmount new_fee;
+ CMutableTransaction mtx;
+ feebumper::Result res = feebumper::CreateTransaction(pwallet, hash, coin_control, totalFee, errors, old_fee, new_fee, mtx);
+ if (res != feebumper::Result::OK) {
+ switch(res) {
+ case feebumper::Result::INVALID_ADDRESS_OR_KEY:
+ throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, errors[0]);
+ break;
+ case feebumper::Result::INVALID_REQUEST:
+ throw JSONRPCError(RPC_INVALID_REQUEST, errors[0]);
+ break;
+ case feebumper::Result::INVALID_PARAMETER:
+ throw JSONRPCError(RPC_INVALID_PARAMETER, errors[0]);
+ break;
+ case feebumper::Result::WALLET_ERROR:
+ throw JSONRPCError(RPC_WALLET_ERROR, errors[0]);
+ break;
+ default:
+ throw JSONRPCError(RPC_MISC_ERROR, errors[0]);
+ break;
}
+ }
- if (options.exists("changePosition"))
- changePosition = options["changePosition"].get_int();
+ // sign bumped transaction
+ if (!feebumper::SignTransaction(pwallet, mtx)) {
+ throw JSONRPCError(RPC_WALLET_ERROR, "Can't sign transaction.");
+ }
+ // commit the bumped transaction
+ uint256 txid;
+ if (feebumper::CommitTransaction(pwallet, hash, std::move(mtx), errors, txid) != feebumper::Result::OK) {
+ throw JSONRPCError(RPC_WALLET_ERROR, errors[0]);
+ }
+ UniValue result(UniValue::VOBJ);
+ result.pushKV("txid", txid.GetHex());
+ result.pushKV("origfee", ValueFromAmount(old_fee));
+ result.pushKV("fee", ValueFromAmount(new_fee));
+ UniValue result_errors(UniValue::VARR);
+ for (const std::string& error : errors) {
+ result_errors.push_back(error);
+ }
+ result.pushKV("errors", result_errors);
- if (options.exists("includeWatching"))
- includeWatching = options["includeWatching"].get_bool();
+ return result;
+}
- if (options.exists("lockUnspents"))
- lockUnspents = options["lockUnspents"].get_bool();
+UniValue generate(const JSONRPCRequest& request)
+{
+ std::shared_ptr<CWallet> const wallet = GetWalletForJSONRPCRequest(request);
+ CWallet* const pwallet = wallet.get();
- if (options.exists("feeRate"))
- {
- feeRate = CFeeRate(AmountFromValue(options["feeRate"]));
- overrideEstimatedFeerate = true;
+
+ if (!EnsureWalletIsAvailable(pwallet, request.fHelp)) {
+ return NullUniValue;
+ }
+
+ if (request.fHelp || request.params.size() < 1 || request.params.size() > 2) {
+ throw std::runtime_error(
+ "generate nblocks ( maxtries )\n"
+ "\nMine up to nblocks blocks immediately (before the RPC call returns) to an address in the wallet.\n"
+ "\nArguments:\n"
+ "1. nblocks (numeric, required) How many blocks are generated immediately.\n"
+ "2. maxtries (numeric, optional) How many iterations to try (default = 1000000).\n"
+ "\nResult:\n"
+ "[ blockhashes ] (array) hashes of blocks generated\n"
+ "\nExamples:\n"
+ "\nGenerate 11 blocks\n"
+ + HelpExampleCli("generate", "11")
+ );
+ }
+
+ int num_generate = request.params[0].get_int();
+ uint64_t max_tries = 1000000;
+ if (!request.params[1].isNull()) {
+ max_tries = request.params[1].get_int();
+ }
+
+ std::shared_ptr<CReserveScript> coinbase_script;
+ pwallet->GetScriptForMining(coinbase_script);
+
+ // If the keypool is exhausted, no script is returned at all. Catch this.
+ if (!coinbase_script) {
+ throw JSONRPCError(RPC_WALLET_KEYPOOL_RAN_OUT, "Error: Keypool ran out, please call keypoolrefill first");
+ }
+
+ //throw an error if no script was provided
+ if (coinbase_script->reserveScript.empty()) {
+ throw JSONRPCError(RPC_INTERNAL_ERROR, "No coinbase script available");
+ }
+
+ return generateBlocks(coinbase_script, num_generate, max_tries, true);
+}
+
+UniValue rescanblockchain(const JSONRPCRequest& request)
+{
+ std::shared_ptr<CWallet> const wallet = GetWalletForJSONRPCRequest(request);
+ CWallet* const pwallet = wallet.get();
+
+ if (!EnsureWalletIsAvailable(pwallet, request.fHelp)) {
+ return NullUniValue;
+ }
+
+ if (request.fHelp || request.params.size() > 2) {
+ throw std::runtime_error(
+ "rescanblockchain (\"start_height\") (\"stop_height\")\n"
+ "\nRescan the local blockchain for wallet related transactions.\n"
+ "\nArguments:\n"
+ "1. \"start_height\" (numeric, optional) block height where the rescan should start\n"
+ "2. \"stop_height\" (numeric, optional) the last block height that should be scanned\n"
+ "\nResult:\n"
+ "{\n"
+ " \"start_height\" (numeric) The block height where the rescan has started. If omitted, rescan started from the genesis block.\n"
+ " \"stop_height\" (numeric) The height of the last rescanned block. If omitted, rescan stopped at the chain tip.\n"
+ "}\n"
+ "\nExamples:\n"
+ + HelpExampleCli("rescanblockchain", "100000 120000")
+ + HelpExampleRpc("rescanblockchain", "100000, 120000")
+ );
+ }
+
+ WalletRescanReserver reserver(pwallet);
+ if (!reserver.reserve()) {
+ throw JSONRPCError(RPC_WALLET_ERROR, "Wallet is currently rescanning. Abort existing rescan or wait.");
+ }
+
+ CBlockIndex *pindexStart = nullptr;
+ CBlockIndex *pindexStop = nullptr;
+ CBlockIndex *pChainTip = nullptr;
+ {
+ LOCK(cs_main);
+ pindexStart = chainActive.Genesis();
+ pChainTip = chainActive.Tip();
+
+ if (!request.params[0].isNull()) {
+ pindexStart = chainActive[request.params[0].get_int()];
+ if (!pindexStart) {
+ throw JSONRPCError(RPC_INVALID_PARAMETER, "Invalid start_height");
+ }
+ }
+
+ if (!request.params[1].isNull()) {
+ pindexStop = chainActive[request.params[1].get_int()];
+ if (!pindexStop) {
+ throw JSONRPCError(RPC_INVALID_PARAMETER, "Invalid stop_height");
+ }
+ else if (pindexStop->nHeight < pindexStart->nHeight) {
+ throw JSONRPCError(RPC_INVALID_PARAMETER, "stop_height must be greater than start_height");
+ }
}
- }
}
- // parse hex string from parameter
- CTransaction origTx;
- if (!DecodeHexTx(origTx, params[0].get_str(), true))
- throw JSONRPCError(RPC_DESERIALIZATION_ERROR, "TX decode failed");
+ // We can't rescan beyond non-pruned blocks, stop and throw an error
+ if (fPruneMode) {
+ LOCK(cs_main);
+ CBlockIndex *block = pindexStop ? pindexStop : pChainTip;
+ while (block && block->nHeight >= pindexStart->nHeight) {
+ if (!(block->nStatus & BLOCK_HAVE_DATA)) {
+ throw JSONRPCError(RPC_MISC_ERROR, "Can't rescan beyond pruned data. Use RPC call getblockchaininfo to determine your pruned height.");
+ }
+ block = block->pprev;
+ }
+ }
- if (origTx.vout.size() == 0)
- throw JSONRPCError(RPC_INVALID_PARAMETER, "TX must have at least one output");
+ CBlockIndex *stopBlock = pwallet->ScanForWalletTransactions(pindexStart, pindexStop, reserver, true);
+ if (!stopBlock) {
+ if (pwallet->IsAbortingRescan()) {
+ throw JSONRPCError(RPC_MISC_ERROR, "Rescan aborted.");
+ }
+ // if we got a nullptr returned, ScanForWalletTransactions did rescan up to the requested stopindex
+ stopBlock = pindexStop ? pindexStop : pChainTip;
+ }
+ else {
+ throw JSONRPCError(RPC_MISC_ERROR, "Rescan failed. Potentially corrupted data files.");
+ }
+ UniValue response(UniValue::VOBJ);
+ response.pushKV("start_height", pindexStart->nHeight);
+ response.pushKV("stop_height", stopBlock->nHeight);
+ return response;
+}
- if (changePosition != -1 && (changePosition < 0 || (unsigned int)changePosition > origTx.vout.size()))
- throw JSONRPCError(RPC_INVALID_PARAMETER, "changePosition out of bounds");
+class DescribeWalletAddressVisitor : public boost::static_visitor<UniValue>
+{
+public:
+ CWallet * const pwallet;
- CMutableTransaction tx(origTx);
- CAmount nFeeOut;
- string strFailReason;
+ void ProcessSubScript(const CScript& subscript, UniValue& obj, bool include_addresses = false) const
+ {
+ // Always present: script type and redeemscript
+ txnouttype which_type;
+ std::vector<std::vector<unsigned char>> solutions_data;
+ Solver(subscript, which_type, solutions_data);
+ obj.pushKV("script", GetTxnOutputType(which_type));
+ obj.pushKV("hex", HexStr(subscript.begin(), subscript.end()));
+
+ CTxDestination embedded;
+ UniValue a(UniValue::VARR);
+ if (ExtractDestination(subscript, embedded)) {
+ // Only when the script corresponds to an address.
+ UniValue subobj(UniValue::VOBJ);
+ UniValue detail = DescribeAddress(embedded);
+ subobj.pushKVs(detail);
+ UniValue wallet_detail = boost::apply_visitor(*this, embedded);
+ subobj.pushKVs(wallet_detail);
+ subobj.pushKV("address", EncodeDestination(embedded));
+ subobj.pushKV("scriptPubKey", HexStr(subscript.begin(), subscript.end()));
+ // Always report the pubkey at the top level, so that `getnewaddress()['pubkey']` always works.
+ if (subobj.exists("pubkey")) obj.pushKV("pubkey", subobj["pubkey"]);
+ obj.pushKV("embedded", std::move(subobj));
+ if (include_addresses) a.push_back(EncodeDestination(embedded));
+ } else if (which_type == TX_MULTISIG) {
+ // Also report some information on multisig scripts (which do not have a corresponding address).
+ // TODO: abstract out the common functionality between this logic and ExtractDestinations.
+ obj.pushKV("sigsrequired", solutions_data[0][0]);
+ UniValue pubkeys(UniValue::VARR);
+ for (size_t i = 1; i < solutions_data.size() - 1; ++i) {
+ CPubKey key(solutions_data[i].begin(), solutions_data[i].end());
+ if (include_addresses) a.push_back(EncodeDestination(key.GetID()));
+ pubkeys.push_back(HexStr(key.begin(), key.end()));
+ }
+ obj.pushKV("pubkeys", std::move(pubkeys));
+ }
- if(!pwalletMain->FundTransaction(tx, nFeeOut, overrideEstimatedFeerate, feeRate, changePosition, strFailReason, includeWatching, lockUnspents, changeAddress))
- throw JSONRPCError(RPC_INTERNAL_ERROR, strFailReason);
+ // The "addresses" field is confusing because it refers to public keys using their P2PKH address.
+ // For that reason, only add the 'addresses' field when needed for backward compatibility. New applications
+ // can use the 'embedded'->'address' field for P2SH or P2WSH wrapped addresses, and 'pubkeys' for
+ // inspecting multisig participants.
+ if (include_addresses) obj.pushKV("addresses", std::move(a));
+ }
+
+ explicit DescribeWalletAddressVisitor(CWallet* _pwallet) : pwallet(_pwallet) {}
+
+ UniValue operator()(const CNoDestination& dest) const { return UniValue(UniValue::VOBJ); }
+
+ UniValue operator()(const CKeyID& keyID) const
+ {
+ UniValue obj(UniValue::VOBJ);
+ CPubKey vchPubKey;
+ if (pwallet && pwallet->GetPubKey(keyID, vchPubKey)) {
+ obj.pushKV("pubkey", HexStr(vchPubKey));
+ obj.pushKV("iscompressed", vchPubKey.IsCompressed());
+ }
+ return obj;
+ }
+
+ UniValue operator()(const CScriptID& scriptID) const
+ {
+ UniValue obj(UniValue::VOBJ);
+ CScript subscript;
+ if (pwallet && pwallet->GetCScript(scriptID, subscript)) {
+ ProcessSubScript(subscript, obj, IsDeprecatedRPCEnabled("validateaddress"));
+ }
+ return obj;
+ }
+
+ UniValue operator()(const WitnessV0KeyHash& id) const
+ {
+ UniValue obj(UniValue::VOBJ);
+ CPubKey pubkey;
+ if (pwallet && pwallet->GetPubKey(CKeyID(id), pubkey)) {
+ obj.pushKV("pubkey", HexStr(pubkey));
+ }
+ return obj;
+ }
+
+ UniValue operator()(const WitnessV0ScriptHash& id) const
+ {
+ UniValue obj(UniValue::VOBJ);
+ CScript subscript;
+ CRIPEMD160 hasher;
+ uint160 hash;
+ hasher.Write(id.begin(), 32).Finalize(hash.begin());
+ if (pwallet && pwallet->GetCScript(CScriptID(hash), subscript)) {
+ ProcessSubScript(subscript, obj);
+ }
+ return obj;
+ }
+
+ UniValue operator()(const WitnessUnknown& id) const { return UniValue(UniValue::VOBJ); }
+};
+
+static UniValue DescribeWalletAddress(CWallet* pwallet, const CTxDestination& dest)
+{
+ UniValue ret(UniValue::VOBJ);
+ UniValue detail = DescribeAddress(dest);
+ ret.pushKVs(detail);
+ ret.pushKVs(boost::apply_visitor(DescribeWalletAddressVisitor(pwallet), dest));
+ return ret;
+}
+
+/** Convert CAddressBookData to JSON record. */
+static UniValue AddressBookDataToJSON(const CAddressBookData& data, const bool verbose)
+{
+ UniValue ret(UniValue::VOBJ);
+ if (verbose) {
+ ret.pushKV("name", data.name);
+ }
+ ret.pushKV("purpose", data.purpose);
+ return ret;
+}
+
+UniValue getaddressinfo(const JSONRPCRequest& request)
+{
+ std::shared_ptr<CWallet> const wallet = GetWalletForJSONRPCRequest(request);
+ CWallet* const pwallet = wallet.get();
+
+ if (!EnsureWalletIsAvailable(pwallet, request.fHelp)) {
+ return NullUniValue;
+ }
+
+ if (request.fHelp || request.params.size() != 1) {
+ throw std::runtime_error(
+ "getaddressinfo \"address\"\n"
+ "\nReturn information about the given bitcoin address. Some information requires the address\n"
+ "to be in the wallet.\n"
+ "\nArguments:\n"
+ "1. \"address\" (string, required) The bitcoin address to get the information of.\n"
+ "\nResult:\n"
+ "{\n"
+ " \"address\" : \"address\", (string) The bitcoin address validated\n"
+ " \"scriptPubKey\" : \"hex\", (string) The hex encoded scriptPubKey generated by the address\n"
+ " \"ismine\" : true|false, (boolean) If the address is yours or not\n"
+ " \"iswatchonly\" : true|false, (boolean) If the address is watchonly\n"
+ " \"isscript\" : true|false, (boolean) If the key is a script\n"
+ " \"iswitness\" : true|false, (boolean) If the address is a witness address\n"
+ " \"witness_version\" : version (numeric, optional) The version number of the witness program\n"
+ " \"witness_program\" : \"hex\" (string, optional) The hex value of the witness program\n"
+ " \"script\" : \"type\" (string, optional) The output script type. Only if \"isscript\" is true and the redeemscript is known. Possible types: nonstandard, pubkey, pubkeyhash, scripthash, multisig, nulldata, witness_v0_keyhash, witness_v0_scripthash, witness_unknown\n"
+ " \"hex\" : \"hex\", (string, optional) The redeemscript for the p2sh address\n"
+ " \"pubkeys\" (string, optional) Array of pubkeys associated with the known redeemscript (only if \"script\" is \"multisig\")\n"
+ " [\n"
+ " \"pubkey\"\n"
+ " ,...\n"
+ " ]\n"
+ " \"sigsrequired\" : xxxxx (numeric, optional) Number of signatures required to spend multisig output (only if \"script\" is \"multisig\")\n"
+ " \"pubkey\" : \"publickeyhex\", (string, optional) The hex value of the raw public key, for single-key addresses (possibly embedded in P2SH or P2WSH)\n"
+ " \"embedded\" : {...}, (object, optional) Information about the address embedded in P2SH or P2WSH, if relevant and known. It includes all getaddressinfo output fields for the embedded address, excluding metadata (\"timestamp\", \"hdkeypath\", \"hdseedid\") and relation to the wallet (\"ismine\", \"iswatchonly\", \"account\").\n"
+ " \"iscompressed\" : true|false, (boolean) If the address is compressed\n"
+ " \"label\" : \"label\" (string) The label associated with the address, \"\" is the default account\n"
+ " \"account\" : \"account\" (string) DEPRECATED. This field will be removed in V0.18. To see this deprecated field, start bitcoind with -deprecatedrpc=accounts. The account associated with the address, \"\" is the default account\n"
+ " \"timestamp\" : timestamp, (number, optional) The creation time of the key if available in seconds since epoch (Jan 1 1970 GMT)\n"
+ " \"hdkeypath\" : \"keypath\" (string, optional) The HD keypath if the key is HD and available\n"
+ " \"hdseedid\" : \"<hash160>\" (string, optional) The Hash160 of the HD seed\n"
+ " \"hdmasterkeyid\" : \"<hash160>\" (string, optional) alias for hdseedid maintained for backwards compatibility. Will be removed in V0.18.\n"
+ " \"labels\" (object) Array of labels associated with the address.\n"
+ " [\n"
+ " { (json object of label data)\n"
+ " \"name\": \"labelname\" (string) The label\n"
+ " \"purpose\": \"string\" (string) Purpose of address (\"send\" for sending address, \"receive\" for receiving address)\n"
+ " },...\n"
+ " ]\n"
+ "}\n"
+ "\nExamples:\n"
+ + HelpExampleCli("getaddressinfo", "\"1PSSGeFHDnKNxiEyFrD1wcEaHr9hrQDDWc\"")
+ + HelpExampleRpc("getaddressinfo", "\"1PSSGeFHDnKNxiEyFrD1wcEaHr9hrQDDWc\"")
+ );
+ }
+
+ LOCK(pwallet->cs_wallet);
+
+ UniValue ret(UniValue::VOBJ);
+ CTxDestination dest = DecodeDestination(request.params[0].get_str());
+
+ // Make sure the destination is valid
+ if (!IsValidDestination(dest)) {
+ throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Invalid address");
+ }
+
+ std::string currentAddress = EncodeDestination(dest);
+ ret.pushKV("address", currentAddress);
+
+ CScript scriptPubKey = GetScriptForDestination(dest);
+ ret.pushKV("scriptPubKey", HexStr(scriptPubKey.begin(), scriptPubKey.end()));
+
+ isminetype mine = IsMine(*pwallet, dest);
+ ret.pushKV("ismine", bool(mine & ISMINE_SPENDABLE));
+ ret.pushKV("iswatchonly", bool(mine & ISMINE_WATCH_ONLY));
+ UniValue detail = DescribeWalletAddress(pwallet, dest);
+ ret.pushKVs(detail);
+ if (pwallet->mapAddressBook.count(dest)) {
+ ret.pushKV("label", pwallet->mapAddressBook[dest].name);
+ if (IsDeprecatedRPCEnabled("accounts")) {
+ ret.pushKV("account", pwallet->mapAddressBook[dest].name);
+ }
+ }
+ const CKeyMetadata* meta = nullptr;
+ CKeyID key_id = GetKeyForDestination(*pwallet, dest);
+ if (!key_id.IsNull()) {
+ auto it = pwallet->mapKeyMetadata.find(key_id);
+ if (it != pwallet->mapKeyMetadata.end()) {
+ meta = &it->second;
+ }
+ }
+ if (!meta) {
+ auto it = pwallet->m_script_metadata.find(CScriptID(scriptPubKey));
+ if (it != pwallet->m_script_metadata.end()) {
+ meta = &it->second;
+ }
+ }
+ if (meta) {
+ ret.pushKV("timestamp", meta->nCreateTime);
+ if (!meta->hdKeypath.empty()) {
+ ret.pushKV("hdkeypath", meta->hdKeypath);
+ ret.pushKV("hdseedid", meta->hd_seed_id.GetHex());
+ ret.pushKV("hdmasterkeyid", meta->hd_seed_id.GetHex());
+ }
+ }
+
+ // Currently only one label can be associated with an address, return an array
+ // so the API remains stable if we allow multiple labels to be associated with
+ // an address.
+ UniValue labels(UniValue::VARR);
+ std::map<CTxDestination, CAddressBookData>::iterator mi = pwallet->mapAddressBook.find(dest);
+ if (mi != pwallet->mapAddressBook.end()) {
+ labels.push_back(AddressBookDataToJSON(mi->second, true));
+ }
+ ret.pushKV("labels", std::move(labels));
+
+ return ret;
+}
+
+static UniValue getaddressesbylabel(const JSONRPCRequest& request)
+{
+ std::shared_ptr<CWallet> const wallet = GetWalletForJSONRPCRequest(request);
+ CWallet* const pwallet = wallet.get();
+
+ if (!EnsureWalletIsAvailable(pwallet, request.fHelp)) {
+ return NullUniValue;
+ }
+
+ if (request.fHelp || request.params.size() != 1)
+ throw std::runtime_error(
+ "getaddressesbylabel \"label\"\n"
+ "\nReturns the list of addresses assigned the specified label.\n"
+ "\nArguments:\n"
+ "1. \"label\" (string, required) The label.\n"
+ "\nResult:\n"
+ "{ (json object with addresses as keys)\n"
+ " \"address\": { (json object with information about address)\n"
+ " \"purpose\": \"string\" (string) Purpose of address (\"send\" for sending address, \"receive\" for receiving address)\n"
+ " },...\n"
+ "}\n"
+ "\nExamples:\n"
+ + HelpExampleCli("getaddressesbylabel", "\"tabby\"")
+ + HelpExampleRpc("getaddressesbylabel", "\"tabby\"")
+ );
+
+ LOCK(pwallet->cs_wallet);
+
+ std::string label = LabelFromValue(request.params[0]);
+
+ // Find all addresses that have the given label
+ UniValue ret(UniValue::VOBJ);
+ for (const std::pair<const CTxDestination, CAddressBookData>& item : pwallet->mapAddressBook) {
+ if (item.second.name == label) {
+ ret.pushKV(EncodeDestination(item.first), AddressBookDataToJSON(item.second, false));
+ }
+ }
+
+ if (ret.empty()) {
+ throw JSONRPCError(RPC_WALLET_INVALID_LABEL_NAME, std::string("No addresses with label " + label));
+ }
+
+ return ret;
+}
+
+static UniValue listlabels(const JSONRPCRequest& request)
+{
+ std::shared_ptr<CWallet> const wallet = GetWalletForJSONRPCRequest(request);
+ CWallet* const pwallet = wallet.get();
+
+ if (!EnsureWalletIsAvailable(pwallet, request.fHelp)) {
+ return NullUniValue;
+ }
+
+ if (request.fHelp || request.params.size() > 1)
+ throw std::runtime_error(
+ "listlabels ( \"purpose\" )\n"
+ "\nReturns the list of all labels, or labels that are assigned to addresses with a specific purpose.\n"
+ "\nArguments:\n"
+ "1. \"purpose\" (string, optional) Address purpose to list labels for ('send','receive'). An empty string is the same as not providing this argument.\n"
+ "\nResult:\n"
+ "[ (json array of string)\n"
+ " \"label\", (string) Label name\n"
+ " ...\n"
+ "]\n"
+ "\nExamples:\n"
+ "\nList all labels\n"
+ + HelpExampleCli("listlabels", "") +
+ "\nList labels that have receiving addresses\n"
+ + HelpExampleCli("listlabels", "receive") +
+ "\nList labels that have sending addresses\n"
+ + HelpExampleCli("listlabels", "send") +
+ "\nAs json rpc call\n"
+ + HelpExampleRpc("listlabels", "receive")
+ );
+
+ LOCK(pwallet->cs_wallet);
+
+ std::string purpose;
+ if (!request.params[0].isNull()) {
+ purpose = request.params[0].get_str();
+ }
+
+ // Add to a set to sort by label name, then insert into Univalue array
+ std::set<std::string> label_set;
+ for (const std::pair<const CTxDestination, CAddressBookData>& entry : pwallet->mapAddressBook) {
+ if (purpose.empty() || entry.second.purpose == purpose) {
+ label_set.insert(entry.second.name);
+ }
+ }
+
+ UniValue ret(UniValue::VARR);
+ for (const std::string& name : label_set) {
+ ret.push_back(name);
+ }
+
+ return ret;
+}
+
+UniValue sethdseed(const JSONRPCRequest& request)
+{
+ std::shared_ptr<CWallet> const wallet = GetWalletForJSONRPCRequest(request);
+ CWallet* const pwallet = wallet.get();
+
+ if (!EnsureWalletIsAvailable(pwallet, request.fHelp)) {
+ return NullUniValue;
+ }
+
+ if (request.fHelp || request.params.size() > 2) {
+ throw std::runtime_error(
+ "sethdseed ( \"newkeypool\" \"seed\" )\n"
+ "\nSet or generate a new HD wallet seed. Non-HD wallets will not be upgraded to being a HD wallet. Wallets that are already\n"
+ "HD will have a new HD seed set so that new keys added to the keypool will be derived from this new seed.\n"
+ "\nNote that you will need to MAKE A NEW BACKUP of your wallet after setting the HD wallet seed.\n"
+ + HelpRequiringPassphrase(pwallet) +
+ "\nArguments:\n"
+ "1. \"newkeypool\" (boolean, optional, default=true) Whether to flush old unused addresses, including change addresses, from the keypool and regenerate it.\n"
+ " If true, the next address from getnewaddress and change address from getrawchangeaddress will be from this new seed.\n"
+ " If false, addresses (including change addresses if the wallet already had HD Chain Split enabled) from the existing\n"
+ " keypool will be used until it has been depleted.\n"
+ "2. \"seed\" (string, optional) The WIF private key to use as the new HD seed; if not provided a random seed will be used.\n"
+ " The seed value can be retrieved using the dumpwallet command. It is the private key marked hdseed=1\n"
+ "\nExamples:\n"
+ + HelpExampleCli("sethdseed", "")
+ + HelpExampleCli("sethdseed", "false")
+ + HelpExampleCli("sethdseed", "true \"wifkey\"")
+ + HelpExampleRpc("sethdseed", "true, \"wifkey\"")
+ );
+ }
+
+ if (IsInitialBlockDownload()) {
+ throw JSONRPCError(RPC_CLIENT_IN_INITIAL_DOWNLOAD, "Cannot set a new HD seed while still in Initial Block Download");
+ }
+
+ LOCK2(cs_main, pwallet->cs_wallet);
+
+ // Do not do anything to non-HD wallets
+ if (!pwallet->IsHDEnabled()) {
+ throw JSONRPCError(RPC_WALLET_ERROR, "Cannot set a HD seed on a non-HD wallet. Start with -upgradewallet in order to upgrade a non-HD wallet to HD");
+ }
+
+ EnsureWalletIsUnlocked(pwallet);
+
+ bool flush_key_pool = true;
+ if (!request.params[0].isNull()) {
+ flush_key_pool = request.params[0].get_bool();
+ }
+
+ CPubKey master_pub_key;
+ if (request.params[1].isNull()) {
+ master_pub_key = pwallet->GenerateNewSeed();
+ } else {
+ CKey key = DecodeSecret(request.params[1].get_str());
+ if (!key.IsValid()) {
+ throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Invalid private key");
+ }
+
+ if (HaveKey(*pwallet, key)) {
+ throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Already have this key (either as an HD seed or as a loose private key)");
+ }
+
+ master_pub_key = pwallet->DeriveNewSeed(key);
+ }
+
+ pwallet->SetHDSeed(master_pub_key);
+ if (flush_key_pool) pwallet->NewKeyPool();
+
+ return NullUniValue;
+}
+
+bool ParseHDKeypath(std::string keypath_str, std::vector<uint32_t>& keypath)
+{
+ std::stringstream ss(keypath_str);
+ std::string item;
+ bool first = true;
+ while (std::getline(ss, item, '/')) {
+ if (item.compare("m") == 0) {
+ if (first) {
+ first = false;
+ continue;
+ }
+ return false;
+ }
+ // Finds whether it is hardened
+ uint32_t path = 0;
+ size_t pos = item.find("'");
+ if (pos != std::string::npos) {
+ // The hardened tick can only be in the last index of the string
+ if (pos != item.size() - 1) {
+ return false;
+ }
+ path |= 0x80000000;
+ item = item.substr(0, item.size() - 1); // Drop the last character which is the hardened tick
+ }
+
+ // Ensure this is only numbers
+ if (item.find_first_not_of( "0123456789" ) != std::string::npos) {
+ return false;
+ }
+ uint32_t number;
+ if (!ParseUInt32(item, &number)) {
+ return false;
+ }
+ path |= number;
+
+ keypath.push_back(path);
+ first = false;
+ }
+ return true;
+}
+
+void AddKeypathToMap(const CWallet* pwallet, const CKeyID& keyID, std::map<CPubKey, std::vector<uint32_t>>& hd_keypaths)
+{
+ CPubKey vchPubKey;
+ if (!pwallet->GetPubKey(keyID, vchPubKey)) {
+ return;
+ }
+ CKeyMetadata meta;
+ auto it = pwallet->mapKeyMetadata.find(keyID);
+ if (it != pwallet->mapKeyMetadata.end()) {
+ meta = it->second;
+ }
+ std::vector<uint32_t> keypath;
+ if (!meta.hdKeypath.empty()) {
+ if (!ParseHDKeypath(meta.hdKeypath, keypath)) {
+ throw JSONRPCError(RPC_INTERNAL_ERROR, "Internal keypath is broken");
+ }
+ // Get the proper master key id
+ CKey key;
+ pwallet->GetKey(meta.hd_seed_id, key);
+ CExtKey masterKey;
+ masterKey.SetSeed(key.begin(), key.size());
+ // Add to map
+ keypath.insert(keypath.begin(), ReadLE32(masterKey.key.GetPubKey().GetID().begin()));
+ } else { // Single pubkeys get the master fingerprint of themselves
+ keypath.insert(keypath.begin(), ReadLE32(vchPubKey.GetID().begin()));
+ }
+ hd_keypaths.emplace(vchPubKey, keypath);
+}
+
+bool FillPSBT(const CWallet* pwallet, PartiallySignedTransaction& psbtx, const CTransaction* txConst, int sighash_type, bool sign, bool bip32derivs)
+{
+ LOCK(pwallet->cs_wallet);
+ // Get all of the previous transactions
+ bool complete = true;
+ for (unsigned int i = 0; i < txConst->vin.size(); ++i) {
+ const CTxIn& txin = txConst->vin[i];
+ PSBTInput& input = psbtx.inputs.at(i);
+
+ // If we don't know about this input, skip it and let someone else deal with it
+ const uint256& txhash = txin.prevout.hash;
+ const auto it = pwallet->mapWallet.find(txhash);
+ if (it != pwallet->mapWallet.end()) {
+ const CWalletTx& wtx = it->second;
+ CTxOut utxo = wtx.tx->vout[txin.prevout.n];
+ // Update both UTXOs from the wallet.
+ input.non_witness_utxo = wtx.tx;
+ input.witness_utxo = utxo;
+ }
+
+ // Get the Sighash type
+ if (sign && input.sighash_type > 0 && input.sighash_type != sighash_type) {
+ throw JSONRPCError(RPC_DESERIALIZATION_ERROR, "Specified Sighash and sighash in PSBT do not match.");
+ }
+
+ SignatureData sigdata;
+ if (sign) {
+ complete &= SignPSBTInput(*pwallet, *psbtx.tx, input, sigdata, i, sighash_type);
+ } else {
+ complete &= SignPSBTInput(PublicOnlySigningProvider(pwallet), *psbtx.tx, input, sigdata, i, sighash_type);
+ }
+
+ if (it != pwallet->mapWallet.end()) {
+ // Drop the unnecessary UTXO if we added both from the wallet.
+ if (sigdata.witness) {
+ input.non_witness_utxo = nullptr;
+ } else {
+ input.witness_utxo.SetNull();
+ }
+ }
+
+ // Get public key paths
+ if (bip32derivs) {
+ for (const auto& pubkey_it : sigdata.misc_pubkeys) {
+ AddKeypathToMap(pwallet, pubkey_it.first, input.hd_keypaths);
+ }
+ }
+ }
+
+ // Fill in the bip32 keypaths and redeemscripts for the outputs so that hardware wallets can identify change
+ for (unsigned int i = 0; i < txConst->vout.size(); ++i) {
+ const CTxOut& out = txConst->vout.at(i);
+ PSBTOutput& psbt_out = psbtx.outputs.at(i);
+
+ // Fill a SignatureData with output info
+ SignatureData sigdata;
+ psbt_out.FillSignatureData(sigdata);
+
+ MutableTransactionSignatureCreator creator(psbtx.tx.get_ptr(), 0, out.nValue, 1);
+ ProduceSignature(*pwallet, creator, out.scriptPubKey, sigdata);
+ psbt_out.FromSignatureData(sigdata);
+
+ // Get public key paths
+ if (bip32derivs) {
+ for (const auto& pubkey_it : sigdata.misc_pubkeys) {
+ AddKeypathToMap(pwallet, pubkey_it.first, psbt_out.hd_keypaths);
+ }
+ }
+ }
+ return complete;
+}
+
+UniValue walletprocesspsbt(const JSONRPCRequest& request)
+{
+ std::shared_ptr<CWallet> const wallet = GetWalletForJSONRPCRequest(request);
+ CWallet* const pwallet = wallet.get();
+
+ if (!EnsureWalletIsAvailable(pwallet, request.fHelp)) {
+ return NullUniValue;
+ }
+
+ if (request.fHelp || request.params.size() < 1 || request.params.size() > 4)
+ throw std::runtime_error(
+ "walletprocesspsbt \"psbt\" ( sign \"sighashtype\" bip32derivs )\n"
+ "\nUpdate a PSBT with input information from our wallet and then sign inputs\n"
+ "that we can sign for.\n"
+ + HelpRequiringPassphrase(pwallet) + "\n"
+
+ "\nArguments:\n"
+ "1. \"psbt\" (string, required) The transaction base64 string\n"
+ "2. sign (boolean, optional, default=true) Also sign the transaction when updating\n"
+ "3. \"sighashtype\" (string, optional, default=ALL) The signature hash type to sign with if not specified by the PSBT. Must be one of\n"
+ " \"ALL\"\n"
+ " \"NONE\"\n"
+ " \"SINGLE\"\n"
+ " \"ALL|ANYONECANPAY\"\n"
+ " \"NONE|ANYONECANPAY\"\n"
+ " \"SINGLE|ANYONECANPAY\"\n"
+ "4. bip32derivs (boolean, optiona, default=false) If true, includes the BIP 32 derivation paths for public keys if we know them\n"
+
+ "\nResult:\n"
+ "{\n"
+ " \"psbt\" : \"value\", (string) The base64-encoded partially signed transaction\n"
+ " \"complete\" : true|false, (boolean) If the transaction has a complete set of signatures\n"
+ " ]\n"
+ "}\n"
+
+ "\nExamples:\n"
+ + HelpExampleCli("walletprocesspsbt", "\"psbt\"")
+ );
+
+ RPCTypeCheck(request.params, {UniValue::VSTR, UniValue::VBOOL, UniValue::VSTR});
+
+ // Unserialize the transaction
+ PartiallySignedTransaction psbtx;
+ std::string error;
+ if (!DecodePSBT(psbtx, request.params[0].get_str(), error)) {
+ throw JSONRPCError(RPC_DESERIALIZATION_ERROR, strprintf("TX decode failed %s", error));
+ }
+
+ // Get the sighash type
+ int nHashType = ParseSighashString(request.params[2]);
+
+ // Use CTransaction for the constant parts of the
+ // transaction to avoid rehashing.
+ const CTransaction txConst(*psbtx.tx);
+
+ // Fill transaction with our data and also sign
+ bool sign = request.params[1].isNull() ? true : request.params[1].get_bool();
+ bool bip32derivs = request.params[3].isNull() ? false : request.params[3].get_bool();
+ bool complete = FillPSBT(pwallet, psbtx, &txConst, nHashType, sign, bip32derivs);
UniValue result(UniValue::VOBJ);
- result.push_back(Pair("hex", EncodeHexTx(tx)));
- result.push_back(Pair("changepos", changePosition));
- result.push_back(Pair("fee", ValueFromAmount(nFeeOut)));
+ CDataStream ssTx(SER_NETWORK, PROTOCOL_VERSION);
+ ssTx << psbtx;
+ result.pushKV("psbt", EncodeBase64((unsigned char*)ssTx.data(), ssTx.size()));
+ result.pushKV("complete", complete);
+
+ return result;
+}
+
+UniValue walletcreatefundedpsbt(const JSONRPCRequest& request)
+{
+ std::shared_ptr<CWallet> const wallet = GetWalletForJSONRPCRequest(request);
+ CWallet* const pwallet = wallet.get();
+
+ if (!EnsureWalletIsAvailable(pwallet, request.fHelp)) {
+ return NullUniValue;
+ }
+
+ if (request.fHelp || request.params.size() < 2 || request.params.size() > 5)
+ throw std::runtime_error(
+ "walletcreatefundedpsbt [{\"txid\":\"id\",\"vout\":n},...] [{\"address\":amount},{\"data\":\"hex\"},...] ( locktime ) ( replaceable ) ( options bip32derivs )\n"
+ "\nCreates and funds a transaction in the Partially Signed Transaction format. Inputs will be added if supplied inputs are not enough\n"
+ "Implements the Creator and Updater roles.\n"
+ "\nArguments:\n"
+ "1. \"inputs\" (array, required) A json array of json objects\n"
+ " [\n"
+ " {\n"
+ " \"txid\":\"id\", (string, required) The transaction id\n"
+ " \"vout\":n, (numeric, required) The output number\n"
+ " \"sequence\":n (numeric, optional) The sequence number\n"
+ " } \n"
+ " ,...\n"
+ " ]\n"
+ "2. \"outputs\" (array, required) a json array with outputs (key-value pairs)\n"
+ " [\n"
+ " {\n"
+ " \"address\": x.xxx, (obj, optional) A key-value pair. The key (string) is the bitcoin address, the value (float or string) is the amount in " + CURRENCY_UNIT + "\n"
+ " },\n"
+ " {\n"
+ " \"data\": \"hex\" (obj, optional) A key-value pair. The key must be \"data\", the value is hex encoded data\n"
+ " }\n"
+ " ,... More key-value pairs of the above form. For compatibility reasons, a dictionary, which holds the key-value pairs directly, is also\n"
+ " accepted as second parameter.\n"
+ " ]\n"
+ "3. locktime (numeric, optional, default=0) Raw locktime. Non-0 value also locktime-activates inputs\n"
+ " Allows this transaction to be replaced by a transaction with higher fees. If provided, it is an error if explicit sequence numbers are incompatible.\n"
+ "4. options (object, optional)\n"
+ " {\n"
+ " \"changeAddress\" (string, optional, default pool address) The bitcoin address to receive the change\n"
+ " \"changePosition\" (numeric, optional, default random) The index of the change output\n"
+ " \"change_type\" (string, optional) The output type to use. Only valid if changeAddress is not specified. Options are \"legacy\", \"p2sh-segwit\", and \"bech32\". Default is set by -changetype.\n"
+ " \"includeWatching\" (boolean, optional, default false) Also select inputs which are watch only\n"
+ " \"lockUnspents\" (boolean, optional, default false) Lock selected unspent outputs\n"
+ " \"feeRate\" (numeric, optional, default not set: makes wallet determine the fee) Set a specific fee rate in " + CURRENCY_UNIT + "/kB\n"
+ " \"subtractFeeFromOutputs\" (array, optional) A json array of integers.\n"
+ " The fee will be equally deducted from the amount of each specified output.\n"
+ " The outputs are specified by their zero-based index, before any change output is added.\n"
+ " Those recipients will receive less bitcoins than you enter in their corresponding amount field.\n"
+ " If no outputs are specified here, the sender pays the fee.\n"
+ " [vout_index,...]\n"
+ " \"replaceable\" (boolean, optional) Marks this transaction as BIP125 replaceable.\n"
+ " Allows this transaction to be replaced by a transaction with higher fees\n"
+ " \"conf_target\" (numeric, optional) Confirmation target (in blocks)\n"
+ " \"estimate_mode\" (string, optional, default=UNSET) The fee estimate mode, must be one of:\n"
+ " \"UNSET\"\n"
+ " \"ECONOMICAL\"\n"
+ " \"CONSERVATIVE\"\n"
+ " }\n"
+ "5. bip32derivs (boolean, optiona, default=false) If true, includes the BIP 32 derivation paths for public keys if we know them\n"
+ "\nResult:\n"
+ "{\n"
+ " \"psbt\": \"value\", (string) The resulting raw transaction (base64-encoded string)\n"
+ " \"fee\": n, (numeric) Fee in " + CURRENCY_UNIT + " the resulting transaction pays\n"
+ " \"changepos\": n (numeric) The position of the added change output, or -1\n"
+ "}\n"
+ "\nExamples:\n"
+ "\nCreate a transaction with no inputs\n"
+ + HelpExampleCli("walletcreatefundedpsbt", "\"[{\\\"txid\\\":\\\"myid\\\",\\\"vout\\\":0}]\" \"[{\\\"data\\\":\\\"00010203\\\"}]\"")
+ );
+ RPCTypeCheck(request.params, {
+ UniValue::VARR,
+ UniValueType(), // ARR or OBJ, checked later
+ UniValue::VNUM,
+ UniValue::VOBJ,
+ UniValue::VBOOL
+ }, true
+ );
+
+ CAmount fee;
+ int change_position;
+ CMutableTransaction rawTx = ConstructTransaction(request.params[0], request.params[1], request.params[2], request.params[3]["replaceable"]);
+ FundTransaction(pwallet, rawTx, fee, change_position, request.params[3]);
+
+ // Make a blank psbt
+ PartiallySignedTransaction psbtx;
+ psbtx.tx = rawTx;
+ for (unsigned int i = 0; i < rawTx.vin.size(); ++i) {
+ psbtx.inputs.push_back(PSBTInput());
+ }
+ for (unsigned int i = 0; i < rawTx.vout.size(); ++i) {
+ psbtx.outputs.push_back(PSBTOutput());
+ }
+
+ // Use CTransaction for the constant parts of the
+ // transaction to avoid rehashing.
+ const CTransaction txConst(*psbtx.tx);
+
+ // Fill transaction with out data but don't sign
+ bool bip32derivs = request.params[4].isNull() ? false : request.params[5].get_bool();
+ FillPSBT(pwallet, psbtx, &txConst, 1, false, bip32derivs);
+
+ // Serialize the PSBT
+ CDataStream ssTx(SER_NETWORK, PROTOCOL_VERSION);
+ ssTx << psbtx;
+
+ UniValue result(UniValue::VOBJ);
+ result.pushKV("psbt", EncodeBase64((unsigned char*)ssTx.data(), ssTx.size()));
+ result.pushKV("fee", ValueFromAmount(fee));
+ result.pushKV("changepos", change_position);
return result;
}
-extern UniValue dumpprivkey(const UniValue& params, bool fHelp); // in rpcdump.cpp
-extern UniValue importprivkey(const UniValue& params, bool fHelp);
-extern UniValue importaddress(const UniValue& params, bool fHelp);
-extern UniValue importpubkey(const UniValue& params, bool fHelp);
-extern UniValue dumpwallet(const UniValue& params, bool fHelp);
-extern UniValue importwallet(const UniValue& params, bool fHelp);
-extern UniValue importprunedfunds(const UniValue& params, bool fHelp);
-extern UniValue removeprunedfunds(const UniValue& params, bool fHelp);
+UniValue abortrescan(const JSONRPCRequest& request); // in rpcdump.cpp
+UniValue dumpprivkey(const JSONRPCRequest& request); // in rpcdump.cpp
+UniValue importprivkey(const JSONRPCRequest& request);
+UniValue importaddress(const JSONRPCRequest& request);
+UniValue importpubkey(const JSONRPCRequest& request);
+UniValue dumpwallet(const JSONRPCRequest& request);
+UniValue importwallet(const JSONRPCRequest& request);
+UniValue importprunedfunds(const JSONRPCRequest& request);
+UniValue removeprunedfunds(const JSONRPCRequest& request);
+UniValue importmulti(const JSONRPCRequest& request);
static const CRPCCommand commands[] =
-{ // category name actor (function) okSafeMode
- // --------------------- ------------------------ ----------------------- ----------
- { "rawtransactions", "fundrawtransaction", &fundrawtransaction, false },
- { "hidden", "resendwallettransactions", &resendwallettransactions, true },
- { "wallet", "abandontransaction", &abandontransaction, false },
- { "wallet", "addmultisigaddress", &addmultisigaddress, true },
- { "wallet", "addwitnessaddress", &addwitnessaddress, true },
- { "wallet", "backupwallet", &backupwallet, true },
- { "wallet", "dumpprivkey", &dumpprivkey, true },
- { "wallet", "dumpwallet", &dumpwallet, true },
- { "wallet", "encryptwallet", &encryptwallet, true },
- { "wallet", "getaccountaddress", &getaccountaddress, true },
- { "wallet", "getaccount", &getaccount, true },
- { "wallet", "getaddressesbyaccount", &getaddressesbyaccount, true },
- { "wallet", "getbalance", &getbalance, false },
- { "wallet", "getnewaddress", &getnewaddress, true },
- { "wallet", "getrawchangeaddress", &getrawchangeaddress, true },
- { "wallet", "getreceivedbyaccount", &getreceivedbyaccount, false },
- { "wallet", "getreceivedbyaddress", &getreceivedbyaddress, false },
- { "wallet", "gettransaction", &gettransaction, false },
- { "wallet", "getunconfirmedbalance", &getunconfirmedbalance, false },
- { "wallet", "getwalletinfo", &getwalletinfo, false },
- { "wallet", "importprivkey", &importprivkey, true },
- { "wallet", "importwallet", &importwallet, true },
- { "wallet", "importaddress", &importaddress, true },
- { "wallet", "importprunedfunds", &importprunedfunds, true },
- { "wallet", "importpubkey", &importpubkey, true },
- { "wallet", "keypoolrefill", &keypoolrefill, true },
- { "wallet", "listaccounts", &listaccounts, false },
- { "wallet", "listaddressgroupings", &listaddressgroupings, false },
- { "wallet", "listlockunspent", &listlockunspent, false },
- { "wallet", "listreceivedbyaccount", &listreceivedbyaccount, false },
- { "wallet", "listreceivedbyaddress", &listreceivedbyaddress, false },
- { "wallet", "listsinceblock", &listsinceblock, false },
- { "wallet", "listtransactions", &listtransactions, false },
- { "wallet", "listunspent", &listunspent, false },
- { "wallet", "lockunspent", &lockunspent, true },
- { "wallet", "move", &movecmd, false },
- { "wallet", "sendfrom", &sendfrom, false },
- { "wallet", "sendmany", &sendmany, false },
- { "wallet", "sendtoaddress", &sendtoaddress, false },
- { "wallet", "setaccount", &setaccount, true },
- { "wallet", "settxfee", &settxfee, true },
- { "wallet", "signmessage", &signmessage, true },
- { "wallet", "walletlock", &walletlock, true },
- { "wallet", "walletpassphrasechange", &walletpassphrasechange, true },
- { "wallet", "walletpassphrase", &walletpassphrase, true },
- { "wallet", "removeprunedfunds", &removeprunedfunds, true },
+{ // category name actor (function) argNames
+ // --------------------- ------------------------ ----------------------- ----------
+ { "rawtransactions", "fundrawtransaction", &fundrawtransaction, {"hexstring","options","iswitness"} },
+ { "wallet", "walletprocesspsbt", &walletprocesspsbt, {"psbt","sign","sighashtype","bip32derivs"} },
+ { "wallet", "walletcreatefundedpsbt", &walletcreatefundedpsbt, {"inputs","outputs","locktime","options","bip32derivs"} },
+ { "hidden", "resendwallettransactions", &resendwallettransactions, {} },
+ { "wallet", "abandontransaction", &abandontransaction, {"txid"} },
+ { "wallet", "abortrescan", &abortrescan, {} },
+ { "wallet", "addmultisigaddress", &addmultisigaddress, {"nrequired","keys","label|account","address_type"} },
+ { "hidden", "addwitnessaddress", &addwitnessaddress, {"address","p2sh"} },
+ { "wallet", "backupwallet", &backupwallet, {"destination"} },
+ { "wallet", "bumpfee", &bumpfee, {"txid", "options"} },
+ { "wallet", "createwallet", &createwallet, {"wallet_name", "disable_private_keys"} },
+ { "wallet", "dumpprivkey", &dumpprivkey, {"address"} },
+ { "wallet", "dumpwallet", &dumpwallet, {"filename"} },
+ { "wallet", "encryptwallet", &encryptwallet, {"passphrase"} },
+ { "wallet", "getaddressinfo", &getaddressinfo, {"address"} },
+ { "wallet", "getbalance", &getbalance, {"account|dummy","minconf","include_watchonly"} },
+ { "wallet", "getnewaddress", &getnewaddress, {"label|account","address_type"} },
+ { "wallet", "getrawchangeaddress", &getrawchangeaddress, {"address_type"} },
+ { "wallet", "getreceivedbyaddress", &getreceivedbyaddress, {"address","minconf"} },
+ { "wallet", "gettransaction", &gettransaction, {"txid","include_watchonly"} },
+ { "wallet", "getunconfirmedbalance", &getunconfirmedbalance, {} },
+ { "wallet", "getwalletinfo", &getwalletinfo, {} },
+ { "wallet", "importmulti", &importmulti, {"requests","options"} },
+ { "wallet", "importprivkey", &importprivkey, {"privkey","label","rescan"} },
+ { "wallet", "importwallet", &importwallet, {"filename"} },
+ { "wallet", "importaddress", &importaddress, {"address","label","rescan","p2sh"} },
+ { "wallet", "importprunedfunds", &importprunedfunds, {"rawtransaction","txoutproof"} },
+ { "wallet", "importpubkey", &importpubkey, {"pubkey","label","rescan"} },
+ { "wallet", "keypoolrefill", &keypoolrefill, {"newsize"} },
+ { "wallet", "listaddressgroupings", &listaddressgroupings, {} },
+ { "wallet", "listlockunspent", &listlockunspent, {} },
+ { "wallet", "listreceivedbyaddress", &listreceivedbyaddress, {"minconf","include_empty","include_watchonly","address_filter"} },
+ { "wallet", "listsinceblock", &listsinceblock, {"blockhash","target_confirmations","include_watchonly","include_removed"} },
+ { "wallet", "listtransactions", &listtransactions, {"account|dummy","count","skip","include_watchonly"} },
+ { "wallet", "listunspent", &listunspent, {"minconf","maxconf","addresses","include_unsafe","query_options"} },
+ { "wallet", "listwallets", &listwallets, {} },
+ { "wallet", "loadwallet", &loadwallet, {"filename"} },
+ { "wallet", "lockunspent", &lockunspent, {"unlock","transactions"} },
+ { "wallet", "sendmany", &sendmany, {"fromaccount|dummy","amounts","minconf","comment","subtractfeefrom","replaceable","conf_target","estimate_mode"} },
+ { "wallet", "sendtoaddress", &sendtoaddress, {"address","amount","comment","comment_to","subtractfeefromamount","replaceable","conf_target","estimate_mode"} },
+ { "wallet", "settxfee", &settxfee, {"amount"} },
+ { "wallet", "signmessage", &signmessage, {"address","message"} },
+ { "wallet", "signrawtransactionwithwallet", &signrawtransactionwithwallet, {"hexstring","prevtxs","sighashtype"} },
+ { "wallet", "unloadwallet", &unloadwallet, {"wallet_name"} },
+ { "wallet", "walletlock", &walletlock, {} },
+ { "wallet", "walletpassphrasechange", &walletpassphrasechange, {"oldpassphrase","newpassphrase"} },
+ { "wallet", "walletpassphrase", &walletpassphrase, {"passphrase","timeout"} },
+ { "wallet", "removeprunedfunds", &removeprunedfunds, {"txid"} },
+ { "wallet", "rescanblockchain", &rescanblockchain, {"start_height", "stop_height"} },
+ { "wallet", "sethdseed", &sethdseed, {"newkeypool","seed"} },
+
+ /** Account functions (deprecated) */
+ { "wallet", "getaccountaddress", &getaccountaddress, {"account"} },
+ { "wallet", "getaccount", &getaccount, {"address"} },
+ { "wallet", "getaddressesbyaccount", &getaddressesbyaccount, {"account"} },
+ { "wallet", "getreceivedbyaccount", &getreceivedbylabel, {"account","minconf"} },
+ { "wallet", "listaccounts", &listaccounts, {"minconf","include_watchonly"} },
+ { "wallet", "listreceivedbyaccount", &listreceivedbylabel, {"minconf","include_empty","include_watchonly"} },
+ { "wallet", "setaccount", &setlabel, {"address","account"} },
+ { "wallet", "sendfrom", &sendfrom, {"fromaccount","toaddress","amount","minconf","comment","comment_to"} },
+ { "wallet", "move", &movecmd, {"fromaccount","toaccount","amount","minconf","comment"} },
+
+ /** Label functions (to replace non-balance account functions) */
+ { "wallet", "getaddressesbylabel", &getaddressesbylabel, {"label"} },
+ { "wallet", "getreceivedbylabel", &getreceivedbylabel, {"label","minconf"} },
+ { "wallet", "listlabels", &listlabels, {"purpose"} },
+ { "wallet", "listreceivedbylabel", &listreceivedbylabel, {"minconf","include_empty","include_watchonly"} },
+ { "wallet", "setlabel", &setlabel, {"address","label"} },
+
+ { "generating", "generate", &generate, {"nblocks","maxtries"} },
};
-void RegisterWalletRPCCommands(CRPCTable &tableRPC)
+void RegisterWalletRPCCommands(CRPCTable &t)
{
for (unsigned int vcidx = 0; vcidx < ARRAYLEN(commands); vcidx++)
- tableRPC.appendCommand(commands[vcidx].name, &commands[vcidx]);
+ t.appendCommand(commands[vcidx].name, &commands[vcidx]);
}
diff --git a/src/wallet/rpcwallet.h b/src/wallet/rpcwallet.h
index a5de7e2de1..9b9a159b86 100644
--- a/src/wallet/rpcwallet.h
+++ b/src/wallet/rpcwallet.h
@@ -1,12 +1,34 @@
-// Copyright (c) 2016 The Bitcoin Core developers
+// Copyright (c) 2016-2018 The Bitcoin Core developers
// Distributed under the MIT software license, see the accompanying
// file COPYING or http://www.opensource.org/licenses/mit-license.php.
#ifndef BITCOIN_WALLET_RPCWALLET_H
#define BITCOIN_WALLET_RPCWALLET_H
+#include <string>
+
class CRPCTable;
+class CWallet;
+class JSONRPCRequest;
+class UniValue;
+struct PartiallySignedTransaction;
+class CTransaction;
+
+void RegisterWalletRPCCommands(CRPCTable &t);
+
+/**
+ * Figures out what wallet, if any, to use for a JSONRPCRequest.
+ *
+ * @param[in] request JSONRPCRequest that wishes to access a wallet
+ * @return nullptr if no wallet should be used, or a pointer to the CWallet
+ */
+std::shared_ptr<CWallet> GetWalletForJSONRPCRequest(const JSONRPCRequest& request);
-void RegisterWalletRPCCommands(CRPCTable &tableRPC);
+std::string HelpRequiringPassphrase(CWallet *);
+void EnsureWalletIsUnlocked(CWallet *);
+bool EnsureWalletIsAvailable(CWallet *, bool avoidException);
+UniValue getaddressinfo(const JSONRPCRequest& request);
+UniValue signrawtransactionwithwallet(const JSONRPCRequest& request);
+bool FillPSBT(const CWallet* pwallet, PartiallySignedTransaction& psbtx, const CTransaction* txConst, int sighash_type = 1, bool sign = true, bool bip32derivs = false);
#endif //BITCOIN_WALLET_RPCWALLET_H
diff --git a/src/wallet/test/accounting_tests.cpp b/src/wallet/test/accounting_tests.cpp
index a6cada46a2..79060887be 100644
--- a/src/wallet/test/accounting_tests.cpp
+++ b/src/wallet/test/accounting_tests.cpp
@@ -1,30 +1,26 @@
-// Copyright (c) 2012-2015 The Bitcoin Core developers
+// Copyright (c) 2012-2018 The Bitcoin Core developers
// Distributed under the MIT software license, see the accompanying
// file COPYING or http://www.opensource.org/licenses/mit-license.php.
-#include "wallet/wallet.h"
-#include "wallet/walletdb.h"
+#include <wallet/wallet.h>
-#include "wallet/test/wallet_test_fixture.h"
+#include <wallet/test/wallet_test_fixture.h>
#include <stdint.h>
-#include <boost/foreach.hpp>
#include <boost/test/unit_test.hpp>
-extern CWallet* pwalletMain;
-
BOOST_FIXTURE_TEST_SUITE(accounting_tests, WalletTestingSetup)
static void
-GetResults(CWalletDB& walletdb, std::map<CAmount, CAccountingEntry>& results)
+GetResults(CWallet& wallet, std::map<CAmount, CAccountingEntry>& results)
{
std::list<CAccountingEntry> aes;
results.clear();
- BOOST_CHECK(walletdb.ReorderTransactions(pwalletMain) == DB_LOAD_OK);
- walletdb.ListAccountCreditDebit("", aes);
- BOOST_FOREACH(CAccountingEntry& ae, aes)
+ BOOST_CHECK(wallet.ReorderTransactions() == DBErrors::LOAD_OK);
+ wallet.ListAccountCreditDebit("", aes);
+ for (CAccountingEntry& ae : aes)
{
results[ae.nOrderPos] = ae;
}
@@ -32,34 +28,33 @@ GetResults(CWalletDB& walletdb, std::map<CAmount, CAccountingEntry>& results)
BOOST_AUTO_TEST_CASE(acc_orderupgrade)
{
- CWalletDB walletdb(pwalletMain->strWalletFile);
std::vector<CWalletTx*> vpwtx;
- CWalletTx wtx;
+ CWalletTx wtx(nullptr /* pwallet */, MakeTransactionRef());
CAccountingEntry ae;
std::map<CAmount, CAccountingEntry> results;
- LOCK(pwalletMain->cs_wallet);
+ LOCK(m_wallet.cs_wallet);
ae.strAccount = "";
ae.nCreditDebit = 1;
ae.nTime = 1333333333;
ae.strOtherAccount = "b";
ae.strComment = "";
- pwalletMain->AddAccountingEntry(ae, walletdb);
+ m_wallet.AddAccountingEntry(ae);
wtx.mapValue["comment"] = "z";
- pwalletMain->AddToWallet(wtx);
- vpwtx.push_back(&pwalletMain->mapWallet[wtx.GetHash()]);
+ m_wallet.AddToWallet(wtx);
+ vpwtx.push_back(&m_wallet.mapWallet.at(wtx.GetHash()));
vpwtx[0]->nTimeReceived = (unsigned int)1333333335;
vpwtx[0]->nOrderPos = -1;
ae.nTime = 1333333336;
ae.strOtherAccount = "c";
- pwalletMain->AddAccountingEntry(ae, walletdb);
+ m_wallet.AddAccountingEntry(ae);
- GetResults(walletdb, results);
+ GetResults(m_wallet, results);
- BOOST_CHECK(pwalletMain->nOrderPosNext == 3);
+ BOOST_CHECK(m_wallet.nOrderPosNext == 3);
BOOST_CHECK(2 == results.size());
BOOST_CHECK(results[0].nTime == 1333333333);
BOOST_CHECK(results[0].strComment.empty());
@@ -70,13 +65,13 @@ BOOST_AUTO_TEST_CASE(acc_orderupgrade)
ae.nTime = 1333333330;
ae.strOtherAccount = "d";
- ae.nOrderPos = pwalletMain->IncOrderPosNext();
- pwalletMain->AddAccountingEntry(ae, walletdb);
+ ae.nOrderPos = m_wallet.IncOrderPosNext();
+ m_wallet.AddAccountingEntry(ae);
- GetResults(walletdb, results);
+ GetResults(m_wallet, results);
BOOST_CHECK(results.size() == 3);
- BOOST_CHECK(pwalletMain->nOrderPosNext == 4);
+ BOOST_CHECK(m_wallet.nOrderPosNext == 4);
BOOST_CHECK(results[0].nTime == 1333333333);
BOOST_CHECK(1 == vpwtx[0]->nOrderPos);
BOOST_CHECK(results[2].nTime == 1333333336);
@@ -86,29 +81,29 @@ BOOST_AUTO_TEST_CASE(acc_orderupgrade)
wtx.mapValue["comment"] = "y";
{
- CMutableTransaction tx(wtx);
- --tx.nLockTime; // Just to change the hash :)
- *static_cast<CTransaction*>(&wtx) = CTransaction(tx);
+ CMutableTransaction tx(*wtx.tx);
+ ++tx.nLockTime; // Just to change the hash :)
+ wtx.SetTx(MakeTransactionRef(std::move(tx)));
}
- pwalletMain->AddToWallet(wtx);
- vpwtx.push_back(&pwalletMain->mapWallet[wtx.GetHash()]);
+ m_wallet.AddToWallet(wtx);
+ vpwtx.push_back(&m_wallet.mapWallet.at(wtx.GetHash()));
vpwtx[1]->nTimeReceived = (unsigned int)1333333336;
wtx.mapValue["comment"] = "x";
{
- CMutableTransaction tx(wtx);
- --tx.nLockTime; // Just to change the hash :)
- *static_cast<CTransaction*>(&wtx) = CTransaction(tx);
+ CMutableTransaction tx(*wtx.tx);
+ ++tx.nLockTime; // Just to change the hash :)
+ wtx.SetTx(MakeTransactionRef(std::move(tx)));
}
- pwalletMain->AddToWallet(wtx);
- vpwtx.push_back(&pwalletMain->mapWallet[wtx.GetHash()]);
+ m_wallet.AddToWallet(wtx);
+ vpwtx.push_back(&m_wallet.mapWallet.at(wtx.GetHash()));
vpwtx[2]->nTimeReceived = (unsigned int)1333333329;
vpwtx[2]->nOrderPos = -1;
- GetResults(walletdb, results);
+ GetResults(m_wallet, results);
BOOST_CHECK(results.size() == 3);
- BOOST_CHECK(pwalletMain->nOrderPosNext == 6);
+ BOOST_CHECK(m_wallet.nOrderPosNext == 6);
BOOST_CHECK(0 == vpwtx[2]->nOrderPos);
BOOST_CHECK(results[1].nTime == 1333333333);
BOOST_CHECK(2 == vpwtx[0]->nOrderPos);
@@ -121,12 +116,12 @@ BOOST_AUTO_TEST_CASE(acc_orderupgrade)
ae.nTime = 1333333334;
ae.strOtherAccount = "e";
ae.nOrderPos = -1;
- pwalletMain->AddAccountingEntry(ae, walletdb);
+ m_wallet.AddAccountingEntry(ae);
- GetResults(walletdb, results);
+ GetResults(m_wallet, results);
BOOST_CHECK(results.size() == 4);
- BOOST_CHECK(pwalletMain->nOrderPosNext == 7);
+ BOOST_CHECK(m_wallet.nOrderPosNext == 7);
BOOST_CHECK(0 == vpwtx[2]->nOrderPos);
BOOST_CHECK(results[1].nTime == 1333333333);
BOOST_CHECK(2 == vpwtx[0]->nOrderPos);
diff --git a/src/wallet/test/coinselector_tests.cpp b/src/wallet/test/coinselector_tests.cpp
new file mode 100644
index 0000000000..0776ffda9c
--- /dev/null
+++ b/src/wallet/test/coinselector_tests.cpp
@@ -0,0 +1,584 @@
+// Copyright (c) 2017-2018 The Bitcoin Core developers
+// Distributed under the MIT software license, see the accompanying
+// file COPYING or http://www.opensource.org/licenses/mit-license.php.
+
+#include <wallet/wallet.h>
+#include <wallet/coinselection.h>
+#include <wallet/coincontrol.h>
+#include <amount.h>
+#include <primitives/transaction.h>
+#include <random.h>
+#include <test/test_bitcoin.h>
+#include <wallet/test/wallet_test_fixture.h>
+
+#include <boost/test/unit_test.hpp>
+#include <random>
+
+BOOST_FIXTURE_TEST_SUITE(coinselector_tests, WalletTestingSetup)
+
+// how many times to run all the tests to have a chance to catch errors that only show up with particular random shuffles
+#define RUN_TESTS 100
+
+// some tests fail 1% of the time due to bad luck.
+// we repeat those tests this many times and only complain if all iterations of the test fail
+#define RANDOM_REPEATS 5
+
+std::vector<std::unique_ptr<CWalletTx>> wtxn;
+
+typedef std::set<CInputCoin> CoinSet;
+
+static std::vector<COutput> vCoins;
+static CWallet testWallet("dummy", WalletDatabase::CreateDummy());
+static CAmount balance = 0;
+
+CoinEligibilityFilter filter_standard(1, 6, 0);
+CoinEligibilityFilter filter_confirmed(1, 1, 0);
+CoinEligibilityFilter filter_standard_extra(6, 6, 0);
+CoinSelectionParams coin_selection_params(false, 0, 0, CFeeRate(0), 0);
+
+static void add_coin(const CAmount& nValue, int nInput, std::vector<CInputCoin>& set)
+{
+ CMutableTransaction tx;
+ tx.vout.resize(nInput + 1);
+ tx.vout[nInput].nValue = nValue;
+ set.emplace_back(MakeTransactionRef(tx), nInput);
+}
+
+static void add_coin(const CAmount& nValue, int nInput, CoinSet& set)
+{
+ CMutableTransaction tx;
+ tx.vout.resize(nInput + 1);
+ tx.vout[nInput].nValue = nValue;
+ set.emplace(MakeTransactionRef(tx), nInput);
+}
+
+static void add_coin(const CAmount& nValue, int nAge = 6*24, bool fIsFromMe = false, int nInput=0)
+{
+ balance += nValue;
+ static int nextLockTime = 0;
+ CMutableTransaction tx;
+ tx.nLockTime = nextLockTime++; // so all transactions get different hashes
+ tx.vout.resize(nInput + 1);
+ tx.vout[nInput].nValue = nValue;
+ if (fIsFromMe) {
+ // IsFromMe() returns (GetDebit() > 0), and GetDebit() is 0 if vin.empty(),
+ // so stop vin being empty, and cache a non-zero Debit to fake out IsFromMe()
+ tx.vin.resize(1);
+ }
+ std::unique_ptr<CWalletTx> wtx(new CWalletTx(&testWallet, MakeTransactionRef(std::move(tx))));
+ if (fIsFromMe)
+ {
+ wtx->fDebitCached = true;
+ wtx->nDebitCached = 1;
+ }
+ COutput output(wtx.get(), nInput, nAge, true /* spendable */, true /* solvable */, true /* safe */);
+ vCoins.push_back(output);
+ testWallet.AddToWallet(*wtx.get());
+ wtxn.emplace_back(std::move(wtx));
+}
+
+static void empty_wallet(void)
+{
+ vCoins.clear();
+ wtxn.clear();
+ balance = 0;
+}
+
+static bool equal_sets(CoinSet a, CoinSet b)
+{
+ std::pair<CoinSet::iterator, CoinSet::iterator> ret = mismatch(a.begin(), a.end(), b.begin());
+ return ret.first == a.end() && ret.second == b.end();
+}
+
+static CAmount make_hard_case(int utxos, std::vector<CInputCoin>& utxo_pool)
+{
+ utxo_pool.clear();
+ CAmount target = 0;
+ for (int i = 0; i < utxos; ++i) {
+ target += (CAmount)1 << (utxos+i);
+ add_coin((CAmount)1 << (utxos+i), 2*i, utxo_pool);
+ add_coin(((CAmount)1 << (utxos+i)) + ((CAmount)1 << (utxos-1-i)), 2*i + 1, utxo_pool);
+ }
+ return target;
+}
+
+inline std::vector<OutputGroup>& GroupCoins(const std::vector<CInputCoin>& coins)
+{
+ static std::vector<OutputGroup> static_groups;
+ static_groups.clear();
+ for (auto& coin : coins) static_groups.emplace_back(coin, 0, true, 0, 0);
+ return static_groups;
+}
+
+inline std::vector<OutputGroup>& GroupCoins(const std::vector<COutput>& coins)
+{
+ static std::vector<OutputGroup> static_groups;
+ static_groups.clear();
+ for (auto& coin : coins) static_groups.emplace_back(coin.GetInputCoin(), coin.nDepth, coin.tx->fDebitCached && coin.tx->nDebitCached == 1 /* HACK: we can't figure out the is_me flag so we use the conditions defined above; perhaps set safe to false for !fIsFromMe in add_coin() */, 0, 0);
+ return static_groups;
+}
+
+// Branch and bound coin selection tests
+BOOST_AUTO_TEST_CASE(bnb_search_test)
+{
+
+ LOCK(testWallet.cs_wallet);
+
+ // Setup
+ std::vector<CInputCoin> utxo_pool;
+ CoinSet selection;
+ CoinSet actual_selection;
+ CAmount value_ret = 0;
+ CAmount not_input_fees = 0;
+
+ /////////////////////////
+ // Known Outcome tests //
+ /////////////////////////
+ BOOST_TEST_MESSAGE("Testing known outcomes");
+
+ // Empty utxo pool
+ BOOST_CHECK(!SelectCoinsBnB(GroupCoins(utxo_pool), 1 * CENT, 0.5 * CENT, selection, value_ret, not_input_fees));
+ selection.clear();
+
+ // Add utxos
+ add_coin(1 * CENT, 1, utxo_pool);
+ add_coin(2 * CENT, 2, utxo_pool);
+ add_coin(3 * CENT, 3, utxo_pool);
+ add_coin(4 * CENT, 4, utxo_pool);
+
+ // Select 1 Cent
+ add_coin(1 * CENT, 1, actual_selection);
+ BOOST_CHECK(SelectCoinsBnB(GroupCoins(utxo_pool), 1 * CENT, 0.5 * CENT, selection, value_ret, not_input_fees));
+ BOOST_CHECK(equal_sets(selection, actual_selection));
+ actual_selection.clear();
+ selection.clear();
+
+ // Select 2 Cent
+ add_coin(2 * CENT, 2, actual_selection);
+ BOOST_CHECK(SelectCoinsBnB(GroupCoins(utxo_pool), 2 * CENT, 0.5 * CENT, selection, value_ret, not_input_fees));
+ BOOST_CHECK(equal_sets(selection, actual_selection));
+ actual_selection.clear();
+ selection.clear();
+
+ // Select 5 Cent
+ add_coin(3 * CENT, 3, actual_selection);
+ add_coin(2 * CENT, 2, actual_selection);
+ BOOST_CHECK(SelectCoinsBnB(GroupCoins(utxo_pool), 5 * CENT, 0.5 * CENT, selection, value_ret, not_input_fees));
+ BOOST_CHECK(equal_sets(selection, actual_selection));
+ actual_selection.clear();
+ selection.clear();
+
+ // Select 11 Cent, not possible
+ BOOST_CHECK(!SelectCoinsBnB(GroupCoins(utxo_pool), 11 * CENT, 0.5 * CENT, selection, value_ret, not_input_fees));
+ actual_selection.clear();
+ selection.clear();
+
+ // Select 10 Cent
+ add_coin(5 * CENT, 5, utxo_pool);
+ add_coin(4 * CENT, 4, actual_selection);
+ add_coin(3 * CENT, 3, actual_selection);
+ add_coin(2 * CENT, 2, actual_selection);
+ add_coin(1 * CENT, 1, actual_selection);
+ BOOST_CHECK(SelectCoinsBnB(GroupCoins(utxo_pool), 10 * CENT, 0.5 * CENT, selection, value_ret, not_input_fees));
+ BOOST_CHECK(equal_sets(selection, actual_selection));
+ actual_selection.clear();
+ selection.clear();
+
+ // Negative effective value
+ // Select 10 Cent but have 1 Cent not be possible because too small
+ add_coin(5 * CENT, 5, actual_selection);
+ add_coin(3 * CENT, 3, actual_selection);
+ add_coin(2 * CENT, 2, actual_selection);
+ BOOST_CHECK(SelectCoinsBnB(GroupCoins(utxo_pool), 10 * CENT, 5000, selection, value_ret, not_input_fees));
+
+ // Select 0.25 Cent, not possible
+ BOOST_CHECK(!SelectCoinsBnB(GroupCoins(utxo_pool), 0.25 * CENT, 0.5 * CENT, selection, value_ret, not_input_fees));
+ actual_selection.clear();
+ selection.clear();
+
+ // Iteration exhaustion test
+ CAmount target = make_hard_case(17, utxo_pool);
+ BOOST_CHECK(!SelectCoinsBnB(GroupCoins(utxo_pool), target, 0, selection, value_ret, not_input_fees)); // Should exhaust
+ target = make_hard_case(14, utxo_pool);
+ BOOST_CHECK(SelectCoinsBnB(GroupCoins(utxo_pool), target, 0, selection, value_ret, not_input_fees)); // Should not exhaust
+
+ // Test same value early bailout optimization
+ add_coin(7 * CENT, 7, actual_selection);
+ add_coin(7 * CENT, 7, actual_selection);
+ add_coin(7 * CENT, 7, actual_selection);
+ add_coin(7 * CENT, 7, actual_selection);
+ add_coin(2 * CENT, 7, actual_selection);
+ add_coin(7 * CENT, 7, utxo_pool);
+ add_coin(7 * CENT, 7, utxo_pool);
+ add_coin(7 * CENT, 7, utxo_pool);
+ add_coin(7 * CENT, 7, utxo_pool);
+ add_coin(2 * CENT, 7, utxo_pool);
+ for (int i = 0; i < 50000; ++i) {
+ add_coin(5 * CENT, 7, utxo_pool);
+ }
+ BOOST_CHECK(SelectCoinsBnB(GroupCoins(utxo_pool), 30 * CENT, 5000, selection, value_ret, not_input_fees));
+
+ ////////////////////
+ // Behavior tests //
+ ////////////////////
+ // Select 1 Cent with pool of only greater than 5 Cent
+ utxo_pool.clear();
+ for (int i = 5; i <= 20; ++i) {
+ add_coin(i * CENT, i, utxo_pool);
+ }
+ // Run 100 times, to make sure it is never finding a solution
+ for (int i = 0; i < 100; ++i) {
+ BOOST_CHECK(!SelectCoinsBnB(GroupCoins(utxo_pool), 1 * CENT, 2 * CENT, selection, value_ret, not_input_fees));
+ }
+
+ // Make sure that effective value is working in SelectCoinsMinConf when BnB is used
+ CoinSelectionParams coin_selection_params_bnb(true, 0, 0, CFeeRate(3000), 0);
+ CoinSet setCoinsRet;
+ CAmount nValueRet;
+ bool bnb_used;
+ empty_wallet();
+ add_coin(1);
+ vCoins.at(0).nInputBytes = 40; // Make sure that it has a negative effective value. The next check should assert if this somehow got through. Otherwise it will fail
+ BOOST_CHECK(!testWallet.SelectCoinsMinConf( 1 * CENT, filter_standard, GroupCoins(vCoins), setCoinsRet, nValueRet, coin_selection_params_bnb, bnb_used));
+
+ // Make sure that we aren't using BnB when there are preset inputs
+ empty_wallet();
+ add_coin(5 * CENT);
+ add_coin(3 * CENT);
+ add_coin(2 * CENT);
+ CCoinControl coin_control;
+ coin_control.fAllowOtherInputs = true;
+ coin_control.Select(COutPoint(vCoins.at(0).tx->GetHash(), vCoins.at(0).i));
+ BOOST_CHECK(testWallet.SelectCoins(vCoins, 10 * CENT, setCoinsRet, nValueRet, coin_control, coin_selection_params_bnb, bnb_used));
+ BOOST_CHECK(!bnb_used);
+ BOOST_CHECK(!coin_selection_params_bnb.use_bnb);
+}
+
+BOOST_AUTO_TEST_CASE(knapsack_solver_test)
+{
+ CoinSet setCoinsRet, setCoinsRet2;
+ CAmount nValueRet;
+ bool bnb_used;
+
+ LOCK(testWallet.cs_wallet);
+
+ // test multiple times to allow for differences in the shuffle order
+ for (int i = 0; i < RUN_TESTS; i++)
+ {
+ empty_wallet();
+
+ // with an empty wallet we can't even pay one cent
+ BOOST_CHECK(!testWallet.SelectCoinsMinConf( 1 * CENT, filter_standard, GroupCoins(vCoins), setCoinsRet, nValueRet, coin_selection_params, bnb_used));
+
+ add_coin(1*CENT, 4); // add a new 1 cent coin
+
+ // with a new 1 cent coin, we still can't find a mature 1 cent
+ BOOST_CHECK(!testWallet.SelectCoinsMinConf( 1 * CENT, filter_standard, GroupCoins(vCoins), setCoinsRet, nValueRet, coin_selection_params, bnb_used));
+
+ // but we can find a new 1 cent
+ BOOST_CHECK( testWallet.SelectCoinsMinConf( 1 * CENT, filter_confirmed, GroupCoins(vCoins), setCoinsRet, nValueRet, coin_selection_params, bnb_used));
+ BOOST_CHECK_EQUAL(nValueRet, 1 * CENT);
+
+ add_coin(2*CENT); // add a mature 2 cent coin
+
+ // we can't make 3 cents of mature coins
+ BOOST_CHECK(!testWallet.SelectCoinsMinConf( 3 * CENT, filter_standard, GroupCoins(vCoins), setCoinsRet, nValueRet, coin_selection_params, bnb_used));
+
+ // we can make 3 cents of new coins
+ BOOST_CHECK( testWallet.SelectCoinsMinConf( 3 * CENT, filter_confirmed, GroupCoins(vCoins), setCoinsRet, nValueRet, coin_selection_params, bnb_used));
+ BOOST_CHECK_EQUAL(nValueRet, 3 * CENT);
+
+ add_coin(5*CENT); // add a mature 5 cent coin,
+ add_coin(10*CENT, 3, true); // a new 10 cent coin sent from one of our own addresses
+ add_coin(20*CENT); // and a mature 20 cent coin
+
+ // now we have new: 1+10=11 (of which 10 was self-sent), and mature: 2+5+20=27. total = 38
+
+ // we can't make 38 cents only if we disallow new coins:
+ BOOST_CHECK(!testWallet.SelectCoinsMinConf(38 * CENT, filter_standard, GroupCoins(vCoins), setCoinsRet, nValueRet, coin_selection_params, bnb_used));
+ // we can't even make 37 cents if we don't allow new coins even if they're from us
+ BOOST_CHECK(!testWallet.SelectCoinsMinConf(38 * CENT, filter_standard_extra, GroupCoins(vCoins), setCoinsRet, nValueRet, coin_selection_params, bnb_used));
+ // but we can make 37 cents if we accept new coins from ourself
+ BOOST_CHECK( testWallet.SelectCoinsMinConf(37 * CENT, filter_standard, GroupCoins(vCoins), setCoinsRet, nValueRet, coin_selection_params, bnb_used));
+ BOOST_CHECK_EQUAL(nValueRet, 37 * CENT);
+ // and we can make 38 cents if we accept all new coins
+ BOOST_CHECK( testWallet.SelectCoinsMinConf(38 * CENT, filter_confirmed, GroupCoins(vCoins), setCoinsRet, nValueRet, coin_selection_params, bnb_used));
+ BOOST_CHECK_EQUAL(nValueRet, 38 * CENT);
+
+ // try making 34 cents from 1,2,5,10,20 - we can't do it exactly
+ BOOST_CHECK( testWallet.SelectCoinsMinConf(34 * CENT, filter_confirmed, GroupCoins(vCoins), setCoinsRet, nValueRet, coin_selection_params, bnb_used));
+ BOOST_CHECK_EQUAL(nValueRet, 35 * CENT); // but 35 cents is closest
+ BOOST_CHECK_EQUAL(setCoinsRet.size(), 3U); // the best should be 20+10+5. it's incredibly unlikely the 1 or 2 got included (but possible)
+
+ // when we try making 7 cents, the smaller coins (1,2,5) are enough. We should see just 2+5
+ BOOST_CHECK( testWallet.SelectCoinsMinConf( 7 * CENT, filter_confirmed, GroupCoins(vCoins), setCoinsRet, nValueRet, coin_selection_params, bnb_used));
+ BOOST_CHECK_EQUAL(nValueRet, 7 * CENT);
+ BOOST_CHECK_EQUAL(setCoinsRet.size(), 2U);
+
+ // when we try making 8 cents, the smaller coins (1,2,5) are exactly enough.
+ BOOST_CHECK( testWallet.SelectCoinsMinConf( 8 * CENT, filter_confirmed, GroupCoins(vCoins), setCoinsRet, nValueRet, coin_selection_params, bnb_used));
+ BOOST_CHECK(nValueRet == 8 * CENT);
+ BOOST_CHECK_EQUAL(setCoinsRet.size(), 3U);
+
+ // when we try making 9 cents, no subset of smaller coins is enough, and we get the next bigger coin (10)
+ BOOST_CHECK( testWallet.SelectCoinsMinConf( 9 * CENT, filter_confirmed, GroupCoins(vCoins), setCoinsRet, nValueRet, coin_selection_params, bnb_used));
+ BOOST_CHECK_EQUAL(nValueRet, 10 * CENT);
+ BOOST_CHECK_EQUAL(setCoinsRet.size(), 1U);
+
+ // now clear out the wallet and start again to test choosing between subsets of smaller coins and the next biggest coin
+ empty_wallet();
+
+ add_coin( 6*CENT);
+ add_coin( 7*CENT);
+ add_coin( 8*CENT);
+ add_coin(20*CENT);
+ add_coin(30*CENT); // now we have 6+7+8+20+30 = 71 cents total
+
+ // check that we have 71 and not 72
+ BOOST_CHECK( testWallet.SelectCoinsMinConf(71 * CENT, filter_confirmed, GroupCoins(vCoins), setCoinsRet, nValueRet, coin_selection_params, bnb_used));
+ BOOST_CHECK(!testWallet.SelectCoinsMinConf(72 * CENT, filter_confirmed, GroupCoins(vCoins), setCoinsRet, nValueRet, coin_selection_params, bnb_used));
+
+ // now try making 16 cents. the best smaller coins can do is 6+7+8 = 21; not as good at the next biggest coin, 20
+ BOOST_CHECK( testWallet.SelectCoinsMinConf(16 * CENT, filter_confirmed, GroupCoins(vCoins), setCoinsRet, nValueRet, coin_selection_params, bnb_used));
+ BOOST_CHECK_EQUAL(nValueRet, 20 * CENT); // we should get 20 in one coin
+ BOOST_CHECK_EQUAL(setCoinsRet.size(), 1U);
+
+ add_coin( 5*CENT); // now we have 5+6+7+8+20+30 = 75 cents total
+
+ // now if we try making 16 cents again, the smaller coins can make 5+6+7 = 18 cents, better than the next biggest coin, 20
+ BOOST_CHECK( testWallet.SelectCoinsMinConf(16 * CENT, filter_confirmed, GroupCoins(vCoins), setCoinsRet, nValueRet, coin_selection_params, bnb_used));
+ BOOST_CHECK_EQUAL(nValueRet, 18 * CENT); // we should get 18 in 3 coins
+ BOOST_CHECK_EQUAL(setCoinsRet.size(), 3U);
+
+ add_coin( 18*CENT); // now we have 5+6+7+8+18+20+30
+
+ // and now if we try making 16 cents again, the smaller coins can make 5+6+7 = 18 cents, the same as the next biggest coin, 18
+ BOOST_CHECK( testWallet.SelectCoinsMinConf(16 * CENT, filter_confirmed, GroupCoins(vCoins), setCoinsRet, nValueRet, coin_selection_params, bnb_used));
+ BOOST_CHECK_EQUAL(nValueRet, 18 * CENT); // we should get 18 in 1 coin
+ BOOST_CHECK_EQUAL(setCoinsRet.size(), 1U); // because in the event of a tie, the biggest coin wins
+
+ // now try making 11 cents. we should get 5+6
+ BOOST_CHECK( testWallet.SelectCoinsMinConf(11 * CENT, filter_confirmed, GroupCoins(vCoins), setCoinsRet, nValueRet, coin_selection_params, bnb_used));
+ BOOST_CHECK_EQUAL(nValueRet, 11 * CENT);
+ BOOST_CHECK_EQUAL(setCoinsRet.size(), 2U);
+
+ // check that the smallest bigger coin is used
+ add_coin( 1*COIN);
+ add_coin( 2*COIN);
+ add_coin( 3*COIN);
+ add_coin( 4*COIN); // now we have 5+6+7+8+18+20+30+100+200+300+400 = 1094 cents
+ BOOST_CHECK( testWallet.SelectCoinsMinConf(95 * CENT, filter_confirmed, GroupCoins(vCoins), setCoinsRet, nValueRet, coin_selection_params, bnb_used));
+ BOOST_CHECK_EQUAL(nValueRet, 1 * COIN); // we should get 1 BTC in 1 coin
+ BOOST_CHECK_EQUAL(setCoinsRet.size(), 1U);
+
+ BOOST_CHECK( testWallet.SelectCoinsMinConf(195 * CENT, filter_confirmed, GroupCoins(vCoins), setCoinsRet, nValueRet, coin_selection_params, bnb_used));
+ BOOST_CHECK_EQUAL(nValueRet, 2 * COIN); // we should get 2 BTC in 1 coin
+ BOOST_CHECK_EQUAL(setCoinsRet.size(), 1U);
+
+ // empty the wallet and start again, now with fractions of a cent, to test small change avoidance
+
+ empty_wallet();
+ add_coin(MIN_CHANGE * 1 / 10);
+ add_coin(MIN_CHANGE * 2 / 10);
+ add_coin(MIN_CHANGE * 3 / 10);
+ add_coin(MIN_CHANGE * 4 / 10);
+ add_coin(MIN_CHANGE * 5 / 10);
+
+ // try making 1 * MIN_CHANGE from the 1.5 * MIN_CHANGE
+ // we'll get change smaller than MIN_CHANGE whatever happens, so can expect MIN_CHANGE exactly
+ BOOST_CHECK( testWallet.SelectCoinsMinConf(MIN_CHANGE, filter_confirmed, GroupCoins(vCoins), setCoinsRet, nValueRet, coin_selection_params, bnb_used));
+ BOOST_CHECK_EQUAL(nValueRet, MIN_CHANGE);
+
+ // but if we add a bigger coin, small change is avoided
+ add_coin(1111*MIN_CHANGE);
+
+ // try making 1 from 0.1 + 0.2 + 0.3 + 0.4 + 0.5 + 1111 = 1112.5
+ BOOST_CHECK( testWallet.SelectCoinsMinConf(1 * MIN_CHANGE, filter_confirmed, GroupCoins(vCoins), setCoinsRet, nValueRet, coin_selection_params, bnb_used));
+ BOOST_CHECK_EQUAL(nValueRet, 1 * MIN_CHANGE); // we should get the exact amount
+
+ // if we add more small coins:
+ add_coin(MIN_CHANGE * 6 / 10);
+ add_coin(MIN_CHANGE * 7 / 10);
+
+ // and try again to make 1.0 * MIN_CHANGE
+ BOOST_CHECK( testWallet.SelectCoinsMinConf(1 * MIN_CHANGE, filter_confirmed, GroupCoins(vCoins), setCoinsRet, nValueRet, coin_selection_params, bnb_used));
+ BOOST_CHECK_EQUAL(nValueRet, 1 * MIN_CHANGE); // we should get the exact amount
+
+ // run the 'mtgox' test (see http://blockexplorer.com/tx/29a3efd3ef04f9153d47a990bd7b048a4b2d213daaa5fb8ed670fb85f13bdbcf)
+ // they tried to consolidate 10 50k coins into one 500k coin, and ended up with 50k in change
+ empty_wallet();
+ for (int j = 0; j < 20; j++)
+ add_coin(50000 * COIN);
+
+ BOOST_CHECK( testWallet.SelectCoinsMinConf(500000 * COIN, filter_confirmed, GroupCoins(vCoins), setCoinsRet, nValueRet, coin_selection_params, bnb_used));
+ BOOST_CHECK_EQUAL(nValueRet, 500000 * COIN); // we should get the exact amount
+ BOOST_CHECK_EQUAL(setCoinsRet.size(), 10U); // in ten coins
+
+ // if there's not enough in the smaller coins to make at least 1 * MIN_CHANGE change (0.5+0.6+0.7 < 1.0+1.0),
+ // we need to try finding an exact subset anyway
+
+ // sometimes it will fail, and so we use the next biggest coin:
+ empty_wallet();
+ add_coin(MIN_CHANGE * 5 / 10);
+ add_coin(MIN_CHANGE * 6 / 10);
+ add_coin(MIN_CHANGE * 7 / 10);
+ add_coin(1111 * MIN_CHANGE);
+ BOOST_CHECK( testWallet.SelectCoinsMinConf(1 * MIN_CHANGE, filter_confirmed, GroupCoins(vCoins), setCoinsRet, nValueRet, coin_selection_params, bnb_used));
+ BOOST_CHECK_EQUAL(nValueRet, 1111 * MIN_CHANGE); // we get the bigger coin
+ BOOST_CHECK_EQUAL(setCoinsRet.size(), 1U);
+
+ // but sometimes it's possible, and we use an exact subset (0.4 + 0.6 = 1.0)
+ empty_wallet();
+ add_coin(MIN_CHANGE * 4 / 10);
+ add_coin(MIN_CHANGE * 6 / 10);
+ add_coin(MIN_CHANGE * 8 / 10);
+ add_coin(1111 * MIN_CHANGE);
+ BOOST_CHECK( testWallet.SelectCoinsMinConf(MIN_CHANGE, filter_confirmed, GroupCoins(vCoins), setCoinsRet, nValueRet, coin_selection_params, bnb_used));
+ BOOST_CHECK_EQUAL(nValueRet, MIN_CHANGE); // we should get the exact amount
+ BOOST_CHECK_EQUAL(setCoinsRet.size(), 2U); // in two coins 0.4+0.6
+
+ // test avoiding small change
+ empty_wallet();
+ add_coin(MIN_CHANGE * 5 / 100);
+ add_coin(MIN_CHANGE * 1);
+ add_coin(MIN_CHANGE * 100);
+
+ // trying to make 100.01 from these three coins
+ BOOST_CHECK(testWallet.SelectCoinsMinConf(MIN_CHANGE * 10001 / 100, filter_confirmed, GroupCoins(vCoins), setCoinsRet, nValueRet, coin_selection_params, bnb_used));
+ BOOST_CHECK_EQUAL(nValueRet, MIN_CHANGE * 10105 / 100); // we should get all coins
+ BOOST_CHECK_EQUAL(setCoinsRet.size(), 3U);
+
+ // but if we try to make 99.9, we should take the bigger of the two small coins to avoid small change
+ BOOST_CHECK(testWallet.SelectCoinsMinConf(MIN_CHANGE * 9990 / 100, filter_confirmed, GroupCoins(vCoins), setCoinsRet, nValueRet, coin_selection_params, bnb_used));
+ BOOST_CHECK_EQUAL(nValueRet, 101 * MIN_CHANGE);
+ BOOST_CHECK_EQUAL(setCoinsRet.size(), 2U);
+
+ // test with many inputs
+ for (CAmount amt=1500; amt < COIN; amt*=10) {
+ empty_wallet();
+ // Create 676 inputs (= (old MAX_STANDARD_TX_SIZE == 100000) / 148 bytes per input)
+ for (uint16_t j = 0; j < 676; j++)
+ add_coin(amt);
+ BOOST_CHECK(testWallet.SelectCoinsMinConf(2000, filter_confirmed, GroupCoins(vCoins), setCoinsRet, nValueRet, coin_selection_params, bnb_used));
+ if (amt - 2000 < MIN_CHANGE) {
+ // needs more than one input:
+ uint16_t returnSize = std::ceil((2000.0 + MIN_CHANGE)/amt);
+ CAmount returnValue = amt * returnSize;
+ BOOST_CHECK_EQUAL(nValueRet, returnValue);
+ BOOST_CHECK_EQUAL(setCoinsRet.size(), returnSize);
+ } else {
+ // one input is sufficient:
+ BOOST_CHECK_EQUAL(nValueRet, amt);
+ BOOST_CHECK_EQUAL(setCoinsRet.size(), 1U);
+ }
+ }
+
+ // test randomness
+ {
+ empty_wallet();
+ for (int i2 = 0; i2 < 100; i2++)
+ add_coin(COIN);
+
+ // picking 50 from 100 coins doesn't depend on the shuffle,
+ // but does depend on randomness in the stochastic approximation code
+ BOOST_CHECK(testWallet.SelectCoinsMinConf(50 * COIN, filter_standard, GroupCoins(vCoins), setCoinsRet , nValueRet, coin_selection_params, bnb_used));
+ BOOST_CHECK(testWallet.SelectCoinsMinConf(50 * COIN, filter_standard, GroupCoins(vCoins), setCoinsRet2, nValueRet, coin_selection_params, bnb_used));
+ BOOST_CHECK(!equal_sets(setCoinsRet, setCoinsRet2));
+
+ int fails = 0;
+ for (int j = 0; j < RANDOM_REPEATS; j++)
+ {
+ // selecting 1 from 100 identical coins depends on the shuffle; this test will fail 1% of the time
+ // run the test RANDOM_REPEATS times and only complain if all of them fail
+ BOOST_CHECK(testWallet.SelectCoinsMinConf(COIN, filter_standard, GroupCoins(vCoins), setCoinsRet , nValueRet, coin_selection_params, bnb_used));
+ BOOST_CHECK(testWallet.SelectCoinsMinConf(COIN, filter_standard, GroupCoins(vCoins), setCoinsRet2, nValueRet, coin_selection_params, bnb_used));
+ if (equal_sets(setCoinsRet, setCoinsRet2))
+ fails++;
+ }
+ BOOST_CHECK_NE(fails, RANDOM_REPEATS);
+
+ // add 75 cents in small change. not enough to make 90 cents,
+ // then try making 90 cents. there are multiple competing "smallest bigger" coins,
+ // one of which should be picked at random
+ add_coin(5 * CENT);
+ add_coin(10 * CENT);
+ add_coin(15 * CENT);
+ add_coin(20 * CENT);
+ add_coin(25 * CENT);
+
+ fails = 0;
+ for (int j = 0; j < RANDOM_REPEATS; j++)
+ {
+ // selecting 1 from 100 identical coins depends on the shuffle; this test will fail 1% of the time
+ // run the test RANDOM_REPEATS times and only complain if all of them fail
+ BOOST_CHECK(testWallet.SelectCoinsMinConf(90*CENT, filter_standard, GroupCoins(vCoins), setCoinsRet , nValueRet, coin_selection_params, bnb_used));
+ BOOST_CHECK(testWallet.SelectCoinsMinConf(90*CENT, filter_standard, GroupCoins(vCoins), setCoinsRet2, nValueRet, coin_selection_params, bnb_used));
+ if (equal_sets(setCoinsRet, setCoinsRet2))
+ fails++;
+ }
+ BOOST_CHECK_NE(fails, RANDOM_REPEATS);
+ }
+ }
+ empty_wallet();
+}
+
+BOOST_AUTO_TEST_CASE(ApproximateBestSubset)
+{
+ CoinSet setCoinsRet;
+ CAmount nValueRet;
+ bool bnb_used;
+
+ LOCK(testWallet.cs_wallet);
+
+ empty_wallet();
+
+ // Test vValue sort order
+ for (int i = 0; i < 1000; i++)
+ add_coin(1000 * COIN);
+ add_coin(3 * COIN);
+
+ BOOST_CHECK(testWallet.SelectCoinsMinConf(1003 * COIN, filter_standard, GroupCoins(vCoins), setCoinsRet, nValueRet, coin_selection_params, bnb_used));
+ BOOST_CHECK_EQUAL(nValueRet, 1003 * COIN);
+ BOOST_CHECK_EQUAL(setCoinsRet.size(), 2U);
+
+ empty_wallet();
+}
+
+// Tests that with the ideal conditions, the coin selector will always be able to find a solution that can pay the target value
+BOOST_AUTO_TEST_CASE(SelectCoins_test)
+{
+ // Random generator stuff
+ std::default_random_engine generator;
+ std::exponential_distribution<double> distribution (100);
+ FastRandomContext rand;
+
+ // Run this test 100 times
+ for (int i = 0; i < 100; ++i)
+ {
+ empty_wallet();
+
+ // Make a wallet with 1000 exponentially distributed random inputs
+ for (int j = 0; j < 1000; ++j)
+ {
+ add_coin((CAmount)(distribution(generator)*10000000));
+ }
+
+ // Generate a random fee rate in the range of 100 - 400
+ CFeeRate rate(rand.randrange(300) + 100);
+
+ // Generate a random target value between 1000 and wallet balance
+ CAmount target = rand.randrange(balance - 1000) + 1000;
+
+ // Perform selection
+ CoinSelectionParams coin_selection_params_knapsack(false, 34, 148, CFeeRate(0), 0);
+ CoinSelectionParams coin_selection_params_bnb(true, 34, 148, CFeeRate(0), 0);
+ CoinSet out_set;
+ CAmount out_value = 0;
+ bool bnb_used = false;
+ BOOST_CHECK(testWallet.SelectCoinsMinConf(target, filter_standard, GroupCoins(vCoins), out_set, out_value, coin_selection_params_bnb, bnb_used) ||
+ testWallet.SelectCoinsMinConf(target, filter_standard, GroupCoins(vCoins), out_set, out_value, coin_selection_params_knapsack, bnb_used));
+ BOOST_CHECK_GE(out_value, target);
+ }
+}
+
+BOOST_AUTO_TEST_SUITE_END()
diff --git a/src/wallet/test/crypto_tests.cpp b/src/wallet/test/crypto_tests.cpp
deleted file mode 100644
index 05387f5f2b..0000000000
--- a/src/wallet/test/crypto_tests.cpp
+++ /dev/null
@@ -1,230 +0,0 @@
-// Copyright (c) 2014 The Bitcoin Core developers
-// Distributed under the MIT software license, see the accompanying
-// file COPYING or http://www.opensource.org/licenses/mit-license.php.
-
-#include "random.h"
-#include "utilstrencodings.h"
-#include "test/test_bitcoin.h"
-#include "wallet/crypter.h"
-
-#include <vector>
-
-#include <boost/test/unit_test.hpp>
-#include <openssl/aes.h>
-#include <openssl/evp.h>
-
-BOOST_FIXTURE_TEST_SUITE(wallet_crypto, BasicTestingSetup)
-
-bool OldSetKeyFromPassphrase(const SecureString& strKeyData, const std::vector<unsigned char>& chSalt, const unsigned int nRounds, const unsigned int nDerivationMethod, unsigned char* chKey, unsigned char* chIV)
-{
- if (nRounds < 1 || chSalt.size() != WALLET_CRYPTO_SALT_SIZE)
- return false;
-
- int i = 0;
- if (nDerivationMethod == 0)
- i = EVP_BytesToKey(EVP_aes_256_cbc(), EVP_sha512(), &chSalt[0],
- (unsigned char *)&strKeyData[0], strKeyData.size(), nRounds, chKey, chIV);
-
- if (i != (int)WALLET_CRYPTO_KEY_SIZE)
- {
- memory_cleanse(chKey, sizeof(chKey));
- memory_cleanse(chIV, sizeof(chIV));
- return false;
- }
- return true;
-}
-
-bool OldEncrypt(const CKeyingMaterial& vchPlaintext, std::vector<unsigned char> &vchCiphertext, const unsigned char chKey[32], const unsigned char chIV[16])
-{
- // max ciphertext len for a n bytes of plaintext is
- // n + AES_BLOCK_SIZE - 1 bytes
- int nLen = vchPlaintext.size();
- int nCLen = nLen + AES_BLOCK_SIZE, nFLen = 0;
- vchCiphertext = std::vector<unsigned char> (nCLen);
-
- EVP_CIPHER_CTX ctx;
-
- bool fOk = true;
-
- EVP_CIPHER_CTX_init(&ctx);
- if (fOk) fOk = EVP_EncryptInit_ex(&ctx, EVP_aes_256_cbc(), NULL, chKey, chIV) != 0;
- if (fOk) fOk = EVP_EncryptUpdate(&ctx, &vchCiphertext[0], &nCLen, &vchPlaintext[0], nLen) != 0;
- if (fOk) fOk = EVP_EncryptFinal_ex(&ctx, (&vchCiphertext[0]) + nCLen, &nFLen) != 0;
- EVP_CIPHER_CTX_cleanup(&ctx);
-
- if (!fOk) return false;
-
- vchCiphertext.resize(nCLen + nFLen);
- return true;
-}
-
-bool OldDecrypt(const std::vector<unsigned char>& vchCiphertext, CKeyingMaterial& vchPlaintext, const unsigned char chKey[32], const unsigned char chIV[16])
-{
- // plaintext will always be equal to or lesser than length of ciphertext
- int nLen = vchCiphertext.size();
- int nPLen = nLen, nFLen = 0;
-
- vchPlaintext = CKeyingMaterial(nPLen);
-
- EVP_CIPHER_CTX ctx;
-
- bool fOk = true;
-
- EVP_CIPHER_CTX_init(&ctx);
- if (fOk) fOk = EVP_DecryptInit_ex(&ctx, EVP_aes_256_cbc(), NULL, chKey, chIV) != 0;
- if (fOk) fOk = EVP_DecryptUpdate(&ctx, &vchPlaintext[0], &nPLen, &vchCiphertext[0], nLen) != 0;
- if (fOk) fOk = EVP_DecryptFinal_ex(&ctx, (&vchPlaintext[0]) + nPLen, &nFLen) != 0;
- EVP_CIPHER_CTX_cleanup(&ctx);
-
- if (!fOk) return false;
-
- vchPlaintext.resize(nPLen + nFLen);
- return true;
-}
-
-class TestCrypter
-{
-public:
-static void TestPassphraseSingle(const std::vector<unsigned char>& vchSalt, const SecureString& passphrase, uint32_t rounds,
- const std::vector<unsigned char>& correctKey = std::vector<unsigned char>(),
- const std::vector<unsigned char>& correctIV=std::vector<unsigned char>())
-{
- unsigned char chKey[WALLET_CRYPTO_KEY_SIZE];
- unsigned char chIV[WALLET_CRYPTO_IV_SIZE];
-
- CCrypter crypt;
- crypt.SetKeyFromPassphrase(passphrase, vchSalt, rounds, 0);
-
- OldSetKeyFromPassphrase(passphrase, vchSalt, rounds, 0, chKey, chIV);
-
- BOOST_CHECK_MESSAGE(memcmp(chKey, crypt.chKey, sizeof(chKey)) == 0, \
- HexStr(chKey, chKey+sizeof(chKey)) + std::string(" != ") + HexStr(crypt.chKey, crypt.chKey + (sizeof crypt.chKey)));
- BOOST_CHECK_MESSAGE(memcmp(chIV, crypt.chIV, sizeof(chIV)) == 0, \
- HexStr(chIV, chIV+sizeof(chIV)) + std::string(" != ") + HexStr(crypt.chIV, crypt.chIV + (sizeof crypt.chIV)));
-
- if(!correctKey.empty())
- BOOST_CHECK_MESSAGE(memcmp(chKey, &correctKey[0], sizeof(chKey)) == 0, \
- HexStr(chKey, chKey+sizeof(chKey)) + std::string(" != ") + HexStr(correctKey.begin(), correctKey.end()));
- if(!correctIV.empty())
- BOOST_CHECK_MESSAGE(memcmp(chIV, &correctIV[0], sizeof(chIV)) == 0,
- HexStr(chIV, chIV+sizeof(chIV)) + std::string(" != ") + HexStr(correctIV.begin(), correctIV.end()));
-}
-
-static void TestPassphrase(const std::vector<unsigned char>& vchSalt, const SecureString& passphrase, uint32_t rounds,
- const std::vector<unsigned char>& correctKey = std::vector<unsigned char>(),
- const std::vector<unsigned char>& correctIV=std::vector<unsigned char>())
-{
- TestPassphraseSingle(vchSalt, passphrase, rounds, correctKey, correctIV);
- for(SecureString::const_iterator i(passphrase.begin()); i != passphrase.end(); ++i)
- TestPassphraseSingle(vchSalt, SecureString(i, passphrase.end()), rounds);
-}
-
-
-static void TestDecrypt(const CCrypter& crypt, const std::vector<unsigned char>& vchCiphertext, \
- const std::vector<unsigned char>& vchPlaintext = std::vector<unsigned char>())
-{
- CKeyingMaterial vchDecrypted1;
- CKeyingMaterial vchDecrypted2;
- int result1, result2;
- result1 = crypt.Decrypt(vchCiphertext, vchDecrypted1);
- result2 = OldDecrypt(vchCiphertext, vchDecrypted2, crypt.chKey, crypt.chIV);
- BOOST_CHECK(result1 == result2);
-
- // These two should be equal. However, OpenSSL 1.0.1j introduced a change
- // that would zero all padding except for the last byte for failed decrypts.
- // This behavior was reverted for 1.0.1k.
- if (vchDecrypted1 != vchDecrypted2 && vchDecrypted1.size() >= AES_BLOCK_SIZE && SSLeay() == 0x100010afL)
- {
- for(CKeyingMaterial::iterator it = vchDecrypted1.end() - AES_BLOCK_SIZE; it != vchDecrypted1.end() - 1; it++)
- *it = 0;
- }
-
- BOOST_CHECK_MESSAGE(vchDecrypted1 == vchDecrypted2, HexStr(vchDecrypted1.begin(), vchDecrypted1.end()) + " != " + HexStr(vchDecrypted2.begin(), vchDecrypted2.end()));
-
- if (vchPlaintext.size())
- BOOST_CHECK(CKeyingMaterial(vchPlaintext.begin(), vchPlaintext.end()) == vchDecrypted2);
-}
-
-static void TestEncryptSingle(const CCrypter& crypt, const CKeyingMaterial& vchPlaintext,
- const std::vector<unsigned char>& vchCiphertextCorrect = std::vector<unsigned char>())
-{
- std::vector<unsigned char> vchCiphertext1;
- std::vector<unsigned char> vchCiphertext2;
- int result1 = crypt.Encrypt(vchPlaintext, vchCiphertext1);
-
- int result2 = OldEncrypt(vchPlaintext, vchCiphertext2, crypt.chKey, crypt.chIV);
- BOOST_CHECK(result1 == result2);
- BOOST_CHECK(vchCiphertext1 == vchCiphertext2);
-
- if (!vchCiphertextCorrect.empty())
- BOOST_CHECK(vchCiphertext2 == vchCiphertextCorrect);
-
- const std::vector<unsigned char> vchPlaintext2(vchPlaintext.begin(), vchPlaintext.end());
-
- if(vchCiphertext1 == vchCiphertext2)
- TestDecrypt(crypt, vchCiphertext1, vchPlaintext2);
-}
-
-static void TestEncrypt(const CCrypter& crypt, const std::vector<unsigned char>& vchPlaintextIn, \
- const std::vector<unsigned char>& vchCiphertextCorrect = std::vector<unsigned char>())
-{
- TestEncryptSingle(crypt, CKeyingMaterial(vchPlaintextIn.begin(), vchPlaintextIn.end()), vchCiphertextCorrect);
- for(std::vector<unsigned char>::const_iterator i(vchPlaintextIn.begin()); i != vchPlaintextIn.end(); ++i)
- TestEncryptSingle(crypt, CKeyingMaterial(i, vchPlaintextIn.end()));
-}
-
-};
-
-BOOST_AUTO_TEST_CASE(passphrase) {
- // These are expensive.
-
- TestCrypter::TestPassphrase(ParseHex("0000deadbeef0000"), "test", 25000, \
- ParseHex("fc7aba077ad5f4c3a0988d8daa4810d0d4a0e3bcb53af662998898f33df0556a"), \
- ParseHex("cf2f2691526dd1aa220896fb8bf7c369"));
-
- std::string hash(GetRandHash().ToString());
- std::vector<unsigned char> vchSalt(8);
- GetRandBytes(&vchSalt[0], vchSalt.size());
- uint32_t rounds = insecure_rand();
- if (rounds > 30000)
- rounds = 30000;
- TestCrypter::TestPassphrase(vchSalt, SecureString(hash.begin(), hash.end()), rounds);
-}
-
-BOOST_AUTO_TEST_CASE(encrypt) {
- std::vector<unsigned char> vchSalt = ParseHex("0000deadbeef0000");
- BOOST_CHECK(vchSalt.size() == WALLET_CRYPTO_SALT_SIZE);
- CCrypter crypt;
- crypt.SetKeyFromPassphrase("passphrase", vchSalt, 25000, 0);
- TestCrypter::TestEncrypt(crypt, ParseHex("22bcade09ac03ff6386914359cfe885cfeb5f77ff0d670f102f619687453b29d"));
-
- for (int i = 0; i != 100; i++)
- {
- uint256 hash(GetRandHash());
- TestCrypter::TestEncrypt(crypt, std::vector<unsigned char>(hash.begin(), hash.end()));
- }
-
-}
-
-BOOST_AUTO_TEST_CASE(decrypt) {
- std::vector<unsigned char> vchSalt = ParseHex("0000deadbeef0000");
- BOOST_CHECK(vchSalt.size() == WALLET_CRYPTO_SALT_SIZE);
- CCrypter crypt;
- crypt.SetKeyFromPassphrase("passphrase", vchSalt, 25000, 0);
-
- // Some corner cases the came up while testing
- TestCrypter::TestDecrypt(crypt,ParseHex("795643ce39d736088367822cdc50535ec6f103715e3e48f4f3b1a60a08ef59ca"));
- TestCrypter::TestDecrypt(crypt,ParseHex("de096f4a8f9bd97db012aa9d90d74de8cdea779c3ee8bc7633d8b5d6da703486"));
- TestCrypter::TestDecrypt(crypt,ParseHex("32d0a8974e3afd9c6c3ebf4d66aa4e6419f8c173de25947f98cf8b7ace49449c"));
- TestCrypter::TestDecrypt(crypt,ParseHex("e7c055cca2faa78cb9ac22c9357a90b4778ded9b2cc220a14cea49f931e596ea"));
- TestCrypter::TestDecrypt(crypt,ParseHex("b88efddd668a6801d19516d6830da4ae9811988ccbaf40df8fbb72f3f4d335fd"));
- TestCrypter::TestDecrypt(crypt,ParseHex("8cae76aa6a43694e961ebcb28c8ca8f8540b84153d72865e8561ddd93fa7bfa9"));
-
- for (int i = 0; i != 100; i++)
- {
- uint256 hash(GetRandHash());
- TestCrypter::TestDecrypt(crypt, std::vector<unsigned char>(hash.begin(), hash.end()));
- }
-}
-
-BOOST_AUTO_TEST_SUITE_END()
diff --git a/src/wallet/test/psbt_wallet_tests.cpp b/src/wallet/test/psbt_wallet_tests.cpp
new file mode 100644
index 0000000000..61c3fa94a6
--- /dev/null
+++ b/src/wallet/test/psbt_wallet_tests.cpp
@@ -0,0 +1,150 @@
+// Copyright (c) 2017-2018 The Bitcoin Core developers
+// Distributed under the MIT software license, see the accompanying
+// file COPYING or http://www.opensource.org/licenses/mit-license.php.
+
+#include <key_io.h>
+#include <script/sign.h>
+#include <utilstrencodings.h>
+#include <wallet/rpcwallet.h>
+#include <wallet/wallet.h>
+#include <univalue.h>
+
+#include <boost/test/unit_test.hpp>
+#include <test/test_bitcoin.h>
+#include <wallet/test/wallet_test_fixture.h>
+
+extern bool ParseHDKeypath(std::string keypath_str, std::vector<uint32_t>& keypath);
+
+BOOST_FIXTURE_TEST_SUITE(psbt_wallet_tests, WalletTestingSetup)
+
+BOOST_AUTO_TEST_CASE(psbt_updater_test)
+{
+ // Create prevtxs and add to wallet
+ CDataStream s_prev_tx1(ParseHex("0200000000010158e87a21b56daf0c23be8e7070456c336f7cbaa5c8757924f545887bb2abdd7501000000171600145f275f436b09a8cc9a2eb2a2f528485c68a56323feffffff02d8231f1b0100000017a914aed962d6654f9a2b36608eb9d64d2b260db4f1118700c2eb0b0000000017a914b7f5faf40e3d40a5a459b1db3535f2b72fa921e88702483045022100a22edcc6e5bc511af4cc4ae0de0fcd75c7e04d8c1c3a8aa9d820ed4b967384ec02200642963597b9b1bc22c75e9f3e117284a962188bf5e8a74c895089046a20ad770121035509a48eb623e10aace8bfd0212fdb8a8e5af3c94b0b133b95e114cab89e4f7965000000"), SER_NETWORK, PROTOCOL_VERSION);
+ CTransactionRef prev_tx1;
+ s_prev_tx1 >> prev_tx1;
+ CWalletTx prev_wtx1(&m_wallet, prev_tx1);
+ m_wallet.mapWallet.emplace(prev_wtx1.GetHash(), std::move(prev_wtx1));
+
+ CDataStream s_prev_tx2(ParseHex("0200000001aad73931018bd25f84ae400b68848be09db706eac2ac18298babee71ab656f8b0000000048473044022058f6fc7c6a33e1b31548d481c826c015bd30135aad42cd67790dab66d2ad243b02204a1ced2604c6735b6393e5b41691dd78b00f0c5942fb9f751856faa938157dba01feffffff0280f0fa020000000017a9140fb9463421696b82c833af241c78c17ddbde493487d0f20a270100000017a91429ca74f8a08f81999428185c97b5d852e4063f618765000000"), SER_NETWORK, PROTOCOL_VERSION);
+ CTransactionRef prev_tx2;
+ s_prev_tx2 >> prev_tx2;
+ CWalletTx prev_wtx2(&m_wallet, prev_tx2);
+ m_wallet.mapWallet.emplace(prev_wtx2.GetHash(), std::move(prev_wtx2));
+
+ // Add scripts
+ CScript rs1;
+ CDataStream s_rs1(ParseHex("475221029583bf39ae0a609747ad199addd634fa6108559d6c5cd39b4c2183f1ab96e07f2102dab61ff49a14db6a7d02b0cd1fbb78fc4b18312b5b4e54dae4dba2fbfef536d752ae"), SER_NETWORK, PROTOCOL_VERSION);
+ s_rs1 >> rs1;
+ m_wallet.AddCScript(rs1);
+
+ CScript rs2;
+ CDataStream s_rs2(ParseHex("2200208c2353173743b595dfb4a07b72ba8e42e3797da74e87fe7d9d7497e3b2028903"), SER_NETWORK, PROTOCOL_VERSION);
+ s_rs2 >> rs2;
+ m_wallet.AddCScript(rs2);
+
+ CScript ws1;
+ CDataStream s_ws1(ParseHex("47522103089dc10c7ac6db54f91329af617333db388cead0c231f723379d1b99030b02dc21023add904f3d6dcf59ddb906b0dee23529b7ffb9ed50e5e86151926860221f0e7352ae"), SER_NETWORK, PROTOCOL_VERSION);
+ s_ws1 >> ws1;
+ m_wallet.AddCScript(ws1);
+
+ // Add hd seed
+ CKey key = DecodeSecret("5KSSJQ7UNfFGwVgpCZDSHm5rVNhMFcFtvWM3zQ8mW4qNDEN7LFd"); // Mainnet and uncompressed form of cUkG8i1RFfWGWy5ziR11zJ5V4U4W3viSFCfyJmZnvQaUsd1xuF3T
+ CPubKey master_pub_key = m_wallet.DeriveNewSeed(key);
+ m_wallet.SetHDSeed(master_pub_key);
+ m_wallet.NewKeyPool();
+
+ // Call FillPSBT
+ PartiallySignedTransaction psbtx;
+ CDataStream ssData(ParseHex("70736274ff01009a020000000258e87a21b56daf0c23be8e7070456c336f7cbaa5c8757924f545887bb2abdd750000000000ffffffff838d0427d0ec650a68aa46bb0b098aea4422c071b2ca78352a077959d07cea1d0100000000ffffffff0270aaf00800000000160014d85c2b71d0060b09c9886aeb815e50991dda124d00e1f5050000000016001400aea9a2e5f0f876a588df5546e8742d1d87008f000000000000000000"), SER_NETWORK, PROTOCOL_VERSION);
+ ssData >> psbtx;
+
+ // Use CTransaction for the constant parts of the
+ // transaction to avoid rehashing.
+ const CTransaction txConst(*psbtx.tx);
+
+ // Fill transaction with our data
+ FillPSBT(&m_wallet, psbtx, &txConst, 1, false, true);
+
+ // Get the final tx
+ CDataStream ssTx(SER_NETWORK, PROTOCOL_VERSION);
+ ssTx << psbtx;
+ std::string final_hex = HexStr(ssTx.begin(), ssTx.end());
+ BOOST_CHECK_EQUAL(final_hex, "70736274ff01009a020000000258e87a21b56daf0c23be8e7070456c336f7cbaa5c8757924f545887bb2abdd750000000000ffffffff838d0427d0ec650a68aa46bb0b098aea4422c071b2ca78352a077959d07cea1d0100000000ffffffff0270aaf00800000000160014d85c2b71d0060b09c9886aeb815e50991dda124d00e1f5050000000016001400aea9a2e5f0f876a588df5546e8742d1d87008f00000000000100bb0200000001aad73931018bd25f84ae400b68848be09db706eac2ac18298babee71ab656f8b0000000048473044022058f6fc7c6a33e1b31548d481c826c015bd30135aad42cd67790dab66d2ad243b02204a1ced2604c6735b6393e5b41691dd78b00f0c5942fb9f751856faa938157dba01feffffff0280f0fa020000000017a9140fb9463421696b82c833af241c78c17ddbde493487d0f20a270100000017a91429ca74f8a08f81999428185c97b5d852e4063f6187650000000104475221029583bf39ae0a609747ad199addd634fa6108559d6c5cd39b4c2183f1ab96e07f2102dab61ff49a14db6a7d02b0cd1fbb78fc4b18312b5b4e54dae4dba2fbfef536d752ae2206029583bf39ae0a609747ad199addd634fa6108559d6c5cd39b4c2183f1ab96e07f10d90c6a4f000000800000008000000080220602dab61ff49a14db6a7d02b0cd1fbb78fc4b18312b5b4e54dae4dba2fbfef536d710d90c6a4f0000008000000080010000800001012000c2eb0b0000000017a914b7f5faf40e3d40a5a459b1db3535f2b72fa921e88701042200208c2353173743b595dfb4a07b72ba8e42e3797da74e87fe7d9d7497e3b2028903010547522103089dc10c7ac6db54f91329af617333db388cead0c231f723379d1b99030b02dc21023add904f3d6dcf59ddb906b0dee23529b7ffb9ed50e5e86151926860221f0e7352ae2206023add904f3d6dcf59ddb906b0dee23529b7ffb9ed50e5e86151926860221f0e7310d90c6a4f000000800000008003000080220603089dc10c7ac6db54f91329af617333db388cead0c231f723379d1b99030b02dc10d90c6a4f00000080000000800200008000220203a9a4c37f5996d3aa25dbac6b570af0650394492942460b354753ed9eeca5877110d90c6a4f000000800000008004000080002202027f6399757d2eff55a136ad02c684b1838b6556e5f1b6b34282a94b6b5005109610d90c6a4f00000080000000800500008000");
+}
+
+BOOST_AUTO_TEST_CASE(parse_hd_keypath)
+{
+ std::vector<uint32_t> keypath;
+
+ BOOST_CHECK(ParseHDKeypath("1/1/1/1/1/1/1/1/1/1/1/1/1/1/1/1/1/1/1/1/1/1/1/1/1/1/1/1", keypath));
+ BOOST_CHECK(!ParseHDKeypath("///////////////////////////", keypath));
+
+ BOOST_CHECK(ParseHDKeypath("1/1/1/1/1/1/1/1/1/1/1/1/1/1/1/1/1/1/1/1/1/1/1/1/1/1/1'/1", keypath));
+ BOOST_CHECK(!ParseHDKeypath("//////////////////////////'/", keypath));
+
+ BOOST_CHECK(ParseHDKeypath("1/1/1/1/1/1/1/1/1/1/1/1/1/1/1/1/1/1/1/1/1/1/1/1/1/1/1/", keypath));
+ BOOST_CHECK(!ParseHDKeypath("1///////////////////////////", keypath));
+
+ BOOST_CHECK(ParseHDKeypath("1/1/1/1/1/1/1/1/1/1/1/1/1/1/1/1/1/1/1/1/1/1/1/1/1/1/1'/", keypath));
+ BOOST_CHECK(!ParseHDKeypath("1/'//////////////////////////", keypath));
+
+ BOOST_CHECK(ParseHDKeypath("", keypath));
+ BOOST_CHECK(!ParseHDKeypath(" ", keypath));
+
+ BOOST_CHECK(ParseHDKeypath("0", keypath));
+ BOOST_CHECK(!ParseHDKeypath("O", keypath));
+
+ BOOST_CHECK(ParseHDKeypath("0000'/0000'/0000'", keypath));
+ BOOST_CHECK(!ParseHDKeypath("0000,/0000,/0000,", keypath));
+
+ BOOST_CHECK(ParseHDKeypath("01234", keypath));
+ BOOST_CHECK(!ParseHDKeypath("0x1234", keypath));
+
+ BOOST_CHECK(ParseHDKeypath("1", keypath));
+ BOOST_CHECK(!ParseHDKeypath(" 1", keypath));
+
+ BOOST_CHECK(ParseHDKeypath("42", keypath));
+ BOOST_CHECK(!ParseHDKeypath("m42", keypath));
+
+ BOOST_CHECK(ParseHDKeypath("4294967295", keypath)); // 4294967295 == 0xFFFFFFFF (uint32_t max)
+ BOOST_CHECK(!ParseHDKeypath("4294967296", keypath)); // 4294967296 == 0xFFFFFFFF (uint32_t max) + 1
+
+ BOOST_CHECK(ParseHDKeypath("m", keypath));
+ BOOST_CHECK(!ParseHDKeypath("n", keypath));
+
+ BOOST_CHECK(ParseHDKeypath("m/", keypath));
+ BOOST_CHECK(!ParseHDKeypath("n/", keypath));
+
+ BOOST_CHECK(ParseHDKeypath("m/0", keypath));
+ BOOST_CHECK(!ParseHDKeypath("n/0", keypath));
+
+ BOOST_CHECK(ParseHDKeypath("m/0'", keypath));
+ BOOST_CHECK(!ParseHDKeypath("m/0''", keypath));
+
+ BOOST_CHECK(ParseHDKeypath("m/0'/0'", keypath));
+ BOOST_CHECK(!ParseHDKeypath("m/'0/0'", keypath));
+
+ BOOST_CHECK(ParseHDKeypath("m/0/0", keypath));
+ BOOST_CHECK(!ParseHDKeypath("n/0/0", keypath));
+
+ BOOST_CHECK(ParseHDKeypath("m/0/0/00", keypath));
+ BOOST_CHECK(!ParseHDKeypath("m/0/0/f00", keypath));
+
+ BOOST_CHECK(ParseHDKeypath("m/0/0/000000000000000000000000000000000000000000000000000000000000000000000000000000000000", keypath));
+ BOOST_CHECK(!ParseHDKeypath("m/1/1/111111111111111111111111111111111111111111111111111111111111111111111111111111111111", keypath));
+
+ BOOST_CHECK(ParseHDKeypath("m/0/00/0", keypath));
+ BOOST_CHECK(!ParseHDKeypath("m/0'/00/'0", keypath));
+
+ BOOST_CHECK(ParseHDKeypath("m/1/", keypath));
+ BOOST_CHECK(!ParseHDKeypath("m/1//", keypath));
+
+ BOOST_CHECK(ParseHDKeypath("m/0/4294967295", keypath)); // 4294967295 == 0xFFFFFFFF (uint32_t max)
+ BOOST_CHECK(!ParseHDKeypath("m/0/4294967296", keypath)); // 4294967296 == 0xFFFFFFFF (uint32_t max) + 1
+
+ BOOST_CHECK(ParseHDKeypath("m/4294967295", keypath)); // 4294967295 == 0xFFFFFFFF (uint32_t max)
+ BOOST_CHECK(!ParseHDKeypath("m/4294967296", keypath)); // 4294967296 == 0xFFFFFFFF (uint32_t max) + 1
+}
+
+BOOST_AUTO_TEST_SUITE_END()
diff --git a/src/wallet/test/wallet_crypto_tests.cpp b/src/wallet/test/wallet_crypto_tests.cpp
new file mode 100644
index 0000000000..f193d5c41d
--- /dev/null
+++ b/src/wallet/test/wallet_crypto_tests.cpp
@@ -0,0 +1,126 @@
+// Copyright (c) 2014-2018 The Bitcoin Core developers
+// Distributed under the MIT software license, see the accompanying
+// file COPYING or http://www.opensource.org/licenses/mit-license.php.
+
+#include <test/test_bitcoin.h>
+#include <utilstrencodings.h>
+#include <wallet/crypter.h>
+
+#include <vector>
+
+#include <boost/test/unit_test.hpp>
+
+BOOST_FIXTURE_TEST_SUITE(wallet_crypto_tests, BasicTestingSetup)
+
+class TestCrypter
+{
+public:
+static void TestPassphraseSingle(const std::vector<unsigned char>& vchSalt, const SecureString& passphrase, uint32_t rounds,
+ const std::vector<unsigned char>& correctKey = std::vector<unsigned char>(),
+ const std::vector<unsigned char>& correctIV=std::vector<unsigned char>())
+{
+ CCrypter crypt;
+ crypt.SetKeyFromPassphrase(passphrase, vchSalt, rounds, 0);
+
+ if(!correctKey.empty())
+ BOOST_CHECK_MESSAGE(memcmp(crypt.vchKey.data(), correctKey.data(), crypt.vchKey.size()) == 0, \
+ HexStr(crypt.vchKey.begin(), crypt.vchKey.end()) + std::string(" != ") + HexStr(correctKey.begin(), correctKey.end()));
+ if(!correctIV.empty())
+ BOOST_CHECK_MESSAGE(memcmp(crypt.vchIV.data(), correctIV.data(), crypt.vchIV.size()) == 0,
+ HexStr(crypt.vchIV.begin(), crypt.vchIV.end()) + std::string(" != ") + HexStr(correctIV.begin(), correctIV.end()));
+}
+
+static void TestPassphrase(const std::vector<unsigned char>& vchSalt, const SecureString& passphrase, uint32_t rounds,
+ const std::vector<unsigned char>& correctKey = std::vector<unsigned char>(),
+ const std::vector<unsigned char>& correctIV=std::vector<unsigned char>())
+{
+ TestPassphraseSingle(vchSalt, passphrase, rounds, correctKey, correctIV);
+ for(SecureString::const_iterator i(passphrase.begin()); i != passphrase.end(); ++i)
+ TestPassphraseSingle(vchSalt, SecureString(i, passphrase.end()), rounds);
+}
+
+static void TestDecrypt(const CCrypter& crypt, const std::vector<unsigned char>& vchCiphertext, \
+ const std::vector<unsigned char>& vchPlaintext = std::vector<unsigned char>())
+{
+ CKeyingMaterial vchDecrypted;
+ crypt.Decrypt(vchCiphertext, vchDecrypted);
+ if (vchPlaintext.size())
+ BOOST_CHECK(CKeyingMaterial(vchPlaintext.begin(), vchPlaintext.end()) == vchDecrypted);
+}
+
+static void TestEncryptSingle(const CCrypter& crypt, const CKeyingMaterial& vchPlaintext,
+ const std::vector<unsigned char>& vchCiphertextCorrect = std::vector<unsigned char>())
+{
+ std::vector<unsigned char> vchCiphertext;
+ crypt.Encrypt(vchPlaintext, vchCiphertext);
+
+ if (!vchCiphertextCorrect.empty())
+ BOOST_CHECK(vchCiphertext == vchCiphertextCorrect);
+
+ const std::vector<unsigned char> vchPlaintext2(vchPlaintext.begin(), vchPlaintext.end());
+ TestDecrypt(crypt, vchCiphertext, vchPlaintext2);
+}
+
+static void TestEncrypt(const CCrypter& crypt, const std::vector<unsigned char>& vchPlaintextIn, \
+ const std::vector<unsigned char>& vchCiphertextCorrect = std::vector<unsigned char>())
+{
+ TestEncryptSingle(crypt, CKeyingMaterial(vchPlaintextIn.begin(), vchPlaintextIn.end()), vchCiphertextCorrect);
+ for(std::vector<unsigned char>::const_iterator i(vchPlaintextIn.begin()); i != vchPlaintextIn.end(); ++i)
+ TestEncryptSingle(crypt, CKeyingMaterial(i, vchPlaintextIn.end()));
+}
+
+};
+
+BOOST_AUTO_TEST_CASE(passphrase) {
+ // These are expensive.
+
+ TestCrypter::TestPassphrase(ParseHex("0000deadbeef0000"), "test", 25000, \
+ ParseHex("fc7aba077ad5f4c3a0988d8daa4810d0d4a0e3bcb53af662998898f33df0556a"), \
+ ParseHex("cf2f2691526dd1aa220896fb8bf7c369"));
+
+ std::string hash(GetRandHash().ToString());
+ std::vector<unsigned char> vchSalt(8);
+ GetRandBytes(vchSalt.data(), vchSalt.size());
+ uint32_t rounds = InsecureRand32();
+ if (rounds > 30000)
+ rounds = 30000;
+ TestCrypter::TestPassphrase(vchSalt, SecureString(hash.begin(), hash.end()), rounds);
+}
+
+BOOST_AUTO_TEST_CASE(encrypt) {
+ std::vector<unsigned char> vchSalt = ParseHex("0000deadbeef0000");
+ BOOST_CHECK(vchSalt.size() == WALLET_CRYPTO_SALT_SIZE);
+ CCrypter crypt;
+ crypt.SetKeyFromPassphrase("passphrase", vchSalt, 25000, 0);
+ TestCrypter::TestEncrypt(crypt, ParseHex("22bcade09ac03ff6386914359cfe885cfeb5f77ff0d670f102f619687453b29d"));
+
+ for (int i = 0; i != 100; i++)
+ {
+ uint256 hash(GetRandHash());
+ TestCrypter::TestEncrypt(crypt, std::vector<unsigned char>(hash.begin(), hash.end()));
+ }
+
+}
+
+BOOST_AUTO_TEST_CASE(decrypt) {
+ std::vector<unsigned char> vchSalt = ParseHex("0000deadbeef0000");
+ BOOST_CHECK(vchSalt.size() == WALLET_CRYPTO_SALT_SIZE);
+ CCrypter crypt;
+ crypt.SetKeyFromPassphrase("passphrase", vchSalt, 25000, 0);
+
+ // Some corner cases the came up while testing
+ TestCrypter::TestDecrypt(crypt,ParseHex("795643ce39d736088367822cdc50535ec6f103715e3e48f4f3b1a60a08ef59ca"));
+ TestCrypter::TestDecrypt(crypt,ParseHex("de096f4a8f9bd97db012aa9d90d74de8cdea779c3ee8bc7633d8b5d6da703486"));
+ TestCrypter::TestDecrypt(crypt,ParseHex("32d0a8974e3afd9c6c3ebf4d66aa4e6419f8c173de25947f98cf8b7ace49449c"));
+ TestCrypter::TestDecrypt(crypt,ParseHex("e7c055cca2faa78cb9ac22c9357a90b4778ded9b2cc220a14cea49f931e596ea"));
+ TestCrypter::TestDecrypt(crypt,ParseHex("b88efddd668a6801d19516d6830da4ae9811988ccbaf40df8fbb72f3f4d335fd"));
+ TestCrypter::TestDecrypt(crypt,ParseHex("8cae76aa6a43694e961ebcb28c8ca8f8540b84153d72865e8561ddd93fa7bfa9"));
+
+ for (int i = 0; i != 100; i++)
+ {
+ uint256 hash(GetRandHash());
+ TestCrypter::TestDecrypt(crypt, std::vector<unsigned char>(hash.begin(), hash.end()));
+ }
+}
+
+BOOST_AUTO_TEST_SUITE_END()
diff --git a/src/wallet/test/wallet_test_fixture.cpp b/src/wallet/test/wallet_test_fixture.cpp
index a76db37617..de59b60349 100644
--- a/src/wallet/test/wallet_test_fixture.cpp
+++ b/src/wallet/test/wallet_test_fixture.cpp
@@ -1,32 +1,23 @@
-// Copyright (c) 2016 The Bitcoin Core developers
+// Copyright (c) 2016-2018 The Bitcoin Core developers
// Distributed under the MIT software license, see the accompanying
// file COPYING or http://www.opensource.org/licenses/mit-license.php.
-#include "wallet/test/wallet_test_fixture.h"
+#include <wallet/test/wallet_test_fixture.h>
-#include "rpc/server.h"
-#include "wallet/db.h"
-#include "wallet/wallet.h"
+#include <rpc/server.h>
+#include <wallet/db.h>
WalletTestingSetup::WalletTestingSetup(const std::string& chainName):
- TestingSetup(chainName)
+ TestingSetup(chainName), m_wallet("mock", WalletDatabase::CreateMock())
{
- bitdb.MakeMock();
-
bool fFirstRun;
- pwalletMain = new CWallet("wallet_test.dat");
- pwalletMain->LoadWallet(fFirstRun);
- RegisterValidationInterface(pwalletMain);
+ m_wallet.LoadWallet(fFirstRun);
+ RegisterValidationInterface(&m_wallet);
RegisterWalletRPCCommands(tableRPC);
}
WalletTestingSetup::~WalletTestingSetup()
{
- UnregisterValidationInterface(pwalletMain);
- delete pwalletMain;
- pwalletMain = NULL;
-
- bitdb.Flush(true);
- bitdb.Reset();
+ UnregisterValidationInterface(&m_wallet);
}
diff --git a/src/wallet/test/wallet_test_fixture.h b/src/wallet/test/wallet_test_fixture.h
index 97a6d98397..ff8ec32b03 100644
--- a/src/wallet/test/wallet_test_fixture.h
+++ b/src/wallet/test/wallet_test_fixture.h
@@ -1,18 +1,23 @@
-// Copyright (c) 2016 The Bitcoin Core developers
+// Copyright (c) 2016-2018 The Bitcoin Core developers
// Distributed under the MIT software license, see the accompanying
// file COPYING or http://www.opensource.org/licenses/mit-license.php.
-#ifndef BITCOIN_WALLET_TEST_FIXTURE_H
-#define BITCOIN_WALLET_TEST_FIXTURE_H
+#ifndef BITCOIN_WALLET_TEST_WALLET_TEST_FIXTURE_H
+#define BITCOIN_WALLET_TEST_WALLET_TEST_FIXTURE_H
-#include "test/test_bitcoin.h"
+#include <test/test_bitcoin.h>
+
+#include <wallet/wallet.h>
+
+#include <memory>
/** Testing setup and teardown for wallet.
*/
struct WalletTestingSetup: public TestingSetup {
- WalletTestingSetup(const std::string& chainName = CBaseChainParams::MAIN);
+ explicit WalletTestingSetup(const std::string& chainName = CBaseChainParams::MAIN);
~WalletTestingSetup();
-};
-#endif
+ CWallet m_wallet;
+};
+#endif // BITCOIN_WALLET_TEST_WALLET_TEST_FIXTURE_H
diff --git a/src/wallet/test/wallet_tests.cpp b/src/wallet/test/wallet_tests.cpp
index c6c5058984..f44311b0be 100644
--- a/src/wallet/test/wallet_tests.cpp
+++ b/src/wallet/test/wallet_tests.cpp
@@ -1,354 +1,377 @@
-// Copyright (c) 2012-2015 The Bitcoin Core developers
+// Copyright (c) 2012-2018 The Bitcoin Core developers
// Distributed under the MIT software license, see the accompanying
// file COPYING or http://www.opensource.org/licenses/mit-license.php.
-#include "wallet/wallet.h"
+#include <wallet/wallet.h>
+#include <memory>
#include <set>
#include <stdint.h>
#include <utility>
#include <vector>
-#include "wallet/test/wallet_test_fixture.h"
+#include <consensus/validation.h>
+#include <rpc/server.h>
+#include <test/test_bitcoin.h>
+#include <validation.h>
+#include <wallet/coincontrol.h>
+#include <wallet/test/wallet_test_fixture.h>
-#include <boost/foreach.hpp>
#include <boost/test/unit_test.hpp>
+#include <univalue.h>
-// how many times to run all the tests to have a chance to catch errors that only show up with particular random shuffles
-#define RUN_TESTS 100
+extern UniValue importmulti(const JSONRPCRequest& request);
+extern UniValue dumpwallet(const JSONRPCRequest& request);
+extern UniValue importwallet(const JSONRPCRequest& request);
-// some tests fail 1% of the time due to bad luck.
-// we repeat those tests this many times and only complain if all iterations of the test fail
-#define RANDOM_REPEATS 5
+BOOST_FIXTURE_TEST_SUITE(wallet_tests, WalletTestingSetup)
+
+static void AddKey(CWallet& wallet, const CKey& key)
+{
+ LOCK(wallet.cs_wallet);
+ wallet.AddKeyPubKey(key, key.GetPubKey());
+}
-using namespace std;
+BOOST_FIXTURE_TEST_CASE(rescan, TestChain100Setup)
+{
+ // Cap last block file size, and mine new block in a new block file.
+ CBlockIndex* const nullBlock = nullptr;
+ CBlockIndex* oldTip = chainActive.Tip();
+ GetBlockFileInfo(oldTip->GetBlockPos().nFile)->nSize = MAX_BLOCKFILE_SIZE;
+ CreateAndProcessBlock({}, GetScriptForRawPubKey(coinbaseKey.GetPubKey()));
+ CBlockIndex* newTip = chainActive.Tip();
-typedef set<pair<const CWalletTx*,unsigned int> > CoinSet;
+ LOCK(cs_main);
-BOOST_FIXTURE_TEST_SUITE(wallet_tests, WalletTestingSetup)
+ // Verify ScanForWalletTransactions picks up transactions in both the old
+ // and new block files.
+ {
+ CWallet wallet("dummy", WalletDatabase::CreateDummy());
+ AddKey(wallet, coinbaseKey);
+ WalletRescanReserver reserver(&wallet);
+ reserver.reserve();
+ BOOST_CHECK_EQUAL(nullBlock, wallet.ScanForWalletTransactions(oldTip, nullptr, reserver));
+ BOOST_CHECK_EQUAL(wallet.GetImmatureBalance(), 100 * COIN);
+ }
+
+ // Prune the older block file.
+ PruneOneBlockFile(oldTip->GetBlockPos().nFile);
+ UnlinkPrunedFiles({oldTip->GetBlockPos().nFile});
+
+ // Verify ScanForWalletTransactions only picks transactions in the new block
+ // file.
+ {
+ CWallet wallet("dummy", WalletDatabase::CreateDummy());
+ AddKey(wallet, coinbaseKey);
+ WalletRescanReserver reserver(&wallet);
+ reserver.reserve();
+ BOOST_CHECK_EQUAL(oldTip, wallet.ScanForWalletTransactions(oldTip, nullptr, reserver));
+ BOOST_CHECK_EQUAL(wallet.GetImmatureBalance(), 50 * COIN);
+ }
-static const CWallet wallet;
-static vector<COutput> vCoins;
+ // Verify importmulti RPC returns failure for a key whose creation time is
+ // before the missing block, and success for a key whose creation time is
+ // after.
+ {
+ std::shared_ptr<CWallet> wallet = std::make_shared<CWallet>("dummy", WalletDatabase::CreateDummy());
+ AddWallet(wallet);
+ UniValue keys;
+ keys.setArray();
+ UniValue key;
+ key.setObject();
+ key.pushKV("scriptPubKey", HexStr(GetScriptForRawPubKey(coinbaseKey.GetPubKey())));
+ key.pushKV("timestamp", 0);
+ key.pushKV("internal", UniValue(true));
+ keys.push_back(key);
+ key.clear();
+ key.setObject();
+ CKey futureKey;
+ futureKey.MakeNewKey(true);
+ key.pushKV("scriptPubKey", HexStr(GetScriptForRawPubKey(futureKey.GetPubKey())));
+ key.pushKV("timestamp", newTip->GetBlockTimeMax() + TIMESTAMP_WINDOW + 1);
+ key.pushKV("internal", UniValue(true));
+ keys.push_back(key);
+ JSONRPCRequest request;
+ request.params.setArray();
+ request.params.push_back(keys);
+
+ UniValue response = importmulti(request);
+ BOOST_CHECK_EQUAL(response.write(),
+ strprintf("[{\"success\":false,\"error\":{\"code\":-1,\"message\":\"Rescan failed for key with creation "
+ "timestamp %d. There was an error reading a block from time %d, which is after or within %d "
+ "seconds of key creation, and could contain transactions pertaining to the key. As a result, "
+ "transactions and coins using this key may not appear in the wallet. This error could be caused "
+ "by pruning or data corruption (see bitcoind log for details) and could be dealt with by "
+ "downloading and rescanning the relevant blocks (see -reindex and -rescan "
+ "options).\"}},{\"success\":true}]",
+ 0, oldTip->GetBlockTimeMax(), TIMESTAMP_WINDOW));
+ RemoveWallet(wallet);
+ }
+}
-static void add_coin(const CAmount& nValue, int nAge = 6*24, bool fIsFromMe = false, int nInput=0)
+// Verify importwallet RPC starts rescan at earliest block with timestamp
+// greater or equal than key birthday. Previously there was a bug where
+// importwallet RPC would start the scan at the latest block with timestamp less
+// than or equal to key birthday.
+BOOST_FIXTURE_TEST_CASE(importwallet_rescan, TestChain100Setup)
+{
+ // Create two blocks with same timestamp to verify that importwallet rescan
+ // will pick up both blocks, not just the first.
+ const int64_t BLOCK_TIME = chainActive.Tip()->GetBlockTimeMax() + 5;
+ SetMockTime(BLOCK_TIME);
+ m_coinbase_txns.emplace_back(CreateAndProcessBlock({}, GetScriptForRawPubKey(coinbaseKey.GetPubKey())).vtx[0]);
+ m_coinbase_txns.emplace_back(CreateAndProcessBlock({}, GetScriptForRawPubKey(coinbaseKey.GetPubKey())).vtx[0]);
+
+ // Set key birthday to block time increased by the timestamp window, so
+ // rescan will start at the block time.
+ const int64_t KEY_TIME = BLOCK_TIME + TIMESTAMP_WINDOW;
+ SetMockTime(KEY_TIME);
+ m_coinbase_txns.emplace_back(CreateAndProcessBlock({}, GetScriptForRawPubKey(coinbaseKey.GetPubKey())).vtx[0]);
+
+ LOCK(cs_main);
+
+ std::string backup_file = (SetDataDir("importwallet_rescan") / "wallet.backup").string();
+
+ // Import key into wallet and call dumpwallet to create backup file.
+ {
+ std::shared_ptr<CWallet> wallet = std::make_shared<CWallet>("dummy", WalletDatabase::CreateDummy());
+ LOCK(wallet->cs_wallet);
+ wallet->mapKeyMetadata[coinbaseKey.GetPubKey().GetID()].nCreateTime = KEY_TIME;
+ wallet->AddKeyPubKey(coinbaseKey, coinbaseKey.GetPubKey());
+
+ JSONRPCRequest request;
+ request.params.setArray();
+ request.params.push_back(backup_file);
+ AddWallet(wallet);
+ ::dumpwallet(request);
+ RemoveWallet(wallet);
+ }
+
+ // Call importwallet RPC and verify all blocks with timestamps >= BLOCK_TIME
+ // were scanned, and no prior blocks were scanned.
+ {
+ std::shared_ptr<CWallet> wallet = std::make_shared<CWallet>("dummy", WalletDatabase::CreateDummy());
+
+ JSONRPCRequest request;
+ request.params.setArray();
+ request.params.push_back(backup_file);
+ AddWallet(wallet);
+ ::importwallet(request);
+ RemoveWallet(wallet);
+
+ LOCK(wallet->cs_wallet);
+ BOOST_CHECK_EQUAL(wallet->mapWallet.size(), 3U);
+ BOOST_CHECK_EQUAL(m_coinbase_txns.size(), 103U);
+ for (size_t i = 0; i < m_coinbase_txns.size(); ++i) {
+ bool found = wallet->GetWalletTx(m_coinbase_txns[i]->GetHash());
+ bool expected = i >= 100;
+ BOOST_CHECK_EQUAL(found, expected);
+ }
+ }
+
+ SetMockTime(0);
+}
+
+// Check that GetImmatureCredit() returns a newly calculated value instead of
+// the cached value after a MarkDirty() call.
+//
+// This is a regression test written to verify a bugfix for the immature credit
+// function. Similar tests probably should be written for the other credit and
+// debit functions.
+BOOST_FIXTURE_TEST_CASE(coin_mark_dirty_immature_credit, TestChain100Setup)
+{
+ CWallet wallet("dummy", WalletDatabase::CreateDummy());
+ CWalletTx wtx(&wallet, m_coinbase_txns.back());
+ LOCK2(cs_main, wallet.cs_wallet);
+ wtx.hashBlock = chainActive.Tip()->GetBlockHash();
+ wtx.nIndex = 0;
+
+ // 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(), 0);
+
+ // 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);
+}
+
+static int64_t AddTx(CWallet& wallet, uint32_t lockTime, int64_t mockTime, int64_t blockTime)
{
- static int nextLockTime = 0;
CMutableTransaction tx;
- tx.nLockTime = nextLockTime++; // so all transactions get different hashes
- tx.vout.resize(nInput+1);
- tx.vout[nInput].nValue = nValue;
- if (fIsFromMe) {
- // IsFromMe() returns (GetDebit() > 0), and GetDebit() is 0 if vin.empty(),
- // so stop vin being empty, and cache a non-zero Debit to fake out IsFromMe()
- tx.vin.resize(1);
+ tx.nLockTime = lockTime;
+ SetMockTime(mockTime);
+ CBlockIndex* block = nullptr;
+ if (blockTime > 0) {
+ LOCK(cs_main);
+ auto inserted = mapBlockIndex.emplace(GetRandHash(), new CBlockIndex);
+ assert(inserted.second);
+ const uint256& hash = inserted.first->first;
+ block = inserted.first->second;
+ block->nTime = blockTime;
+ block->phashBlock = &hash;
+ }
+
+ CWalletTx wtx(&wallet, MakeTransactionRef(tx));
+ if (block) {
+ wtx.SetMerkleBranch(block, 0);
}
- CWalletTx* wtx = new CWalletTx(&wallet, tx);
- if (fIsFromMe)
{
- wtx->fDebitCached = true;
- wtx->nDebitCached = 1;
+ LOCK(cs_main);
+ wallet.AddToWallet(wtx);
}
- COutput output(wtx, nInput, nAge, true, true);
- vCoins.push_back(output);
+ LOCK(wallet.cs_wallet);
+ return wallet.mapWallet.at(wtx.GetHash()).nTimeSmart;
}
-static void empty_wallet(void)
+// Simple test to verify assignment of CWalletTx::nSmartTime value. Could be
+// expanded to cover more corner cases of smart time logic.
+BOOST_AUTO_TEST_CASE(ComputeTimeSmart)
{
- BOOST_FOREACH(COutput output, vCoins)
- delete output.tx;
- vCoins.clear();
+ // New transaction should use clock time if lower than block time.
+ BOOST_CHECK_EQUAL(AddTx(m_wallet, 1, 100, 120), 100);
+
+ // Test that updating existing transaction does not change smart time.
+ BOOST_CHECK_EQUAL(AddTx(m_wallet, 1, 200, 220), 100);
+
+ // New transaction should use clock time if there's no block time.
+ BOOST_CHECK_EQUAL(AddTx(m_wallet, 2, 300, 0), 300);
+
+ // New transaction should use block time if lower than clock time.
+ BOOST_CHECK_EQUAL(AddTx(m_wallet, 3, 420, 400), 400);
+
+ // New transaction should use latest entry time if higher than
+ // min(block time, clock time).
+ BOOST_CHECK_EQUAL(AddTx(m_wallet, 4, 500, 390), 400);
+
+ // If there are future entries, new transaction should use time of the
+ // newest entry that is no more than 300 seconds ahead of the clock time.
+ BOOST_CHECK_EQUAL(AddTx(m_wallet, 5, 50, 600), 300);
+
+ // Reset mock time for other tests.
+ SetMockTime(0);
}
-static bool equal_sets(CoinSet a, CoinSet b)
+BOOST_AUTO_TEST_CASE(LoadReceiveRequests)
{
- pair<CoinSet::iterator, CoinSet::iterator> ret = mismatch(a.begin(), a.end(), b.begin());
- return ret.first == a.end() && ret.second == b.end();
+ CTxDestination dest = CKeyID();
+ LOCK(m_wallet.cs_wallet);
+ m_wallet.AddDestData(dest, "misc", "val_misc");
+ m_wallet.AddDestData(dest, "rr0", "val_rr0");
+ m_wallet.AddDestData(dest, "rr1", "val_rr1");
+
+ auto values = m_wallet.GetDestValues("rr");
+ BOOST_CHECK_EQUAL(values.size(), 2U);
+ BOOST_CHECK_EQUAL(values[0], "val_rr0");
+ BOOST_CHECK_EQUAL(values[1], "val_rr1");
}
-BOOST_AUTO_TEST_CASE(coin_selection_tests)
+class ListCoinsTestingSetup : public TestChain100Setup
{
- CoinSet setCoinsRet, setCoinsRet2;
- CAmount nValueRet;
+public:
+ ListCoinsTestingSetup()
+ {
+ CreateAndProcessBlock({}, GetScriptForRawPubKey(coinbaseKey.GetPubKey()));
+ wallet = MakeUnique<CWallet>("mock", WalletDatabase::CreateMock());
+ bool firstRun;
+ wallet->LoadWallet(firstRun);
+ AddKey(*wallet, coinbaseKey);
+ WalletRescanReserver reserver(wallet.get());
+ reserver.reserve();
+ wallet->ScanForWalletTransactions(chainActive.Genesis(), nullptr, reserver);
+ }
- LOCK(wallet.cs_wallet);
+ ~ListCoinsTestingSetup()
+ {
+ wallet.reset();
+ }
- // test multiple times to allow for differences in the shuffle order
- for (int i = 0; i < RUN_TESTS; i++)
+ CWalletTx& AddTx(CRecipient recipient)
{
- empty_wallet();
-
- // with an empty wallet we can't even pay one cent
- BOOST_CHECK(!wallet.SelectCoinsMinConf( 1 * CENT, 1, 6, vCoins, setCoinsRet, nValueRet));
-
- add_coin(1*CENT, 4); // add a new 1 cent coin
-
- // with a new 1 cent coin, we still can't find a mature 1 cent
- BOOST_CHECK(!wallet.SelectCoinsMinConf( 1 * CENT, 1, 6, vCoins, setCoinsRet, nValueRet));
-
- // but we can find a new 1 cent
- BOOST_CHECK( wallet.SelectCoinsMinConf( 1 * CENT, 1, 1, vCoins, setCoinsRet, nValueRet));
- BOOST_CHECK_EQUAL(nValueRet, 1 * CENT);
-
- add_coin(2*CENT); // add a mature 2 cent coin
-
- // we can't make 3 cents of mature coins
- BOOST_CHECK(!wallet.SelectCoinsMinConf( 3 * CENT, 1, 6, vCoins, setCoinsRet, nValueRet));
-
- // we can make 3 cents of new coins
- BOOST_CHECK( wallet.SelectCoinsMinConf( 3 * CENT, 1, 1, vCoins, setCoinsRet, nValueRet));
- BOOST_CHECK_EQUAL(nValueRet, 3 * CENT);
-
- add_coin(5*CENT); // add a mature 5 cent coin,
- add_coin(10*CENT, 3, true); // a new 10 cent coin sent from one of our own addresses
- add_coin(20*CENT); // and a mature 20 cent coin
-
- // now we have new: 1+10=11 (of which 10 was self-sent), and mature: 2+5+20=27. total = 38
-
- // we can't make 38 cents only if we disallow new coins:
- BOOST_CHECK(!wallet.SelectCoinsMinConf(38 * CENT, 1, 6, vCoins, setCoinsRet, nValueRet));
- // we can't even make 37 cents if we don't allow new coins even if they're from us
- BOOST_CHECK(!wallet.SelectCoinsMinConf(38 * CENT, 6, 6, vCoins, setCoinsRet, nValueRet));
- // but we can make 37 cents if we accept new coins from ourself
- BOOST_CHECK( wallet.SelectCoinsMinConf(37 * CENT, 1, 6, vCoins, setCoinsRet, nValueRet));
- BOOST_CHECK_EQUAL(nValueRet, 37 * CENT);
- // and we can make 38 cents if we accept all new coins
- BOOST_CHECK( wallet.SelectCoinsMinConf(38 * CENT, 1, 1, vCoins, setCoinsRet, nValueRet));
- BOOST_CHECK_EQUAL(nValueRet, 38 * CENT);
-
- // try making 34 cents from 1,2,5,10,20 - we can't do it exactly
- BOOST_CHECK( wallet.SelectCoinsMinConf(34 * CENT, 1, 1, vCoins, setCoinsRet, nValueRet));
- BOOST_CHECK_EQUAL(nValueRet, 35 * CENT); // but 35 cents is closest
- BOOST_CHECK_EQUAL(setCoinsRet.size(), 3U); // the best should be 20+10+5. it's incredibly unlikely the 1 or 2 got included (but possible)
-
- // when we try making 7 cents, the smaller coins (1,2,5) are enough. We should see just 2+5
- BOOST_CHECK( wallet.SelectCoinsMinConf( 7 * CENT, 1, 1, vCoins, setCoinsRet, nValueRet));
- BOOST_CHECK_EQUAL(nValueRet, 7 * CENT);
- BOOST_CHECK_EQUAL(setCoinsRet.size(), 2U);
-
- // when we try making 8 cents, the smaller coins (1,2,5) are exactly enough.
- BOOST_CHECK( wallet.SelectCoinsMinConf( 8 * CENT, 1, 1, vCoins, setCoinsRet, nValueRet));
- BOOST_CHECK(nValueRet == 8 * CENT);
- BOOST_CHECK_EQUAL(setCoinsRet.size(), 3U);
-
- // when we try making 9 cents, no subset of smaller coins is enough, and we get the next bigger coin (10)
- BOOST_CHECK( wallet.SelectCoinsMinConf( 9 * CENT, 1, 1, vCoins, setCoinsRet, nValueRet));
- BOOST_CHECK_EQUAL(nValueRet, 10 * CENT);
- BOOST_CHECK_EQUAL(setCoinsRet.size(), 1U);
-
- // now clear out the wallet and start again to test choosing between subsets of smaller coins and the next biggest coin
- empty_wallet();
-
- add_coin( 6*CENT);
- add_coin( 7*CENT);
- add_coin( 8*CENT);
- add_coin(20*CENT);
- add_coin(30*CENT); // now we have 6+7+8+20+30 = 71 cents total
-
- // check that we have 71 and not 72
- BOOST_CHECK( wallet.SelectCoinsMinConf(71 * CENT, 1, 1, vCoins, setCoinsRet, nValueRet));
- BOOST_CHECK(!wallet.SelectCoinsMinConf(72 * CENT, 1, 1, vCoins, setCoinsRet, nValueRet));
-
- // now try making 16 cents. the best smaller coins can do is 6+7+8 = 21; not as good at the next biggest coin, 20
- BOOST_CHECK( wallet.SelectCoinsMinConf(16 * CENT, 1, 1, vCoins, setCoinsRet, nValueRet));
- BOOST_CHECK_EQUAL(nValueRet, 20 * CENT); // we should get 20 in one coin
- BOOST_CHECK_EQUAL(setCoinsRet.size(), 1U);
-
- add_coin( 5*CENT); // now we have 5+6+7+8+20+30 = 75 cents total
-
- // now if we try making 16 cents again, the smaller coins can make 5+6+7 = 18 cents, better than the next biggest coin, 20
- BOOST_CHECK( wallet.SelectCoinsMinConf(16 * CENT, 1, 1, vCoins, setCoinsRet, nValueRet));
- BOOST_CHECK_EQUAL(nValueRet, 18 * CENT); // we should get 18 in 3 coins
- BOOST_CHECK_EQUAL(setCoinsRet.size(), 3U);
-
- add_coin( 18*CENT); // now we have 5+6+7+8+18+20+30
-
- // and now if we try making 16 cents again, the smaller coins can make 5+6+7 = 18 cents, the same as the next biggest coin, 18
- BOOST_CHECK( wallet.SelectCoinsMinConf(16 * CENT, 1, 1, vCoins, setCoinsRet, nValueRet));
- BOOST_CHECK_EQUAL(nValueRet, 18 * CENT); // we should get 18 in 1 coin
- BOOST_CHECK_EQUAL(setCoinsRet.size(), 1U); // because in the event of a tie, the biggest coin wins
-
- // now try making 11 cents. we should get 5+6
- BOOST_CHECK( wallet.SelectCoinsMinConf(11 * CENT, 1, 1, vCoins, setCoinsRet, nValueRet));
- BOOST_CHECK_EQUAL(nValueRet, 11 * CENT);
- BOOST_CHECK_EQUAL(setCoinsRet.size(), 2U);
-
- // check that the smallest bigger coin is used
- add_coin( 1*COIN);
- add_coin( 2*COIN);
- add_coin( 3*COIN);
- add_coin( 4*COIN); // now we have 5+6+7+8+18+20+30+100+200+300+400 = 1094 cents
- BOOST_CHECK( wallet.SelectCoinsMinConf(95 * CENT, 1, 1, vCoins, setCoinsRet, nValueRet));
- BOOST_CHECK_EQUAL(nValueRet, 1 * COIN); // we should get 1 BTC in 1 coin
- BOOST_CHECK_EQUAL(setCoinsRet.size(), 1U);
-
- BOOST_CHECK( wallet.SelectCoinsMinConf(195 * CENT, 1, 1, vCoins, setCoinsRet, nValueRet));
- BOOST_CHECK_EQUAL(nValueRet, 2 * COIN); // we should get 2 BTC in 1 coin
- BOOST_CHECK_EQUAL(setCoinsRet.size(), 1U);
-
- // empty the wallet and start again, now with fractions of a cent, to test small change avoidance
-
- empty_wallet();
- add_coin(MIN_CHANGE * 1 / 10);
- add_coin(MIN_CHANGE * 2 / 10);
- add_coin(MIN_CHANGE * 3 / 10);
- add_coin(MIN_CHANGE * 4 / 10);
- add_coin(MIN_CHANGE * 5 / 10);
-
- // try making 1 * MIN_CHANGE from the 1.5 * MIN_CHANGE
- // we'll get change smaller than MIN_CHANGE whatever happens, so can expect MIN_CHANGE exactly
- BOOST_CHECK( wallet.SelectCoinsMinConf(MIN_CHANGE, 1, 1, vCoins, setCoinsRet, nValueRet));
- BOOST_CHECK_EQUAL(nValueRet, MIN_CHANGE);
-
- // but if we add a bigger coin, small change is avoided
- add_coin(1111*MIN_CHANGE);
-
- // try making 1 from 0.1 + 0.2 + 0.3 + 0.4 + 0.5 + 1111 = 1112.5
- BOOST_CHECK( wallet.SelectCoinsMinConf(1 * MIN_CHANGE, 1, 1, vCoins, setCoinsRet, nValueRet));
- BOOST_CHECK_EQUAL(nValueRet, 1 * MIN_CHANGE); // we should get the exact amount
-
- // if we add more small coins:
- add_coin(MIN_CHANGE * 6 / 10);
- add_coin(MIN_CHANGE * 7 / 10);
-
- // and try again to make 1.0 * MIN_CHANGE
- BOOST_CHECK( wallet.SelectCoinsMinConf(1 * MIN_CHANGE, 1, 1, vCoins, setCoinsRet, nValueRet));
- BOOST_CHECK_EQUAL(nValueRet, 1 * MIN_CHANGE); // we should get the exact amount
-
- // run the 'mtgox' test (see http://blockexplorer.com/tx/29a3efd3ef04f9153d47a990bd7b048a4b2d213daaa5fb8ed670fb85f13bdbcf)
- // they tried to consolidate 10 50k coins into one 500k coin, and ended up with 50k in change
- empty_wallet();
- for (int i = 0; i < 20; i++)
- add_coin(50000 * COIN);
-
- BOOST_CHECK( wallet.SelectCoinsMinConf(500000 * COIN, 1, 1, vCoins, setCoinsRet, nValueRet));
- BOOST_CHECK_EQUAL(nValueRet, 500000 * COIN); // we should get the exact amount
- BOOST_CHECK_EQUAL(setCoinsRet.size(), 10U); // in ten coins
-
- // if there's not enough in the smaller coins to make at least 1 * MIN_CHANGE change (0.5+0.6+0.7 < 1.0+1.0),
- // we need to try finding an exact subset anyway
-
- // sometimes it will fail, and so we use the next biggest coin:
- empty_wallet();
- add_coin(MIN_CHANGE * 5 / 10);
- add_coin(MIN_CHANGE * 6 / 10);
- add_coin(MIN_CHANGE * 7 / 10);
- add_coin(1111 * MIN_CHANGE);
- BOOST_CHECK( wallet.SelectCoinsMinConf(1 * MIN_CHANGE, 1, 1, vCoins, setCoinsRet, nValueRet));
- BOOST_CHECK_EQUAL(nValueRet, 1111 * MIN_CHANGE); // we get the bigger coin
- BOOST_CHECK_EQUAL(setCoinsRet.size(), 1U);
-
- // but sometimes it's possible, and we use an exact subset (0.4 + 0.6 = 1.0)
- empty_wallet();
- add_coin(MIN_CHANGE * 4 / 10);
- add_coin(MIN_CHANGE * 6 / 10);
- add_coin(MIN_CHANGE * 8 / 10);
- add_coin(1111 * MIN_CHANGE);
- BOOST_CHECK( wallet.SelectCoinsMinConf(MIN_CHANGE, 1, 1, vCoins, setCoinsRet, nValueRet));
- BOOST_CHECK_EQUAL(nValueRet, MIN_CHANGE); // we should get the exact amount
- BOOST_CHECK_EQUAL(setCoinsRet.size(), 2U); // in two coins 0.4+0.6
-
- // test avoiding small change
- empty_wallet();
- add_coin(MIN_CHANGE * 5 / 100);
- add_coin(MIN_CHANGE * 1);
- add_coin(MIN_CHANGE * 100);
-
- // trying to make 100.01 from these three coins
- BOOST_CHECK(wallet.SelectCoinsMinConf(MIN_CHANGE * 10001 / 100, 1, 1, vCoins, setCoinsRet, nValueRet));
- BOOST_CHECK_EQUAL(nValueRet, MIN_CHANGE * 10105 / 100); // we should get all coins
- BOOST_CHECK_EQUAL(setCoinsRet.size(), 3U);
-
- // but if we try to make 99.9, we should take the bigger of the two small coins to avoid small change
- BOOST_CHECK(wallet.SelectCoinsMinConf(MIN_CHANGE * 9990 / 100, 1, 1, vCoins, setCoinsRet, nValueRet));
- BOOST_CHECK_EQUAL(nValueRet, 101 * MIN_CHANGE);
- BOOST_CHECK_EQUAL(setCoinsRet.size(), 2U);
-
- // test with many inputs
- for (CAmount amt=1500; amt < COIN; amt*=10) {
- empty_wallet();
- // Create 676 inputs (= MAX_STANDARD_TX_SIZE / 148 bytes per input)
- for (uint16_t j = 0; j < 676; j++)
- add_coin(amt);
- BOOST_CHECK(wallet.SelectCoinsMinConf(2000, 1, 1, vCoins, setCoinsRet, nValueRet));
- if (amt - 2000 < MIN_CHANGE) {
- // needs more than one input:
- uint16_t returnSize = std::ceil((2000.0 + MIN_CHANGE)/amt);
- CAmount returnValue = amt * returnSize;
- BOOST_CHECK_EQUAL(nValueRet, returnValue);
- BOOST_CHECK_EQUAL(setCoinsRet.size(), returnSize);
- } else {
- // one input is sufficient:
- BOOST_CHECK_EQUAL(nValueRet, amt);
- BOOST_CHECK_EQUAL(setCoinsRet.size(), 1U);
- }
+ CTransactionRef tx;
+ CReserveKey reservekey(wallet.get());
+ CAmount fee;
+ int changePos = -1;
+ std::string error;
+ CCoinControl dummy;
+ BOOST_CHECK(wallet->CreateTransaction({recipient}, tx, reservekey, fee, changePos, error, dummy));
+ CValidationState state;
+ BOOST_CHECK(wallet->CommitTransaction(tx, {}, {}, {}, reservekey, nullptr, state));
+ CMutableTransaction blocktx;
+ {
+ LOCK(wallet->cs_wallet);
+ blocktx = CMutableTransaction(*wallet->mapWallet.at(tx->GetHash()).tx);
}
+ CreateAndProcessBlock({CMutableTransaction(blocktx)}, GetScriptForRawPubKey(coinbaseKey.GetPubKey()));
+ LOCK(wallet->cs_wallet);
+ auto it = wallet->mapWallet.find(tx->GetHash());
+ BOOST_CHECK(it != wallet->mapWallet.end());
+ it->second.SetMerkleBranch(chainActive.Tip(), 1);
+ return it->second;
+ }
- // test randomness
- {
- empty_wallet();
- for (int i2 = 0; i2 < 100; i2++)
- add_coin(COIN);
-
- // picking 50 from 100 coins doesn't depend on the shuffle,
- // but does depend on randomness in the stochastic approximation code
- BOOST_CHECK(wallet.SelectCoinsMinConf(50 * COIN, 1, 6, vCoins, setCoinsRet , nValueRet));
- BOOST_CHECK(wallet.SelectCoinsMinConf(50 * COIN, 1, 6, vCoins, setCoinsRet2, nValueRet));
- BOOST_CHECK(!equal_sets(setCoinsRet, setCoinsRet2));
-
- int fails = 0;
- for (int i = 0; i < RANDOM_REPEATS; i++)
- {
- // selecting 1 from 100 identical coins depends on the shuffle; this test will fail 1% of the time
- // run the test RANDOM_REPEATS times and only complain if all of them fail
- BOOST_CHECK(wallet.SelectCoinsMinConf(COIN, 1, 6, vCoins, setCoinsRet , nValueRet));
- BOOST_CHECK(wallet.SelectCoinsMinConf(COIN, 1, 6, vCoins, setCoinsRet2, nValueRet));
- if (equal_sets(setCoinsRet, setCoinsRet2))
- fails++;
- }
- BOOST_CHECK_NE(fails, RANDOM_REPEATS);
-
- // add 75 cents in small change. not enough to make 90 cents,
- // then try making 90 cents. there are multiple competing "smallest bigger" coins,
- // one of which should be picked at random
- add_coin(5 * CENT);
- add_coin(10 * CENT);
- add_coin(15 * CENT);
- add_coin(20 * CENT);
- add_coin(25 * CENT);
-
- fails = 0;
- for (int i = 0; i < RANDOM_REPEATS; i++)
- {
- // selecting 1 from 100 identical coins depends on the shuffle; this test will fail 1% of the time
- // run the test RANDOM_REPEATS times and only complain if all of them fail
- BOOST_CHECK(wallet.SelectCoinsMinConf(90*CENT, 1, 6, vCoins, setCoinsRet , nValueRet));
- BOOST_CHECK(wallet.SelectCoinsMinConf(90*CENT, 1, 6, vCoins, setCoinsRet2, nValueRet));
- if (equal_sets(setCoinsRet, setCoinsRet2))
- fails++;
- }
- BOOST_CHECK_NE(fails, RANDOM_REPEATS);
+ std::unique_ptr<CWallet> wallet;
+};
+
+BOOST_FIXTURE_TEST_CASE(ListCoins, ListCoinsTestingSetup)
+{
+ std::string coinbaseAddress = coinbaseKey.GetPubKey().GetID().ToString();
+
+ // Confirm ListCoins initially returns 1 coin grouped under coinbaseKey
+ // address.
+ auto list = wallet->ListCoins();
+ BOOST_CHECK_EQUAL(list.size(), 1U);
+ BOOST_CHECK_EQUAL(boost::get<CKeyID>(list.begin()->first).ToString(), coinbaseAddress);
+ BOOST_CHECK_EQUAL(list.begin()->second.size(), 1U);
+
+ // 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(), 1U);
+ BOOST_CHECK_EQUAL(boost::get<CKeyID>(list.begin()->first).ToString(), coinbaseAddress);
+ BOOST_CHECK_EQUAL(list.begin()->second.size(), 2U);
+
+ // Lock both coins. Confirm number of available coins drops to 0.
+ {
+ LOCK2(cs_main, wallet->cs_wallet);
+ std::vector<COutput> available;
+ wallet->AvailableCoins(available);
+ BOOST_CHECK_EQUAL(available.size(), 2U);
+ }
+ for (const auto& group : list) {
+ for (const auto& coin : group.second) {
+ LOCK(wallet->cs_wallet);
+ wallet->LockCoin(COutPoint(coin.tx->GetHash(), coin.i));
}
}
- empty_wallet();
+ {
+ LOCK2(cs_main, wallet->cs_wallet);
+ std::vector<COutput> available;
+ wallet->AvailableCoins(available);
+ BOOST_CHECK_EQUAL(available.size(), 0U);
+ }
+ // Confirm ListCoins still returns same result as before, despite coins
+ // being locked.
+ list = wallet->ListCoins();
+ BOOST_CHECK_EQUAL(list.size(), 1U);
+ BOOST_CHECK_EQUAL(boost::get<CKeyID>(list.begin()->first).ToString(), coinbaseAddress);
+ BOOST_CHECK_EQUAL(list.begin()->second.size(), 2U);
}
-BOOST_AUTO_TEST_CASE(ApproximateBestSubset)
+BOOST_FIXTURE_TEST_CASE(wallet_disableprivkeys, TestChain100Setup)
{
- CoinSet setCoinsRet;
- CAmount nValueRet;
-
- LOCK(wallet.cs_wallet);
-
- empty_wallet();
-
- // Test vValue sort order
- for (int i = 0; i < 1000; i++)
- add_coin(1000 * COIN);
- add_coin(3 * COIN);
-
- BOOST_CHECK(wallet.SelectCoinsMinConf(1003 * COIN, 1, 6, vCoins, setCoinsRet, nValueRet));
- BOOST_CHECK_EQUAL(nValueRet, 1003 * COIN);
- BOOST_CHECK_EQUAL(setCoinsRet.size(), 2U);
+ std::shared_ptr<CWallet> wallet = std::make_shared<CWallet>("dummy", WalletDatabase::CreateDummy());
+ wallet->SetWalletFlag(WALLET_FLAG_DISABLE_PRIVATE_KEYS);
+ BOOST_CHECK(!wallet->TopUpKeyPool(1000));
+ CPubKey pubkey;
+ BOOST_CHECK(!wallet->GetKeyFromPool(pubkey, false));
}
BOOST_AUTO_TEST_SUITE_END()
diff --git a/src/wallet/wallet.cpp b/src/wallet/wallet.cpp
index 10aca2e499..5a7fdf9a85 100644
--- a/src/wallet/wallet.cpp
+++ b/src/wallet/wallet.cpp
@@ -1,60 +1,96 @@
// Copyright (c) 2009-2010 Satoshi Nakamoto
-// Copyright (c) 2009-2015 The Bitcoin Core developers
+// Copyright (c) 2009-2018 The Bitcoin Core developers
// Distributed under the MIT software license, see the accompanying
// file COPYING or http://www.opensource.org/licenses/mit-license.php.
-#include "wallet/wallet.h"
-
-#include "base58.h"
-#include "checkpoints.h"
-#include "chain.h"
-#include "coincontrol.h"
-#include "consensus/consensus.h"
-#include "consensus/validation.h"
-#include "key.h"
-#include "keystore.h"
-#include "main.h"
-#include "net.h"
-#include "policy/policy.h"
-#include "primitives/block.h"
-#include "primitives/transaction.h"
-#include "script/script.h"
-#include "script/sign.h"
-#include "timedata.h"
-#include "txmempool.h"
-#include "util.h"
-#include "ui_interface.h"
-#include "utilmoneystr.h"
-
+#include <wallet/wallet.h>
+
+#include <checkpoints.h>
+#include <chain.h>
+#include <wallet/coincontrol.h>
+#include <consensus/consensus.h>
+#include <consensus/validation.h>
+#include <fs.h>
+#include <key.h>
+#include <key_io.h>
+#include <keystore.h>
+#include <validation.h>
+#include <net.h>
+#include <policy/fees.h>
+#include <policy/policy.h>
+#include <policy/rbf.h>
+#include <primitives/block.h>
+#include <primitives/transaction.h>
+#include <script/script.h>
+#include <shutdown.h>
+#include <timedata.h>
+#include <txmempool.h>
+#include <utilmoneystr.h>
+#include <wallet/fees.h>
+#include <wallet/walletutil.h>
+
+#include <algorithm>
#include <assert.h>
+#include <future>
#include <boost/algorithm/string/replace.hpp>
-#include <boost/filesystem.hpp>
-#include <boost/thread.hpp>
-using namespace std;
+static const size_t OUTPUT_GROUP_MAX_ENTRIES = 10;
-CWallet* pwalletMain = NULL;
-/** Transaction fee set by the user */
-CFeeRate payTxFee(DEFAULT_TRANSACTION_FEE);
-unsigned int nTxConfirmTarget = DEFAULT_TX_CONFIRM_TARGET;
-bool bSpendZeroConfChange = DEFAULT_SPEND_ZEROCONF_CHANGE;
-bool fSendFreeTransactions = DEFAULT_SEND_FREE_TRANSACTIONS;
+static CCriticalSection cs_wallets;
+static std::vector<std::shared_ptr<CWallet>> vpwallets GUARDED_BY(cs_wallets);
-const char * DEFAULT_WALLET_DAT = "wallet.dat";
-const uint32_t BIP32_HARDENED_KEY_LIMIT = 0x80000000;
+bool AddWallet(const std::shared_ptr<CWallet>& wallet)
+{
+ LOCK(cs_wallets);
+ assert(wallet);
+ std::vector<std::shared_ptr<CWallet>>::const_iterator i = std::find(vpwallets.begin(), vpwallets.end(), wallet);
+ if (i != vpwallets.end()) return false;
+ vpwallets.push_back(wallet);
+ return true;
+}
-/**
- * Fees smaller than this (in satoshi) are considered zero fee (for transaction creation)
- * Override with -mintxfee
- */
-CFeeRate CWallet::minTxFee = CFeeRate(DEFAULT_TRANSACTION_MINFEE);
-/**
- * If fee estimation does not have enough data to provide estimates, use this fee instead.
- * Has no effect if not using fee estimation
- * Override with -fallbackfee
- */
-CFeeRate CWallet::fallbackFee = CFeeRate(DEFAULT_FALLBACK_FEE);
+bool RemoveWallet(const std::shared_ptr<CWallet>& wallet)
+{
+ LOCK(cs_wallets);
+ assert(wallet);
+ std::vector<std::shared_ptr<CWallet>>::iterator i = std::find(vpwallets.begin(), vpwallets.end(), wallet);
+ if (i == vpwallets.end()) return false;
+ vpwallets.erase(i);
+ return true;
+}
+
+bool HasWallets()
+{
+ LOCK(cs_wallets);
+ return !vpwallets.empty();
+}
+
+std::vector<std::shared_ptr<CWallet>> GetWallets()
+{
+ LOCK(cs_wallets);
+ return vpwallets;
+}
+
+std::shared_ptr<CWallet> GetWallet(const std::string& name)
+{
+ LOCK(cs_wallets);
+ for (const std::shared_ptr<CWallet>& wallet : vpwallets) {
+ if (wallet->GetName() == name) return wallet;
+ }
+ return nullptr;
+}
+
+// Custom deleter for shared_ptr<CWallet>.
+static void ReleaseWallet(CWallet* wallet)
+{
+ wallet->WalletLogPrintf("Releasing wallet\n");
+ wallet->BlockUntilSyncedToCurrentChain();
+ wallet->Flush();
+ delete wallet;
+}
+
+const uint32_t BIP32_HARDENED_KEY_LIMIT = 0x80000000;
const uint256 CMerkleTx::ABANDON_HASH(uint256S("0000000000000000000000000000000000000000000000000000000000000001"));
@@ -63,31 +99,85 @@ const uint256 CMerkleTx::ABANDON_HASH(uint256S("00000000000000000000000000000000
* @{
*/
-struct CompareValueOnly
+std::string COutput::ToString() const
{
- bool operator()(const pair<CAmount, pair<const CWalletTx*, unsigned int> >& t1,
- const pair<CAmount, pair<const CWalletTx*, unsigned int> >& t2) const
+ return strprintf("COutput(%s, %d, %d) [%s]", tx->GetHash().ToString(), i, nDepth, FormatMoney(tx->tx->vout[i].nValue));
+}
+
+/** A class to identify which pubkeys a script and a keystore have in common. */
+class CAffectedKeysVisitor : public boost::static_visitor<void> {
+private:
+ const CKeyStore &keystore;
+ std::vector<CKeyID> &vKeys;
+
+public:
+ /**
+ * @param[in] keystoreIn The CKeyStore that is queried for the presence of a pubkey.
+ * @param[out] vKeysIn A vector to which a script's pubkey identifiers are appended if they are in the keystore.
+ */
+ CAffectedKeysVisitor(const CKeyStore &keystoreIn, std::vector<CKeyID> &vKeysIn) : keystore(keystoreIn), vKeys(vKeysIn) {}
+
+ /**
+ * Apply the visitor to each destination in a script, recursively to the redeemscript
+ * in the case of p2sh destinations.
+ * @param[in] script The CScript from which destinations are extracted.
+ * @post Any CKeyIDs that script and keystore have in common are appended to the visitor's vKeys.
+ */
+ void Process(const CScript &script) {
+ txnouttype type;
+ std::vector<CTxDestination> vDest;
+ int nRequired;
+ if (ExtractDestinations(script, type, vDest, nRequired)) {
+ for (const CTxDestination &dest : vDest)
+ boost::apply_visitor(*this, dest);
+ }
+ }
+
+ void operator()(const CKeyID &keyId) {
+ if (keystore.HaveKey(keyId))
+ vKeys.push_back(keyId);
+ }
+
+ void operator()(const CScriptID &scriptId) {
+ CScript script;
+ if (keystore.GetCScript(scriptId, script))
+ Process(script);
+ }
+
+ void operator()(const WitnessV0ScriptHash& scriptID)
{
- return t1.first < t2.first;
+ CScriptID id;
+ CRIPEMD160().Write(scriptID.begin(), 32).Finalize(id.begin());
+ CScript script;
+ if (keystore.GetCScript(id, script)) {
+ Process(script);
+ }
}
-};
-std::string COutput::ToString() const
-{
- return strprintf("COutput(%s, %d, %d) [%s]", tx->GetHash().ToString(), i, nDepth, FormatMoney(tx->vout[i].nValue));
-}
+ void operator()(const WitnessV0KeyHash& keyid)
+ {
+ CKeyID id(keyid);
+ if (keystore.HaveKey(id)) {
+ vKeys.push_back(id);
+ }
+ }
+
+ template<typename X>
+ void operator()(const X &none) {}
+};
const CWalletTx* CWallet::GetWalletTx(const uint256& hash) const
{
LOCK(cs_wallet);
std::map<uint256, CWalletTx>::const_iterator it = mapWallet.find(hash);
if (it == mapWallet.end())
- return NULL;
+ return nullptr;
return &(it->second);
}
-CPubKey CWallet::GenerateNewKey()
+CPubKey CWallet::GenerateNewKey(WalletBatch &batch, bool internal)
{
+ assert(!IsWalletFlagSet(WALLET_FLAG_DISABLE_PRIVATE_KEYS));
AssertLockHeld(cs_wallet); // mapKeyMetadata
bool fCompressed = CanSupportFeature(FEATURE_COMPRPUBKEY); // default to compressed public keys if we want 0.6.0 wallets
@@ -99,117 +189,146 @@ CPubKey CWallet::GenerateNewKey()
// use HD key derivation if HD was enabled during wallet creation
if (IsHDEnabled()) {
- // for now we use a fixed keypath scheme of m/0'/0'/k
- CKey key; //master key seed (256bit)
- CExtKey masterKey; //hd master key
- CExtKey accountKey; //key at m/0'
- CExtKey externalChainChildKey; //key at m/0'/0'
- CExtKey childKey; //key at m/0'/0'/<n>'
-
- // try to get the master key
- if (!GetKey(hdChain.masterKeyID, key))
- throw std::runtime_error(std::string(__func__) + ": Master key not found");
-
- masterKey.SetMaster(key.begin(), key.size());
-
- // derive m/0'
- // use hardened derivation (child keys >= 0x80000000 are hardened after bip32)
- masterKey.Derive(accountKey, BIP32_HARDENED_KEY_LIMIT);
-
- // derive m/0'/0'
- accountKey.Derive(externalChainChildKey, BIP32_HARDENED_KEY_LIMIT);
-
- // derive child key at next index, skip keys already known to the wallet
- do
- {
- // always derive hardened keys
- // childIndex | BIP32_HARDENED_KEY_LIMIT = derive childIndex in hardened child-index-range
- // example: 1 | BIP32_HARDENED_KEY_LIMIT == 0x80000001 == 2147483649
- externalChainChildKey.Derive(childKey, hdChain.nExternalChainCounter | BIP32_HARDENED_KEY_LIMIT);
- metadata.hdKeypath = "m/0'/0'/"+std::to_string(hdChain.nExternalChainCounter)+"'";
- metadata.hdMasterKeyID = hdChain.masterKeyID;
- // increment childkey index
- hdChain.nExternalChainCounter++;
- } while(HaveKey(childKey.key.GetPubKey().GetID()));
- secret = childKey.key;
-
- // update the chain model in the database
- if (!CWalletDB(strWalletFile).WriteHDChain(hdChain))
- throw std::runtime_error(std::string(__func__) + ": Writing HD chain model failed");
+ DeriveNewChildKey(batch, metadata, secret, (CanSupportFeature(FEATURE_HD_SPLIT) ? internal : false));
} else {
secret.MakeNewKey(fCompressed);
}
// Compressed public keys were introduced in version 0.6.0
- if (fCompressed)
+ if (fCompressed) {
SetMinVersion(FEATURE_COMPRPUBKEY);
+ }
CPubKey pubkey = secret.GetPubKey();
assert(secret.VerifyPubKey(pubkey));
mapKeyMetadata[pubkey.GetID()] = metadata;
- if (!nTimeFirstKey || nCreationTime < nTimeFirstKey)
- nTimeFirstKey = nCreationTime;
+ UpdateTimeFirstKey(nCreationTime);
- if (!AddKeyPubKey(secret, pubkey))
+ if (!AddKeyPubKeyWithDB(batch, secret, pubkey)) {
throw std::runtime_error(std::string(__func__) + ": AddKey failed");
+ }
return pubkey;
}
-bool CWallet::AddKeyPubKey(const CKey& secret, const CPubKey &pubkey)
+void CWallet::DeriveNewChildKey(WalletBatch &batch, CKeyMetadata& metadata, CKey& secret, bool internal)
+{
+ // for now we use a fixed keypath scheme of m/0'/0'/k
+ CKey seed; //seed (256bit)
+ CExtKey masterKey; //hd master key
+ CExtKey accountKey; //key at m/0'
+ CExtKey chainChildKey; //key at m/0'/0' (external) or m/0'/1' (internal)
+ CExtKey childKey; //key at m/0'/0'/<n>'
+
+ // try to get the seed
+ if (!GetKey(hdChain.seed_id, seed))
+ throw std::runtime_error(std::string(__func__) + ": seed not found");
+
+ masterKey.SetSeed(seed.begin(), seed.size());
+
+ // derive m/0'
+ // use hardened derivation (child keys >= 0x80000000 are hardened after bip32)
+ masterKey.Derive(accountKey, BIP32_HARDENED_KEY_LIMIT);
+
+ // derive m/0'/0' (external chain) OR m/0'/1' (internal chain)
+ assert(internal ? CanSupportFeature(FEATURE_HD_SPLIT) : true);
+ accountKey.Derive(chainChildKey, BIP32_HARDENED_KEY_LIMIT+(internal ? 1 : 0));
+
+ // derive child key at next index, skip keys already known to the wallet
+ do {
+ // always derive hardened keys
+ // childIndex | BIP32_HARDENED_KEY_LIMIT = derive childIndex in hardened child-index-range
+ // example: 1 | BIP32_HARDENED_KEY_LIMIT == 0x80000001 == 2147483649
+ if (internal) {
+ chainChildKey.Derive(childKey, hdChain.nInternalChainCounter | BIP32_HARDENED_KEY_LIMIT);
+ metadata.hdKeypath = "m/0'/1'/" + std::to_string(hdChain.nInternalChainCounter) + "'";
+ hdChain.nInternalChainCounter++;
+ }
+ else {
+ chainChildKey.Derive(childKey, hdChain.nExternalChainCounter | BIP32_HARDENED_KEY_LIMIT);
+ metadata.hdKeypath = "m/0'/0'/" + std::to_string(hdChain.nExternalChainCounter) + "'";
+ hdChain.nExternalChainCounter++;
+ }
+ } while (HaveKey(childKey.key.GetPubKey().GetID()));
+ secret = childKey.key;
+ metadata.hd_seed_id = hdChain.seed_id;
+ // update the chain model in the database
+ if (!batch.WriteHDChain(hdChain))
+ throw std::runtime_error(std::string(__func__) + ": Writing HD chain model failed");
+}
+
+bool CWallet::AddKeyPubKeyWithDB(WalletBatch &batch, const CKey& secret, const CPubKey &pubkey)
{
AssertLockHeld(cs_wallet); // mapKeyMetadata
- if (!CCryptoKeyStore::AddKeyPubKey(secret, pubkey))
+
+ // CCryptoKeyStore has no concept of wallet databases, but calls AddCryptedKey
+ // which is overridden below. To avoid flushes, the database handle is
+ // tunneled through to it.
+ bool needsDB = !encrypted_batch;
+ if (needsDB) {
+ encrypted_batch = &batch;
+ }
+ if (!CCryptoKeyStore::AddKeyPubKey(secret, pubkey)) {
+ if (needsDB) encrypted_batch = nullptr;
return false;
+ }
+ if (needsDB) encrypted_batch = nullptr;
// check if we need to remove from watch-only
CScript script;
script = GetScriptForDestination(pubkey.GetID());
- if (HaveWatchOnly(script))
+ if (HaveWatchOnly(script)) {
RemoveWatchOnly(script);
+ }
script = GetScriptForRawPubKey(pubkey);
- if (HaveWatchOnly(script))
+ if (HaveWatchOnly(script)) {
RemoveWatchOnly(script);
+ }
- if (!fFileBacked)
- return true;
if (!IsCrypted()) {
- return CWalletDB(strWalletFile).WriteKey(pubkey,
+ return batch.WriteKey(pubkey,
secret.GetPrivKey(),
mapKeyMetadata[pubkey.GetID()]);
}
return true;
}
+bool CWallet::AddKeyPubKey(const CKey& secret, const CPubKey &pubkey)
+{
+ WalletBatch batch(*database);
+ return CWallet::AddKeyPubKeyWithDB(batch, secret, pubkey);
+}
+
bool CWallet::AddCryptedKey(const CPubKey &vchPubKey,
- const vector<unsigned char> &vchCryptedSecret)
+ const std::vector<unsigned char> &vchCryptedSecret)
{
if (!CCryptoKeyStore::AddCryptedKey(vchPubKey, vchCryptedSecret))
return false;
- if (!fFileBacked)
- return true;
{
LOCK(cs_wallet);
- if (pwalletdbEncryption)
- return pwalletdbEncryption->WriteCryptedKey(vchPubKey,
+ if (encrypted_batch)
+ return encrypted_batch->WriteCryptedKey(vchPubKey,
vchCryptedSecret,
mapKeyMetadata[vchPubKey.GetID()]);
else
- return CWalletDB(strWalletFile).WriteCryptedKey(vchPubKey,
+ return WalletBatch(*database).WriteCryptedKey(vchPubKey,
vchCryptedSecret,
mapKeyMetadata[vchPubKey.GetID()]);
}
- return false;
}
-bool CWallet::LoadKeyMetadata(const CPubKey &pubkey, const CKeyMetadata &meta)
+void CWallet::LoadKeyMetadata(const CKeyID& keyID, const CKeyMetadata &meta)
{
AssertLockHeld(cs_wallet); // mapKeyMetadata
- if (meta.nCreateTime && (!nTimeFirstKey || meta.nCreateTime < nTimeFirstKey))
- nTimeFirstKey = meta.nCreateTime;
+ UpdateTimeFirstKey(meta.nCreateTime);
+ mapKeyMetadata[keyID] = meta;
+}
- mapKeyMetadata[pubkey.GetID()] = meta;
- return true;
+void CWallet::LoadScriptMetadata(const CScriptID& script_id, const CKeyMetadata &meta)
+{
+ AssertLockHeld(cs_wallet); // m_script_metadata
+ UpdateTimeFirstKey(meta.nCreateTime);
+ m_script_metadata[script_id] = meta;
}
bool CWallet::LoadCryptedKey(const CPubKey &vchPubKey, const std::vector<unsigned char> &vchCryptedSecret)
@@ -217,13 +336,27 @@ bool CWallet::LoadCryptedKey(const CPubKey &vchPubKey, const std::vector<unsigne
return CCryptoKeyStore::AddCryptedKey(vchPubKey, vchCryptedSecret);
}
+/**
+ * Update wallet first key creation time. This should be called whenever keys
+ * are added to the wallet, with the oldest key creation time.
+ */
+void CWallet::UpdateTimeFirstKey(int64_t nCreateTime)
+{
+ AssertLockHeld(cs_wallet);
+ if (nCreateTime <= 1) {
+ // Cannot determine birthday information, so set the wallet birthday to
+ // the beginning of time.
+ nTimeFirstKey = 1;
+ } else if (!nTimeFirstKey || nCreateTime < nTimeFirstKey) {
+ nTimeFirstKey = nCreateTime;
+ }
+}
+
bool CWallet::AddCScript(const CScript& redeemScript)
{
if (!CCryptoKeyStore::AddCScript(redeemScript))
return false;
- if (!fFileBacked)
- return true;
- return CWalletDB(strWalletFile).WriteCScript(Hash160(redeemScript), redeemScript);
+ return WalletBatch(*database).WriteCScript(Hash160(redeemScript), redeemScript);
}
bool CWallet::LoadCScript(const CScript& redeemScript)
@@ -233,24 +366,28 @@ bool CWallet::LoadCScript(const CScript& redeemScript)
* these. Do not add them to the wallet and warn. */
if (redeemScript.size() > MAX_SCRIPT_ELEMENT_SIZE)
{
- std::string strAddr = CBitcoinAddress(CScriptID(redeemScript)).ToString();
- LogPrintf("%s: Warning: This wallet contains a redeemScript of size %i which exceeds maximum size %i thus can never be redeemed. Do not use address %s.\n",
- __func__, redeemScript.size(), MAX_SCRIPT_ELEMENT_SIZE, strAddr);
+ std::string strAddr = EncodeDestination(CScriptID(redeemScript));
+ WalletLogPrintf("%s: Warning: This wallet contains a redeemScript of size %i which exceeds maximum size %i thus can never be redeemed. Do not use address %s.\n", __func__, redeemScript.size(), MAX_SCRIPT_ELEMENT_SIZE, strAddr);
return true;
}
return CCryptoKeyStore::AddCScript(redeemScript);
}
-bool CWallet::AddWatchOnly(const CScript &dest)
+bool CWallet::AddWatchOnly(const CScript& dest)
{
if (!CCryptoKeyStore::AddWatchOnly(dest))
return false;
- nTimeFirstKey = 1; // No birthday information for watch-only keys.
+ const CKeyMetadata& meta = m_script_metadata[CScriptID(dest)];
+ UpdateTimeFirstKey(meta.nCreateTime);
NotifyWatchonlyChanged(true);
- if (!fFileBacked)
- return true;
- return CWalletDB(strWalletFile).WriteWatchOnly(dest);
+ return WalletBatch(*database).WriteWatchOnly(dest, meta);
+}
+
+bool CWallet::AddWatchOnly(const CScript& dest, int64_t nCreateTime)
+{
+ m_script_metadata[CScriptID(dest)].nCreateTime = nCreateTime;
+ return AddWatchOnly(dest);
}
bool CWallet::RemoveWatchOnly(const CScript &dest)
@@ -260,9 +397,8 @@ bool CWallet::RemoveWatchOnly(const CScript &dest)
return false;
if (!HaveWatchOnly())
NotifyWatchonlyChanged(false);
- if (fFileBacked)
- if (!CWalletDB(strWalletFile).EraseWatchOnly(dest))
- return false;
+ if (!WalletBatch(*database).EraseWatchOnly(dest))
+ return false;
return true;
}
@@ -275,17 +411,17 @@ bool CWallet::LoadWatchOnly(const CScript &dest)
bool CWallet::Unlock(const SecureString& strWalletPassphrase)
{
CCrypter crypter;
- CKeyingMaterial vMasterKey;
+ CKeyingMaterial _vMasterKey;
{
LOCK(cs_wallet);
- BOOST_FOREACH(const MasterKeyMap::value_type& pMasterKey, mapMasterKeys)
+ for (const MasterKeyMap::value_type& pMasterKey : mapMasterKeys)
{
if(!crypter.SetKeyFromPassphrase(strWalletPassphrase, pMasterKey.second.vchSalt, pMasterKey.second.nDeriveIterations, pMasterKey.second.nDerivationMethod))
return false;
- if (!crypter.Decrypt(pMasterKey.second.vchCryptedKey, vMasterKey))
+ if (!crypter.Decrypt(pMasterKey.second.vchCryptedKey, _vMasterKey))
continue; // try another master key
- if (CCryptoKeyStore::Unlock(vMasterKey))
+ if (CCryptoKeyStore::Unlock(_vMasterKey))
return true;
}
}
@@ -301,33 +437,33 @@ bool CWallet::ChangeWalletPassphrase(const SecureString& strOldWalletPassphrase,
Lock();
CCrypter crypter;
- CKeyingMaterial vMasterKey;
- BOOST_FOREACH(MasterKeyMap::value_type& pMasterKey, mapMasterKeys)
+ CKeyingMaterial _vMasterKey;
+ for (MasterKeyMap::value_type& pMasterKey : mapMasterKeys)
{
if(!crypter.SetKeyFromPassphrase(strOldWalletPassphrase, pMasterKey.second.vchSalt, pMasterKey.second.nDeriveIterations, pMasterKey.second.nDerivationMethod))
return false;
- if (!crypter.Decrypt(pMasterKey.second.vchCryptedKey, vMasterKey))
+ if (!crypter.Decrypt(pMasterKey.second.vchCryptedKey, _vMasterKey))
return false;
- if (CCryptoKeyStore::Unlock(vMasterKey))
+ if (CCryptoKeyStore::Unlock(_vMasterKey))
{
int64_t nStartTime = GetTimeMillis();
crypter.SetKeyFromPassphrase(strNewWalletPassphrase, pMasterKey.second.vchSalt, pMasterKey.second.nDeriveIterations, pMasterKey.second.nDerivationMethod);
- pMasterKey.second.nDeriveIterations = pMasterKey.second.nDeriveIterations * (100 / ((double)(GetTimeMillis() - nStartTime)));
+ pMasterKey.second.nDeriveIterations = static_cast<unsigned int>(pMasterKey.second.nDeriveIterations * (100 / ((double)(GetTimeMillis() - nStartTime))));
nStartTime = GetTimeMillis();
crypter.SetKeyFromPassphrase(strNewWalletPassphrase, pMasterKey.second.vchSalt, pMasterKey.second.nDeriveIterations, pMasterKey.second.nDerivationMethod);
- pMasterKey.second.nDeriveIterations = (pMasterKey.second.nDeriveIterations + pMasterKey.second.nDeriveIterations * 100 / ((double)(GetTimeMillis() - nStartTime))) / 2;
+ pMasterKey.second.nDeriveIterations = (pMasterKey.second.nDeriveIterations + static_cast<unsigned int>(pMasterKey.second.nDeriveIterations * 100 / ((double)(GetTimeMillis() - nStartTime)))) / 2;
if (pMasterKey.second.nDeriveIterations < 25000)
pMasterKey.second.nDeriveIterations = 25000;
- LogPrintf("Wallet passphrase changed to an nDeriveIterations of %i\n", pMasterKey.second.nDeriveIterations);
+ WalletLogPrintf("Wallet passphrase changed to an nDeriveIterations of %i\n", pMasterKey.second.nDeriveIterations);
if (!crypter.SetKeyFromPassphrase(strNewWalletPassphrase, pMasterKey.second.vchSalt, pMasterKey.second.nDeriveIterations, pMasterKey.second.nDerivationMethod))
return false;
- if (!crypter.Encrypt(vMasterKey, pMasterKey.second.vchCryptedKey))
+ if (!crypter.Encrypt(_vMasterKey, pMasterKey.second.vchCryptedKey))
return false;
- CWalletDB(strWalletFile).WriteMasterKey(pMasterKey.first, pMasterKey.second);
+ WalletBatch(*database).WriteMasterKey(pMasterKey.first, pMasterKey.second);
if (fWasLocked)
Lock();
return true;
@@ -338,17 +474,17 @@ bool CWallet::ChangeWalletPassphrase(const SecureString& strOldWalletPassphrase,
return false;
}
-void CWallet::SetBestChain(const CBlockLocator& loc)
+void CWallet::ChainStateFlushed(const CBlockLocator& loc)
{
- CWalletDB walletdb(strWalletFile);
- walletdb.WriteBestBlock(loc);
+ WalletBatch batch(*database);
+ batch.WriteBestBlock(loc);
}
-bool CWallet::SetMinVersion(enum WalletFeature nVersion, CWalletDB* pwalletdbIn, bool fExplicit)
+void CWallet::SetMinVersion(enum WalletFeature nVersion, WalletBatch* batch_in, bool fExplicit)
{
LOCK(cs_wallet); // nWalletVersion
if (nWalletVersion >= nVersion)
- return true;
+ return;
// when doing an explicit upgrade, if we pass the max version permitted, upgrade all the way
if (fExplicit && nVersion > nWalletMaxVersion)
@@ -359,16 +495,13 @@ bool CWallet::SetMinVersion(enum WalletFeature nVersion, CWalletDB* pwalletdbIn,
if (nVersion > nWalletMaxVersion)
nWalletMaxVersion = nVersion;
- if (fFileBacked)
{
- CWalletDB* pwalletdb = pwalletdbIn ? pwalletdbIn : new CWalletDB(strWalletFile);
+ WalletBatch* batch = batch_in ? batch_in : new WalletBatch(*database);
if (nWalletVersion > 40000)
- pwalletdb->WriteMinVersion(nWalletVersion);
- if (!pwalletdbIn)
- delete pwalletdb;
+ batch->WriteMinVersion(nWalletVersion);
+ if (!batch_in)
+ delete batch;
}
-
- return true;
}
bool CWallet::SetMaxVersion(int nVersion)
@@ -383,9 +516,9 @@ bool CWallet::SetMaxVersion(int nVersion)
return true;
}
-set<uint256> CWallet::GetConflicts(const uint256& txid) const
+std::set<uint256> CWallet::GetConflicts(const uint256& txid) const
{
- set<uint256> result;
+ std::set<uint256> result;
AssertLockHeld(cs_wallet);
std::map<uint256, CWalletTx>::const_iterator it = mapWallet.find(txid);
@@ -395,102 +528,56 @@ set<uint256> CWallet::GetConflicts(const uint256& txid) const
std::pair<TxSpends::const_iterator, TxSpends::const_iterator> range;
- BOOST_FOREACH(const CTxIn& txin, wtx.vin)
+ for (const CTxIn& txin : wtx.tx->vin)
{
if (mapTxSpends.count(txin.prevout) <= 1)
continue; // No conflict if zero or one spends
range = mapTxSpends.equal_range(txin.prevout);
- for (TxSpends::const_iterator it = range.first; it != range.second; ++it)
- result.insert(it->second);
+ for (TxSpends::const_iterator _it = range.first; _it != range.second; ++_it)
+ result.insert(_it->second);
}
return result;
}
-void CWallet::Flush(bool shutdown)
+bool CWallet::HasWalletSpend(const uint256& txid) const
{
- bitdb.Flush(shutdown);
+ AssertLockHeld(cs_wallet);
+ auto iter = mapTxSpends.lower_bound(COutPoint(txid, 0));
+ return (iter != mapTxSpends.end() && iter->first.hash == txid);
}
-bool CWallet::Verify()
+void CWallet::Flush(bool shutdown)
{
- LogPrintf("Using BerkeleyDB version %s\n", DbEnv::version(0, 0, 0));
- std::string walletFile = GetArg("-wallet", DEFAULT_WALLET_DAT);
-
- LogPrintf("Using wallet %s\n", walletFile);
- uiInterface.InitMessage(_("Verifying wallet..."));
-
- // Wallet file must be a plain filename without a directory
- if (walletFile != boost::filesystem::basename(walletFile) + boost::filesystem::extension(walletFile))
- return InitError(strprintf(_("Wallet %s resides outside data directory %s"), walletFile, GetDataDir().string()));
-
- if (!bitdb.Open(GetDataDir()))
- {
- // try moving the database env out of the way
- boost::filesystem::path pathDatabase = GetDataDir() / "database";
- boost::filesystem::path pathDatabaseBak = GetDataDir() / strprintf("database.%d.bak", GetTime());
- try {
- boost::filesystem::rename(pathDatabase, pathDatabaseBak);
- LogPrintf("Moved old %s to %s. Retrying.\n", pathDatabase.string(), pathDatabaseBak.string());
- } catch (const boost::filesystem::filesystem_error&) {
- // failure is ok (well, not really, but it's not worse than what we started with)
- }
-
- // try again
- if (!bitdb.Open(GetDataDir())) {
- // if it still fails, it probably means we can't even create the database env
- return InitError(strprintf(_("Error initializing wallet database environment %s!"), GetDataDir()));
- }
- }
-
- if (GetBoolArg("-salvagewallet", false))
- {
- // Recover readable keypairs:
- if (!CWalletDB::Recover(bitdb, walletFile, true))
- return false;
- }
-
- if (boost::filesystem::exists(GetDataDir() / walletFile))
- {
- CDBEnv::VerifyResult r = bitdb.Verify(walletFile, CWalletDB::Recover);
- if (r == CDBEnv::RECOVER_OK)
- {
- InitWarning(strprintf(_("Warning: Wallet file corrupt, data salvaged!"
- " Original %s saved as %s in %s; if"
- " your balance or transactions are incorrect you should"
- " restore from a backup."),
- walletFile, "wallet.{timestamp}.bak", GetDataDir()));
- }
- if (r == CDBEnv::RECOVER_FAIL)
- return InitError(strprintf(_("%s corrupt, salvage failed"), walletFile));
- }
-
- return true;
+ database->Flush(shutdown);
}
-void CWallet::SyncMetaData(pair<TxSpends::iterator, TxSpends::iterator> range)
+void CWallet::SyncMetaData(std::pair<TxSpends::iterator, TxSpends::iterator> range)
{
// We want all the wallet transactions in range to have the same metadata as
// the oldest (smallest nOrderPos).
// So: find smallest nOrderPos:
int nMinOrderPos = std::numeric_limits<int>::max();
- const CWalletTx* copyFrom = NULL;
- for (TxSpends::iterator it = range.first; it != range.second; ++it)
- {
- const uint256& hash = it->second;
- int n = mapWallet[hash].nOrderPos;
- if (n < nMinOrderPos)
- {
- nMinOrderPos = n;
- copyFrom = &mapWallet[hash];
+ const CWalletTx* copyFrom = nullptr;
+ for (TxSpends::iterator it = range.first; it != range.second; ++it) {
+ const CWalletTx* wtx = &mapWallet.at(it->second);
+ if (wtx->nOrderPos < nMinOrderPos) {
+ nMinOrderPos = wtx->nOrderPos;
+ copyFrom = wtx;
}
}
+
+ if (!copyFrom) {
+ return;
+ }
+
// Now copy data from copyFrom to rest:
for (TxSpends::iterator it = range.first; it != range.second; ++it)
{
const uint256& hash = it->second;
- CWalletTx* copyTo = &mapWallet[hash];
+ CWalletTx* copyTo = &mapWallet.at(hash);
if (copyFrom == copyTo) continue;
+ assert(copyFrom && "Oldest wallet transaction in range assumed to have been found.");
if (!copyFrom->IsEquivalentTo(*copyTo)) continue;
copyTo->mapValue = copyFrom->mapValue;
copyTo->vOrderForm = copyFrom->vOrderForm;
@@ -511,7 +598,7 @@ void CWallet::SyncMetaData(pair<TxSpends::iterator, TxSpends::iterator> range)
bool CWallet::IsSpent(const uint256& hash, unsigned int n) const
{
const COutPoint outpoint(hash, n);
- pair<TxSpends::const_iterator, TxSpends::const_iterator> range;
+ std::pair<TxSpends::const_iterator, TxSpends::const_iterator> range;
range = mapTxSpends.equal_range(outpoint);
for (TxSpends::const_iterator it = range.first; it != range.second; ++it)
@@ -529,9 +616,11 @@ bool CWallet::IsSpent(const uint256& hash, unsigned int n) const
void CWallet::AddToSpends(const COutPoint& outpoint, const uint256& wtxid)
{
- mapTxSpends.insert(make_pair(outpoint, wtxid));
+ mapTxSpends.insert(std::make_pair(outpoint, wtxid));
- pair<TxSpends::iterator, TxSpends::iterator> range;
+ setLockedCoins.erase(outpoint);
+
+ std::pair<TxSpends::iterator, TxSpends::iterator> range;
range = mapTxSpends.equal_range(outpoint);
SyncMetaData(range);
}
@@ -539,12 +628,13 @@ void CWallet::AddToSpends(const COutPoint& outpoint, const uint256& wtxid)
void CWallet::AddToSpends(const uint256& wtxid)
{
- assert(mapWallet.count(wtxid));
- CWalletTx& thisTx = mapWallet[wtxid];
+ auto it = mapWallet.find(wtxid);
+ assert(it != mapWallet.end());
+ CWalletTx& thisTx = it->second;
if (thisTx.IsCoinBase()) // Coinbases don't spend anything!
return;
- BOOST_FOREACH(const CTxIn& txin, thisTx.vin)
+ for (const CTxIn& txin : thisTx.tx->vin)
AddToSpends(txin.prevout, wtxid);
}
@@ -553,10 +643,10 @@ bool CWallet::EncryptWallet(const SecureString& strWalletPassphrase)
if (IsCrypted())
return false;
- CKeyingMaterial vMasterKey;
+ CKeyingMaterial _vMasterKey;
- vMasterKey.resize(WALLET_CRYPTO_KEY_SIZE);
- GetStrongRandBytes(&vMasterKey[0], WALLET_CRYPTO_KEY_SIZE);
+ _vMasterKey.resize(WALLET_CRYPTO_KEY_SIZE);
+ GetStrongRandBytes(&_vMasterKey[0], WALLET_CRYPTO_KEY_SIZE);
CMasterKey kMasterKey;
@@ -566,73 +656,62 @@ bool CWallet::EncryptWallet(const SecureString& strWalletPassphrase)
CCrypter crypter;
int64_t nStartTime = GetTimeMillis();
crypter.SetKeyFromPassphrase(strWalletPassphrase, kMasterKey.vchSalt, 25000, kMasterKey.nDerivationMethod);
- kMasterKey.nDeriveIterations = 2500000 / ((double)(GetTimeMillis() - nStartTime));
+ kMasterKey.nDeriveIterations = static_cast<unsigned int>(2500000 / ((double)(GetTimeMillis() - nStartTime)));
nStartTime = GetTimeMillis();
crypter.SetKeyFromPassphrase(strWalletPassphrase, kMasterKey.vchSalt, kMasterKey.nDeriveIterations, kMasterKey.nDerivationMethod);
- kMasterKey.nDeriveIterations = (kMasterKey.nDeriveIterations + kMasterKey.nDeriveIterations * 100 / ((double)(GetTimeMillis() - nStartTime))) / 2;
+ kMasterKey.nDeriveIterations = (kMasterKey.nDeriveIterations + static_cast<unsigned int>(kMasterKey.nDeriveIterations * 100 / ((double)(GetTimeMillis() - nStartTime)))) / 2;
if (kMasterKey.nDeriveIterations < 25000)
kMasterKey.nDeriveIterations = 25000;
- LogPrintf("Encrypting Wallet with an nDeriveIterations of %i\n", kMasterKey.nDeriveIterations);
+ WalletLogPrintf("Encrypting Wallet with an nDeriveIterations of %i\n", kMasterKey.nDeriveIterations);
if (!crypter.SetKeyFromPassphrase(strWalletPassphrase, kMasterKey.vchSalt, kMasterKey.nDeriveIterations, kMasterKey.nDerivationMethod))
return false;
- if (!crypter.Encrypt(vMasterKey, kMasterKey.vchCryptedKey))
+ if (!crypter.Encrypt(_vMasterKey, kMasterKey.vchCryptedKey))
return false;
{
LOCK(cs_wallet);
mapMasterKeys[++nMasterKeyMaxID] = kMasterKey;
- if (fFileBacked)
- {
- assert(!pwalletdbEncryption);
- pwalletdbEncryption = new CWalletDB(strWalletFile);
- if (!pwalletdbEncryption->TxnBegin()) {
- delete pwalletdbEncryption;
- pwalletdbEncryption = NULL;
- return false;
- }
- pwalletdbEncryption->WriteMasterKey(nMasterKeyMaxID, kMasterKey);
+ assert(!encrypted_batch);
+ encrypted_batch = new WalletBatch(*database);
+ if (!encrypted_batch->TxnBegin()) {
+ delete encrypted_batch;
+ encrypted_batch = nullptr;
+ return false;
}
+ encrypted_batch->WriteMasterKey(nMasterKeyMaxID, kMasterKey);
- if (!EncryptKeys(vMasterKey))
+ if (!EncryptKeys(_vMasterKey))
{
- if (fFileBacked) {
- pwalletdbEncryption->TxnAbort();
- delete pwalletdbEncryption;
- }
+ encrypted_batch->TxnAbort();
+ delete encrypted_batch;
// We now probably have half of our keys encrypted in memory, and half not...
// die and let the user reload the unencrypted wallet.
assert(false);
}
// Encryption was introduced in version 0.4.0
- SetMinVersion(FEATURE_WALLETCRYPT, pwalletdbEncryption, true);
-
- if (fFileBacked)
- {
- if (!pwalletdbEncryption->TxnCommit()) {
- delete pwalletdbEncryption;
- // We now have keys encrypted in memory, but not on disk...
- // die to avoid confusion and let the user reload the unencrypted wallet.
- assert(false);
- }
+ SetMinVersion(FEATURE_WALLETCRYPT, encrypted_batch, true);
- delete pwalletdbEncryption;
- pwalletdbEncryption = NULL;
+ if (!encrypted_batch->TxnCommit()) {
+ delete encrypted_batch;
+ // We now have keys encrypted in memory, but not on disk...
+ // die to avoid confusion and let the user reload the unencrypted wallet.
+ assert(false);
}
+ delete encrypted_batch;
+ encrypted_batch = nullptr;
+
Lock();
Unlock(strWalletPassphrase);
- // if we are using HD, replace the HD master key (seed) with a new one
+ // if we are using HD, replace the HD seed with a new one
if (IsHDEnabled()) {
- CKey key;
- CPubKey masterPubKey = GenerateNewHDMasterKey();
- if (!SetHDMasterKey(masterPubKey))
- return false;
+ SetHDSeed(GenerateNewSeed());
}
NewKeyPool();
@@ -640,7 +719,7 @@ bool CWallet::EncryptWallet(const SecureString& strWalletPassphrase)
// Need to completely rewrite the wallet file; if we don't, bdb might keep
// bits of the unencrypted private key in slack space in the database file.
- CDB::Rewrite(strWalletFile);
+ database->Rewrite();
}
NotifyStatusChanged(this);
@@ -648,69 +727,146 @@ bool CWallet::EncryptWallet(const SecureString& strWalletPassphrase)
return true;
}
-int64_t CWallet::IncOrderPosNext(CWalletDB *pwalletdb)
+DBErrors CWallet::ReorderTransactions()
+{
+ LOCK(cs_wallet);
+ WalletBatch batch(*database);
+
+ // Old wallets didn't have any defined order for transactions
+ // Probably a bad idea to change the output of this
+
+ // First: get all CWalletTx and CAccountingEntry into a sorted-by-time multimap.
+ typedef std::pair<CWalletTx*, CAccountingEntry*> TxPair;
+ typedef std::multimap<int64_t, TxPair > TxItems;
+ TxItems txByTime;
+
+ for (auto& entry : mapWallet)
+ {
+ CWalletTx* wtx = &entry.second;
+ txByTime.insert(std::make_pair(wtx->nTimeReceived, TxPair(wtx, nullptr)));
+ }
+ std::list<CAccountingEntry> acentries;
+ batch.ListAccountCreditDebit("", acentries);
+ for (CAccountingEntry& entry : acentries)
+ {
+ txByTime.insert(std::make_pair(entry.nTime, TxPair(nullptr, &entry)));
+ }
+
+ nOrderPosNext = 0;
+ std::vector<int64_t> nOrderPosOffsets;
+ for (TxItems::iterator it = txByTime.begin(); it != txByTime.end(); ++it)
+ {
+ CWalletTx *const pwtx = (*it).second.first;
+ CAccountingEntry *const pacentry = (*it).second.second;
+ int64_t& nOrderPos = (pwtx != nullptr) ? pwtx->nOrderPos : pacentry->nOrderPos;
+
+ if (nOrderPos == -1)
+ {
+ nOrderPos = nOrderPosNext++;
+ nOrderPosOffsets.push_back(nOrderPos);
+
+ if (pwtx)
+ {
+ if (!batch.WriteTx(*pwtx))
+ return DBErrors::LOAD_FAIL;
+ }
+ else
+ if (!batch.WriteAccountingEntry(pacentry->nEntryNo, *pacentry))
+ return DBErrors::LOAD_FAIL;
+ }
+ else
+ {
+ int64_t nOrderPosOff = 0;
+ for (const int64_t& nOffsetStart : nOrderPosOffsets)
+ {
+ if (nOrderPos >= nOffsetStart)
+ ++nOrderPosOff;
+ }
+ nOrderPos += nOrderPosOff;
+ nOrderPosNext = std::max(nOrderPosNext, nOrderPos + 1);
+
+ if (!nOrderPosOff)
+ continue;
+
+ // Since we're changing the order, write it back
+ if (pwtx)
+ {
+ if (!batch.WriteTx(*pwtx))
+ return DBErrors::LOAD_FAIL;
+ }
+ else
+ if (!batch.WriteAccountingEntry(pacentry->nEntryNo, *pacentry))
+ return DBErrors::LOAD_FAIL;
+ }
+ }
+ batch.WriteOrderPosNext(nOrderPosNext);
+
+ return DBErrors::LOAD_OK;
+}
+
+int64_t CWallet::IncOrderPosNext(WalletBatch *batch)
{
AssertLockHeld(cs_wallet); // nOrderPosNext
int64_t nRet = nOrderPosNext++;
- if (pwalletdb) {
- pwalletdb->WriteOrderPosNext(nOrderPosNext);
+ if (batch) {
+ batch->WriteOrderPosNext(nOrderPosNext);
} else {
- CWalletDB(strWalletFile).WriteOrderPosNext(nOrderPosNext);
+ WalletBatch(*database).WriteOrderPosNext(nOrderPosNext);
}
return nRet;
}
bool CWallet::AccountMove(std::string strFrom, std::string strTo, CAmount nAmount, std::string strComment)
{
- CWalletDB walletdb(strWalletFile);
- if (!walletdb.TxnBegin())
+ WalletBatch batch(*database);
+ if (!batch.TxnBegin())
return false;
int64_t nNow = GetAdjustedTime();
// Debit
CAccountingEntry debit;
- debit.nOrderPos = IncOrderPosNext(&walletdb);
+ debit.nOrderPos = IncOrderPosNext(&batch);
debit.strAccount = strFrom;
debit.nCreditDebit = -nAmount;
debit.nTime = nNow;
debit.strOtherAccount = strTo;
debit.strComment = strComment;
- AddAccountingEntry(debit, walletdb);
+ AddAccountingEntry(debit, &batch);
// Credit
CAccountingEntry credit;
- credit.nOrderPos = IncOrderPosNext(&walletdb);
+ credit.nOrderPos = IncOrderPosNext(&batch);
credit.strAccount = strTo;
credit.nCreditDebit = nAmount;
credit.nTime = nNow;
credit.strOtherAccount = strFrom;
credit.strComment = strComment;
- AddAccountingEntry(credit, walletdb);
+ AddAccountingEntry(credit, &batch);
- if (!walletdb.TxnCommit())
+ if (!batch.TxnCommit())
return false;
return true;
}
-bool CWallet::GetAccountPubkey(CPubKey &pubKey, std::string strAccount, bool bForceNew)
+bool CWallet::GetLabelDestination(CTxDestination &dest, const std::string& label, bool bForceNew)
{
- CWalletDB walletdb(strWalletFile);
+ WalletBatch batch(*database);
CAccount account;
- walletdb.ReadAccount(strAccount, account);
+ batch.ReadAccount(label, account);
if (!bForceNew) {
if (!account.vchPubKey.IsValid())
bForceNew = true;
else {
- // Check if the current key has been used
- CScript scriptPubKey = GetScriptForDestination(account.vchPubKey.GetID());
- for (map<uint256, CWalletTx>::iterator it = mapWallet.begin();
+ // Check if the current key has been used (TODO: check other addresses with the same key)
+ CScript scriptPubKey = GetScriptForDestination(GetDestinationForKey(account.vchPubKey, m_default_address_type));
+ for (std::map<uint256, CWalletTx>::iterator it = mapWallet.begin();
it != mapWallet.end() && account.vchPubKey.IsValid();
++it)
- BOOST_FOREACH(const CTxOut& txout, (*it).second.vout)
+ for (const CTxOut& txout : (*it).second.tx->vout)
if (txout.scriptPubKey == scriptPubKey) {
bForceNew = true;
break;
@@ -720,15 +876,17 @@ bool CWallet::GetAccountPubkey(CPubKey &pubKey, std::string strAccount, bool bFo
// Generate a new key
if (bForceNew) {
- if (!GetKeyFromPool(account.vchPubKey))
+ if (!GetKeyFromPool(account.vchPubKey, false))
return false;
- SetAddressBook(account.vchPubKey.GetID(), strAccount, "receive");
- walletdb.WriteAccount(strAccount, account);
+ LearnRelatedScripts(account.vchPubKey, m_default_address_type);
+ dest = GetDestinationForKey(account.vchPubKey, m_default_address_type);
+ SetAddressBook(dest, label, "receive");
+ batch.WriteAccount(label, account);
+ } else {
+ dest = GetDestinationForKey(account.vchPubKey, m_default_address_type);
}
- pubKey = account.vchPubKey;
-
return true;
}
@@ -736,74 +894,58 @@ void CWallet::MarkDirty()
{
{
LOCK(cs_wallet);
- BOOST_FOREACH(PAIRTYPE(const uint256, CWalletTx)& item, mapWallet)
+ for (std::pair<const uint256, CWalletTx>& item : mapWallet)
item.second.MarkDirty();
}
}
+bool CWallet::MarkReplaced(const uint256& originalHash, const uint256& newHash)
+{
+ LOCK(cs_wallet);
+
+ auto mi = mapWallet.find(originalHash);
+
+ // There is a bug if MarkReplaced is not called on an existing wallet transaction.
+ assert(mi != mapWallet.end());
+
+ CWalletTx& wtx = (*mi).second;
+
+ // Ensure for now that we're not overwriting data
+ assert(wtx.mapValue.count("replaced_by_txid") == 0);
+
+ wtx.mapValue["replaced_by_txid"] = newHash.ToString();
+
+ WalletBatch batch(*database, "r+");
+
+ bool success = true;
+ if (!batch.WriteTx(wtx)) {
+ WalletLogPrintf("%s: Updating batch tx %s failed\n", __func__, wtx.GetHash().ToString());
+ success = false;
+ }
+
+ NotifyTransactionChanged(this, originalHash, CT_UPDATED);
+
+ return success;
+}
+
bool CWallet::AddToWallet(const CWalletTx& wtxIn, bool fFlushOnClose)
{
LOCK(cs_wallet);
- CWalletDB walletdb(strWalletFile, "r+", fFlushOnClose);
+ WalletBatch batch(*database, "r+", fFlushOnClose);
uint256 hash = wtxIn.GetHash();
// Inserts only if not already there, returns tx inserted or tx found
- pair<map<uint256, CWalletTx>::iterator, bool> ret = mapWallet.insert(make_pair(hash, wtxIn));
+ std::pair<std::map<uint256, CWalletTx>::iterator, bool> ret = mapWallet.insert(std::make_pair(hash, wtxIn));
CWalletTx& wtx = (*ret.first).second;
wtx.BindWallet(this);
bool fInsertedNew = ret.second;
- if (fInsertedNew)
- {
+ if (fInsertedNew) {
wtx.nTimeReceived = GetAdjustedTime();
- wtx.nOrderPos = IncOrderPosNext(&walletdb);
- wtxOrdered.insert(make_pair(wtx.nOrderPos, TxPair(&wtx, (CAccountingEntry*)0)));
-
- wtx.nTimeSmart = wtx.nTimeReceived;
- if (!wtxIn.hashUnset())
- {
- if (mapBlockIndex.count(wtxIn.hashBlock))
- {
- int64_t latestNow = wtx.nTimeReceived;
- int64_t latestEntry = 0;
- {
- // Tolerate times up to the last timestamp in the wallet not more than 5 minutes into the future
- int64_t latestTolerated = latestNow + 300;
- const TxItems & txOrdered = wtxOrdered;
- for (TxItems::const_reverse_iterator it = txOrdered.rbegin(); it != txOrdered.rend(); ++it)
- {
- CWalletTx *const pwtx = (*it).second.first;
- if (pwtx == &wtx)
- continue;
- CAccountingEntry *const pacentry = (*it).second.second;
- int64_t nSmartTime;
- if (pwtx)
- {
- nSmartTime = pwtx->nTimeSmart;
- if (!nSmartTime)
- nSmartTime = pwtx->nTimeReceived;
- }
- else
- nSmartTime = pacentry->nTime;
- if (nSmartTime <= latestTolerated)
- {
- latestEntry = nSmartTime;
- if (nSmartTime > latestNow)
- latestNow = nSmartTime;
- break;
- }
- }
- }
-
- int64_t blocktime = mapBlockIndex[wtxIn.hashBlock]->GetBlockTime();
- wtx.nTimeSmart = std::max(latestEntry, std::min(blocktime, latestNow));
- }
- else
- LogPrintf("AddToWallet(): found %s in block %s not in index\n",
- wtxIn.GetHash().ToString(),
- wtxIn.hashBlock.ToString());
- }
+ wtx.nOrderPos = IncOrderPosNext(&batch);
+ wtx.m_it_wtxOrdered = wtxOrdered.insert(std::make_pair(wtx.nOrderPos, TxPair(&wtx, nullptr)));
+ wtx.nTimeSmart = ComputeTimeSmart(wtx);
AddToSpends(hash);
}
@@ -832,14 +974,23 @@ bool CWallet::AddToWallet(const CWalletTx& wtxIn, bool fFlushOnClose)
wtx.fFromMe = wtxIn.fFromMe;
fUpdated = true;
}
+ // If we have a witness-stripped version of this transaction, and we
+ // see a new version with a witness, then we must be upgrading a pre-segwit
+ // wallet. Store the new version of the transaction with the witness,
+ // as the stripped-version must be invalid.
+ // TODO: Store all versions of the transaction, instead of just one.
+ if (wtxIn.tx->HasWitness() && !wtx.tx->HasWitness()) {
+ wtx.SetTx(wtxIn.tx);
+ fUpdated = true;
+ }
}
//// debug print
- LogPrintf("AddToWallet %s %s%s\n", wtxIn.GetHash().ToString(), (fInsertedNew ? "new" : ""), (fUpdated ? "update" : ""));
+ WalletLogPrintf("AddToWallet %s %s%s\n", wtxIn.GetHash().ToString(), (fInsertedNew ? "new" : ""), (fUpdated ? "update" : ""));
// Write to disk
if (fInsertedNew || fUpdated)
- if (!walletdb.WriteTx(wtx))
+ if (!batch.WriteTx(wtx))
return false;
// Break debit/credit balance caches:
@@ -849,54 +1000,51 @@ bool CWallet::AddToWallet(const CWalletTx& wtxIn, bool fFlushOnClose)
NotifyTransactionChanged(this, hash, fInsertedNew ? CT_NEW : CT_UPDATED);
// notify an external script when a wallet transaction comes in or is updated
- std::string strCmd = GetArg("-walletnotify", "");
+ std::string strCmd = gArgs.GetArg("-walletnotify", "");
- if ( !strCmd.empty())
+ if (!strCmd.empty())
{
boost::replace_all(strCmd, "%s", wtxIn.GetHash().GetHex());
- boost::thread t(runCommand, strCmd); // thread runs free
+ std::thread t(runCommand, strCmd);
+ t.detach(); // thread runs free
}
return true;
}
-bool CWallet::LoadToWallet(const CWalletTx& wtxIn)
+void CWallet::LoadToWallet(const CWalletTx& wtxIn)
{
uint256 hash = wtxIn.GetHash();
-
- mapWallet[hash] = wtxIn;
- CWalletTx& wtx = mapWallet[hash];
+ const auto& ins = mapWallet.emplace(hash, wtxIn);
+ CWalletTx& wtx = ins.first->second;
wtx.BindWallet(this);
- wtxOrdered.insert(make_pair(wtx.nOrderPos, TxPair(&wtx, (CAccountingEntry*)0)));
+ if (/* insertion took place */ ins.second) {
+ wtx.m_it_wtxOrdered = wtxOrdered.insert(std::make_pair(wtx.nOrderPos, TxPair(&wtx, nullptr)));
+ }
AddToSpends(hash);
- BOOST_FOREACH(const CTxIn& txin, wtx.vin) {
- if (mapWallet.count(txin.prevout.hash)) {
- CWalletTx& prevtx = mapWallet[txin.prevout.hash];
+ for (const CTxIn& txin : wtx.tx->vin) {
+ auto it = mapWallet.find(txin.prevout.hash);
+ if (it != mapWallet.end()) {
+ CWalletTx& prevtx = it->second;
if (prevtx.nIndex == -1 && !prevtx.hashUnset()) {
MarkConflicted(prevtx.hashBlock, wtx.GetHash());
}
}
}
-
- return true;
}
-/**
- * Add a transaction to the wallet, or update it.
- * pblock is optional, but should be provided if the transaction is known to be in a block.
- * If fUpdate is true, existing transactions will be updated.
- */
-bool CWallet::AddToWalletIfInvolvingMe(const CTransaction& tx, const CBlockIndex* pIndex, int posInBlock, bool fUpdate)
+bool CWallet::AddToWalletIfInvolvingMe(const CTransactionRef& ptx, const CBlockIndex* pIndex, int posInBlock, bool fUpdate)
{
+ const CTransaction& tx = *ptx;
{
AssertLockHeld(cs_wallet);
- if (posInBlock != -1) {
- BOOST_FOREACH(const CTxIn& txin, tx.vin) {
+ if (pIndex != nullptr) {
+ for (const CTxIn& txin : tx.vin) {
std::pair<TxSpends::const_iterator, TxSpends::const_iterator> range = mapTxSpends.equal_range(txin.prevout);
while (range.first != range.second) {
if (range.first->second != tx.GetHash()) {
- LogPrintf("Transaction %s (in block %s) conflicts with wallet transaction %s (both spend %s:%i)\n", tx.GetHash().ToString(), pIndex->GetBlockHash().ToString(), range.first->second.ToString(), range.first->first.hash.ToString(), range.first->first.n);
+ WalletLogPrintf("Transaction %s (in block %s) conflicts with wallet transaction %s (both spend %s:%i)\n", tx.GetHash().ToString(), pIndex->GetBlockHash().ToString(), range.first->second.ToString(), range.first->first.hash.ToString(), range.first->first.n);
MarkConflicted(pIndex->GetBlockHash(), range.first->second);
}
range.first++;
@@ -908,10 +1056,34 @@ bool CWallet::AddToWalletIfInvolvingMe(const CTransaction& tx, const CBlockIndex
if (fExisted && !fUpdate) return false;
if (fExisted || IsMine(tx) || IsFromMe(tx))
{
- CWalletTx wtx(this,tx);
+ /* Check if any keys in the wallet keypool that were supposed to be unused
+ * have appeared in a new transaction. If so, remove those keys from the keypool.
+ * This can happen when restoring an old wallet backup that does not contain
+ * the mostly recently created transactions from newer versions of the wallet.
+ */
+
+ // loop though all outputs
+ for (const CTxOut& txout: tx.vout) {
+ // extract addresses and check if they match with an unused keypool key
+ std::vector<CKeyID> vAffected;
+ CAffectedKeysVisitor(*this, vAffected).Process(txout.scriptPubKey);
+ for (const CKeyID &keyid : vAffected) {
+ std::map<CKeyID, int64_t>::const_iterator mi = m_pool_key_to_index.find(keyid);
+ if (mi != m_pool_key_to_index.end()) {
+ WalletLogPrintf("%s: Detected a used keypool key, mark all keypool key up to this key as used\n", __func__);
+ MarkReserveKeysAsUsed(mi->second);
+
+ if (!TopUpKeyPool()) {
+ WalletLogPrintf("%s: Topping up keypool failed (locked wallet)\n", __func__);
+ }
+ }
+ }
+ }
+
+ CWalletTx wtx(this, ptx);
// Get merkle branch if transaction was found in a block
- if (posInBlock != -1)
+ if (pIndex != nullptr)
wtx.SetMerkleBranch(pIndex, posInBlock);
return AddToWallet(wtx, false);
@@ -920,20 +1092,37 @@ bool CWallet::AddToWalletIfInvolvingMe(const CTransaction& tx, const CBlockIndex
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();
+}
+
+void CWallet::MarkInputsDirty(const CTransactionRef& tx)
+{
+ for (const CTxIn& txin : tx->vin) {
+ auto it = mapWallet.find(txin.prevout.hash);
+ if (it != mapWallet.end()) {
+ it->second.MarkDirty();
+ }
+ }
+}
+
bool CWallet::AbandonTransaction(const uint256& hashTx)
{
LOCK2(cs_main, cs_wallet);
- // Do not flush the wallet here for performance reasons
- CWalletDB walletdb(strWalletFile, "r+", false);
+ WalletBatch batch(*database, "r+");
std::set<uint256> todo;
std::set<uint256> done;
// Can't mark abandoned if confirmed or in mempool
- assert(mapWallet.count(hashTx));
- CWalletTx& origtx = mapWallet[hashTx];
- if (origtx.GetDepthInMainChain() > 0 || origtx.InMempool()) {
+ auto it = mapWallet.find(hashTx);
+ assert(it != mapWallet.end());
+ CWalletTx& origtx = it->second;
+ if (origtx.GetDepthInMainChain() != 0 || origtx.InMempool()) {
return false;
}
@@ -943,8 +1132,9 @@ bool CWallet::AbandonTransaction(const uint256& hashTx)
uint256 now = *todo.begin();
todo.erase(now);
done.insert(now);
- assert(mapWallet.count(now));
- CWalletTx& wtx = mapWallet[now];
+ auto it = mapWallet.find(now);
+ assert(it != mapWallet.end());
+ CWalletTx& wtx = it->second;
int currentconfirm = wtx.GetDepthInMainChain();
// If the orig tx was not in block, none of its spends can be
assert(currentconfirm <= 0);
@@ -955,10 +1145,10 @@ bool CWallet::AbandonTransaction(const uint256& hashTx)
wtx.nIndex = -1;
wtx.setAbandoned();
wtx.MarkDirty();
- walletdb.WriteTx(wtx);
+ batch.WriteTx(wtx);
NotifyTransactionChanged(this, wtx.GetHash(), CT_UPDATED);
// Iterate over all its outputs, and mark transactions in the wallet that spend them abandoned too
- TxSpends::const_iterator iter = mapTxSpends.lower_bound(COutPoint(hashTx, 0));
+ TxSpends::const_iterator iter = mapTxSpends.lower_bound(COutPoint(now, 0));
while (iter != mapTxSpends.end() && iter->first.hash == now) {
if (!done.count(iter->second)) {
todo.insert(iter->second);
@@ -967,11 +1157,7 @@ bool CWallet::AbandonTransaction(const uint256& hashTx)
}
// If a transaction changes 'conflicted' state, that changes the balance
// available of the outputs it spends. So force those to be recomputed
- BOOST_FOREACH(const CTxIn& txin, wtx.vin)
- {
- if (mapWallet.count(txin.prevout.hash))
- mapWallet[txin.prevout.hash].MarkDirty();
- }
+ MarkInputsDirty(wtx.tx);
}
}
@@ -983,11 +1169,9 @@ void CWallet::MarkConflicted(const uint256& hashBlock, const uint256& hashTx)
LOCK2(cs_main, cs_wallet);
int conflictconfirms = 0;
- if (mapBlockIndex.count(hashBlock)) {
- CBlockIndex* pindex = mapBlockIndex[hashBlock];
- if (chainActive.Contains(pindex)) {
- conflictconfirms = -(chainActive.Height() - pindex->nHeight + 1);
- }
+ CBlockIndex* pindex = LookupBlockIndex(hashBlock);
+ if (pindex && chainActive.Contains(pindex)) {
+ conflictconfirms = -(chainActive.Height() - pindex->nHeight + 1);
}
// If number of conflict confirms cannot be determined, this means
// that the block is still unknown or not yet part of the main chain,
@@ -997,7 +1181,7 @@ void CWallet::MarkConflicted(const uint256& hashBlock, const uint256& hashTx)
return;
// Do not flush the wallet here for performance reasons
- CWalletDB walletdb(strWalletFile, "r+", false);
+ WalletBatch batch(*database, "r+", false);
std::set<uint256> todo;
std::set<uint256> done;
@@ -1008,8 +1192,9 @@ void CWallet::MarkConflicted(const uint256& hashBlock, const uint256& hashTx)
uint256 now = *todo.begin();
todo.erase(now);
done.insert(now);
- assert(mapWallet.count(now));
- CWalletTx& wtx = mapWallet[now];
+ auto it = mapWallet.find(now);
+ assert(it != mapWallet.end());
+ CWalletTx& wtx = it->second;
int currentconfirm = wtx.GetDepthInMainChain();
if (conflictconfirms < currentconfirm) {
// Block is 'more conflicted' than current confirm; update.
@@ -1017,7 +1202,7 @@ void CWallet::MarkConflicted(const uint256& hashBlock, const uint256& hashTx)
wtx.nIndex = -1;
wtx.hashBlock = hashBlock;
wtx.MarkDirty();
- walletdb.WriteTx(wtx);
+ batch.WriteTx(wtx);
// Iterate over all its outputs, and mark transactions in the wallet that spend them conflicted too
TxSpends::const_iterator iter = mapTxSpends.lower_bound(COutPoint(now, 0));
while (iter != mapTxSpends.end() && iter->first.hash == now) {
@@ -1028,30 +1213,93 @@ void CWallet::MarkConflicted(const uint256& hashBlock, const uint256& hashTx)
}
// If a transaction changes 'conflicted' state, that changes the balance
// available of the outputs it spends. So force those to be recomputed
- BOOST_FOREACH(const CTxIn& txin, wtx.vin)
- {
- if (mapWallet.count(txin.prevout.hash))
- mapWallet[txin.prevout.hash].MarkDirty();
- }
+ MarkInputsDirty(wtx.tx);
}
}
}
-void CWallet::SyncTransaction(const CTransaction& tx, const CBlockIndex *pindex, int posInBlock)
-{
- LOCK2(cs_main, cs_wallet);
-
- if (!AddToWalletIfInvolvingMe(tx, pindex, posInBlock, true))
+void CWallet::SyncTransaction(const CTransactionRef& ptx, const CBlockIndex *pindex, int posInBlock, bool update_tx) {
+ if (!AddToWalletIfInvolvingMe(ptx, pindex, posInBlock, update_tx))
return; // Not one of ours
// If a transaction changes 'conflicted' state, that changes the balance
// available of the outputs it spends. So force those to be
// recomputed, also:
- BOOST_FOREACH(const CTxIn& txin, tx.vin)
+ MarkInputsDirty(ptx);
+}
+
+void CWallet::TransactionAddedToMempool(const CTransactionRef& ptx) {
+ LOCK2(cs_main, cs_wallet);
+ SyncTransaction(ptx);
+
+ auto it = mapWallet.find(ptx->GetHash());
+ if (it != mapWallet.end()) {
+ it->second.fInMempool = true;
+ }
+}
+
+void CWallet::TransactionRemovedFromMempool(const CTransactionRef &ptx) {
+ LOCK(cs_wallet);
+ auto it = mapWallet.find(ptx->GetHash());
+ if (it != mapWallet.end()) {
+ it->second.fInMempool = false;
+ }
+}
+
+void CWallet::BlockConnected(const std::shared_ptr<const CBlock>& pblock, const CBlockIndex *pindex, const std::vector<CTransactionRef>& vtxConflicted) {
+ LOCK2(cs_main, cs_wallet);
+ // TODO: Temporarily ensure that mempool removals are notified before
+ // connected transactions. This shouldn't matter, but the abandoned
+ // state of transactions in our wallet is currently cleared when we
+ // receive another notification and there is a race condition where
+ // notification of a connected conflict might cause an outside process
+ // to abandon a transaction and then have it inadvertently cleared by
+ // the notification that the conflicted transaction was evicted.
+
+ for (const CTransactionRef& ptx : vtxConflicted) {
+ SyncTransaction(ptx);
+ TransactionRemovedFromMempool(ptx);
+ }
+ for (size_t i = 0; i < pblock->vtx.size(); i++) {
+ SyncTransaction(pblock->vtx[i], pindex, i);
+ TransactionRemovedFromMempool(pblock->vtx[i]);
+ }
+
+ m_last_block_processed = pindex;
+}
+
+void CWallet::BlockDisconnected(const std::shared_ptr<const CBlock>& pblock) {
+ LOCK2(cs_main, cs_wallet);
+
+ for (const CTransactionRef& ptx : pblock->vtx) {
+ SyncTransaction(ptx);
+ }
+}
+
+
+
+void CWallet::BlockUntilSyncedToCurrentChain() {
+ AssertLockNotHeld(cs_main);
+ AssertLockNotHeld(cs_wallet);
+
{
- if (mapWallet.count(txin.prevout.hash))
- mapWallet[txin.prevout.hash].MarkDirty();
+ // Skip the queue-draining stuff if we know we're caught up with
+ // chainActive.Tip()...
+ // We could also take cs_wallet here, and call m_last_block_processed
+ // protected by cs_wallet instead of cs_main, but as long as we need
+ // cs_main here anyway, it's easier to just call it cs_main-protected.
+ LOCK(cs_main);
+ const CBlockIndex* initialChainTip = chainActive.Tip();
+
+ if (m_last_block_processed && m_last_block_processed->GetAncestor(initialChainTip->nHeight) == initialChainTip) {
+ return;
+ }
}
+
+ // ...otherwise put a callback in the validation interface queue and wait
+ // for the queue to drain enough to execute it (indicating we are caught up
+ // at least with the time we entered this function).
+ SyncWithValidationInterfaceQueue();
}
@@ -1059,28 +1307,30 @@ isminetype CWallet::IsMine(const CTxIn &txin) const
{
{
LOCK(cs_wallet);
- map<uint256, CWalletTx>::const_iterator mi = mapWallet.find(txin.prevout.hash);
+ std::map<uint256, CWalletTx>::const_iterator mi = mapWallet.find(txin.prevout.hash);
if (mi != mapWallet.end())
{
const CWalletTx& prev = (*mi).second;
- if (txin.prevout.n < prev.vout.size())
- return IsMine(prev.vout[txin.prevout.n]);
+ if (txin.prevout.n < prev.tx->vout.size())
+ return IsMine(prev.tx->vout[txin.prevout.n]);
}
}
return ISMINE_NO;
}
+// Note that this function doesn't distinguish between a 0-valued input,
+// and a not-"is mine" (according to the filter) input.
CAmount CWallet::GetDebit(const CTxIn &txin, const isminefilter& filter) const
{
{
LOCK(cs_wallet);
- map<uint256, CWalletTx>::const_iterator mi = mapWallet.find(txin.prevout.hash);
+ std::map<uint256, CWalletTx>::const_iterator mi = mapWallet.find(txin.prevout.hash);
if (mi != mapWallet.end())
{
const CWalletTx& prev = (*mi).second;
- if (txin.prevout.n < prev.vout.size())
- if (IsMine(prev.vout[txin.prevout.n]) & filter)
- return prev.vout[txin.prevout.n].nValue;
+ if (txin.prevout.n < prev.tx->vout.size())
+ if (IsMine(prev.tx->vout[txin.prevout.n]) & filter)
+ return prev.tx->vout[txin.prevout.n].nValue;
}
}
return 0;
@@ -1129,7 +1379,7 @@ CAmount CWallet::GetChange(const CTxOut& txout) const
bool CWallet::IsMine(const CTransaction& tx) const
{
- BOOST_FOREACH(const CTxOut& txout, tx.vout)
+ for (const CTxOut& txout : tx.vout)
if (IsMine(txout))
return true;
return false;
@@ -1143,7 +1393,7 @@ bool CWallet::IsFromMe(const CTransaction& tx) const
CAmount CWallet::GetDebit(const CTransaction& tx, const isminefilter& filter) const
{
CAmount nDebit = 0;
- BOOST_FOREACH(const CTxIn& txin, tx.vin)
+ for (const CTxIn& txin : tx.vin)
{
nDebit += GetDebit(txin, filter);
if (!MoneyRange(nDebit))
@@ -1152,10 +1402,31 @@ CAmount CWallet::GetDebit(const CTransaction& tx, const isminefilter& filter) co
return nDebit;
}
+bool CWallet::IsAllFromMe(const CTransaction& tx, const isminefilter& filter) const
+{
+ LOCK(cs_wallet);
+
+ for (const CTxIn& txin : tx.vin)
+ {
+ auto mi = mapWallet.find(txin.prevout.hash);
+ if (mi == mapWallet.end())
+ return false; // any unknown inputs can't be from us
+
+ const CWalletTx& prev = (*mi).second;
+
+ if (txin.prevout.n >= prev.tx->vout.size())
+ return false; // invalid input!
+
+ if (!(IsMine(prev.tx->vout[txin.prevout.n]) & filter))
+ return false;
+ }
+ return true;
+}
+
CAmount CWallet::GetCredit(const CTransaction& tx, const isminefilter& filter) const
{
CAmount nCredit = 0;
- BOOST_FOREACH(const CTxOut& txout, tx.vout)
+ for (const CTxOut& txout : tx.vout)
{
nCredit += GetCredit(txout, filter);
if (!MoneyRange(nCredit))
@@ -1167,7 +1438,7 @@ CAmount CWallet::GetCredit(const CTransaction& tx, const isminefilter& filter) c
CAmount CWallet::GetChange(const CTransaction& tx) const
{
CAmount nChange = 0;
- BOOST_FOREACH(const CTxOut& txout, tx.vout)
+ for (const CTxOut& txout : tx.vout)
{
nChange += GetChange(txout);
if (!MoneyRange(nChange))
@@ -1176,66 +1447,93 @@ CAmount CWallet::GetChange(const CTransaction& tx) const
return nChange;
}
-CPubKey CWallet::GenerateNewHDMasterKey()
+CPubKey CWallet::GenerateNewSeed()
{
+ assert(!IsWalletFlagSet(WALLET_FLAG_DISABLE_PRIVATE_KEYS));
CKey key;
key.MakeNewKey(true);
+ return DeriveNewSeed(key);
+}
+CPubKey CWallet::DeriveNewSeed(const CKey& key)
+{
int64_t nCreationTime = GetTime();
CKeyMetadata metadata(nCreationTime);
- // calculate the pubkey
- CPubKey pubkey = key.GetPubKey();
- assert(key.VerifyPubKey(pubkey));
+ // calculate the seed
+ CPubKey seed = key.GetPubKey();
+ assert(key.VerifyPubKey(seed));
- // set the hd keypath to "m" -> Master, refers the masterkeyid to itself
- metadata.hdKeypath = "m";
- metadata.hdMasterKeyID = pubkey.GetID();
+ // set the hd keypath to "s" -> Seed, refers the seed to itself
+ metadata.hdKeypath = "s";
+ metadata.hd_seed_id = seed.GetID();
{
LOCK(cs_wallet);
// mem store the metadata
- mapKeyMetadata[pubkey.GetID()] = metadata;
+ mapKeyMetadata[seed.GetID()] = metadata;
// write the key&metadata to the database
- if (!AddKeyPubKey(key, pubkey))
+ if (!AddKeyPubKey(key, seed))
throw std::runtime_error(std::string(__func__) + ": AddKeyPubKey failed");
}
- return pubkey;
+ return seed;
}
-bool CWallet::SetHDMasterKey(const CPubKey& pubkey)
+void CWallet::SetHDSeed(const CPubKey& seed)
{
LOCK(cs_wallet);
-
- // ensure this wallet.dat can only be opened by clients supporting HD
- SetMinVersion(FEATURE_HD);
-
// store the keyid (hash160) together with
// the child index counter in the database
// as a hdchain object
CHDChain newHdChain;
- newHdChain.masterKeyID = pubkey.GetID();
+ newHdChain.nVersion = CanSupportFeature(FEATURE_HD_SPLIT) ? CHDChain::VERSION_HD_CHAIN_SPLIT : CHDChain::VERSION_HD_BASE;
+ newHdChain.seed_id = seed.GetID();
SetHDChain(newHdChain, false);
-
- return true;
}
-bool CWallet::SetHDChain(const CHDChain& chain, bool memonly)
+void CWallet::SetHDChain(const CHDChain& chain, bool memonly)
{
LOCK(cs_wallet);
- if (!memonly && !CWalletDB(strWalletFile).WriteHDChain(chain))
- throw runtime_error(std::string(__func__) + ": writing chain failed");
+ if (!memonly && !WalletBatch(*database).WriteHDChain(chain))
+ throw std::runtime_error(std::string(__func__) + ": writing chain failed");
hdChain = chain;
- return true;
}
-bool CWallet::IsHDEnabled()
+bool CWallet::IsHDEnabled() const
{
- return !hdChain.masterKeyID.IsNull();
+ return !hdChain.seed_id.IsNull();
+}
+
+void CWallet::SetWalletFlag(uint64_t flags)
+{
+ LOCK(cs_wallet);
+ m_wallet_flags |= flags;
+ if (!WalletBatch(*database).WriteWalletFlags(m_wallet_flags))
+ throw std::runtime_error(std::string(__func__) + ": writing wallet flags failed");
+}
+
+bool CWallet::IsWalletFlagSet(uint64_t flag)
+{
+ return (m_wallet_flags & flag);
+}
+
+bool CWallet::SetWalletFlags(uint64_t overwriteFlags, bool memonly)
+{
+ LOCK(cs_wallet);
+ m_wallet_flags = overwriteFlags;
+ if (((overwriteFlags & g_known_wallet_flags) >> 32) ^ (overwriteFlags >> 32)) {
+ // contains unknown non-tolerable wallet flags
+ return false;
+ }
+ if (!memonly && !WalletBatch(*database).WriteWalletFlags(m_wallet_flags)) {
+ throw std::runtime_error(std::string(__func__) + ": writing wallet flags failed");
+ }
+
+ return true;
}
int64_t CWalletTx::GetTxTime() const
@@ -1244,47 +1542,80 @@ int64_t CWalletTx::GetTxTime() const
return n ? n : nTimeReceived;
}
-int CWalletTx::GetRequestCount() const
+// Helper for producing a max-sized low-S low-R signature (eg 71 bytes)
+// or a max-sized low-S signature (e.g. 72 bytes) if use_max_sig is true
+bool CWallet::DummySignInput(CTxIn &tx_in, const CTxOut &txout, bool use_max_sig) const
+{
+ // Fill in dummy signatures for fee calculation.
+ const CScript& scriptPubKey = txout.scriptPubKey;
+ SignatureData sigdata;
+
+ if (!ProduceSignature(*this, use_max_sig ? DUMMY_MAXIMUM_SIGNATURE_CREATOR : DUMMY_SIGNATURE_CREATOR, scriptPubKey, sigdata)) {
+ return false;
+ }
+ UpdateInput(tx_in, sigdata);
+ return true;
+}
+
+// Helper for producing a bunch of max-sized low-S low-R signatures (eg 71 bytes)
+bool CWallet::DummySignTx(CMutableTransaction &txNew, const std::vector<CTxOut> &txouts, bool use_max_sig) const
{
- // Returns -1 if it wasn't being tracked
- int nRequests = -1;
+ // Fill in dummy signatures for fee calculation.
+ int nIn = 0;
+ for (const auto& txout : txouts)
{
- LOCK(pwallet->cs_wallet);
- if (IsCoinBase())
- {
- // Generated block
- if (!hashUnset())
- {
- map<uint256, int>::const_iterator mi = pwallet->mapRequestCount.find(hashBlock);
- if (mi != pwallet->mapRequestCount.end())
- nRequests = (*mi).second;
- }
+ if (!DummySignInput(txNew.vin[nIn], txout, use_max_sig)) {
+ return false;
}
- else
- {
- // Did anyone request this transaction?
- map<uint256, int>::const_iterator mi = pwallet->mapRequestCount.find(GetHash());
- if (mi != pwallet->mapRequestCount.end())
- {
- nRequests = (*mi).second;
- // How about the block it's in?
- if (nRequests == 0 && !hashUnset())
- {
- map<uint256, int>::const_iterator mi = pwallet->mapRequestCount.find(hashBlock);
- if (mi != pwallet->mapRequestCount.end())
- nRequests = (*mi).second;
- else
- nRequests = 1; // If it's in someone else's block it must have got out
- }
- }
+ nIn++;
+ }
+ return true;
+}
+
+int64_t CalculateMaximumSignedTxSize(const CTransaction &tx, const CWallet *wallet, bool use_max_sig)
+{
+ std::vector<CTxOut> txouts;
+ // Look up the inputs. We should have already checked that this transaction
+ // IsAllFromMe(ISMINE_SPENDABLE), so every input should already be in our
+ // wallet, with a valid index into the vout array, and the ability to sign.
+ for (auto& input : tx.vin) {
+ const auto mi = wallet->mapWallet.find(input.prevout.hash);
+ if (mi == wallet->mapWallet.end()) {
+ return -1;
}
+ assert(input.prevout.n < mi->second.tx->vout.size());
+ txouts.emplace_back(mi->second.tx->vout[input.prevout.n]);
+ }
+ return CalculateMaximumSignedTxSize(tx, wallet, txouts, use_max_sig);
+}
+
+// txouts needs to be in the order of tx.vin
+int64_t CalculateMaximumSignedTxSize(const CTransaction &tx, const CWallet *wallet, const std::vector<CTxOut>& txouts, bool use_max_sig)
+{
+ CMutableTransaction txNew(tx);
+ if (!wallet->DummySignTx(txNew, txouts, use_max_sig)) {
+ // This should never happen, because IsAllFromMe(ISMINE_SPENDABLE)
+ // implies that we can sign for every input.
+ return -1;
}
- return nRequests;
+ return GetVirtualTransactionSize(txNew);
}
-void CWalletTx::GetAmounts(list<COutputEntry>& listReceived,
- list<COutputEntry>& listSent, CAmount& nFee, string& strSentAccount, const isminefilter& filter) const
+int CalculateMaximumSignedInputSize(const CTxOut& txout, const CWallet* wallet, bool use_max_sig)
+{
+ CMutableTransaction txn;
+ txn.vin.push_back(CTxIn(COutPoint()));
+ if (!wallet->DummySignInput(txn.vin[0], txout, use_max_sig)) {
+ // This should never happen, because IsAllFromMe(ISMINE_SPENDABLE)
+ // implies that we can sign for every input.
+ return -1;
+ }
+ return GetVirtualTransactionInputSize(txn.vin[0]);
+}
+
+void CWalletTx::GetAmounts(std::list<COutputEntry>& listReceived,
+ std::list<COutputEntry>& listSent, CAmount& nFee, std::string& strSentAccount, const isminefilter& filter) const
{
nFee = 0;
listReceived.clear();
@@ -1295,14 +1626,14 @@ void CWalletTx::GetAmounts(list<COutputEntry>& listReceived,
CAmount nDebit = GetDebit(filter);
if (nDebit > 0) // debit>0 means we signed/sent this transaction
{
- CAmount nValueOut = GetValueOut();
+ CAmount nValueOut = tx->GetValueOut();
nFee = nDebit - nValueOut;
}
// Sent/received.
- for (unsigned int i = 0; i < vout.size(); ++i)
+ for (unsigned int i = 0; i < tx->vout.size(); ++i)
{
- const CTxOut& txout = vout[i];
+ const CTxOut& txout = tx->vout[i];
isminetype fIsMine = pwallet->IsMine(txout);
// Only need to handle txouts if AT LEAST one of these is true:
// 1) they debit from us (sent)
@@ -1321,8 +1652,8 @@ void CWalletTx::GetAmounts(list<COutputEntry>& listReceived,
if (!ExtractDestination(txout.scriptPubKey, address) && !txout.scriptPubKey.IsUnspendable())
{
- LogPrintf("CWalletTx::GetAmounts: Unknown transaction type found, txid %s\n",
- this->GetHash().ToString());
+ pwallet->WalletLogPrintf("CWalletTx::GetAmounts: Unknown transaction type found, txid %s\n",
+ this->GetHash().ToString());
address = CNoDestination();
}
@@ -1339,84 +1670,128 @@ void CWalletTx::GetAmounts(list<COutputEntry>& listReceived,
}
-void CWalletTx::GetAccountAmounts(const string& strAccount, CAmount& nReceived,
- CAmount& nSent, CAmount& nFee, const isminefilter& filter) const
+/**
+ * Scan active chain for relevant transactions after importing keys. This should
+ * be called whenever new keys are added to the wallet, with the oldest key
+ * creation time.
+ *
+ * @return Earliest timestamp that could be successfully scanned from. Timestamp
+ * returned will be higher than startTime if relevant blocks could not be read.
+ */
+int64_t CWallet::RescanFromTime(int64_t startTime, const WalletRescanReserver& reserver, bool update)
{
- nReceived = nSent = nFee = 0;
-
- CAmount allFee;
- string strSentAccount;
- list<COutputEntry> listReceived;
- list<COutputEntry> listSent;
- GetAmounts(listReceived, listSent, allFee, strSentAccount, filter);
-
- if (strAccount == strSentAccount)
+ // Find starting block. May be null if nCreateTime is greater than the
+ // highest blockchain timestamp, in which case there is nothing that needs
+ // to be scanned.
+ CBlockIndex* startBlock = nullptr;
{
- BOOST_FOREACH(const COutputEntry& s, listSent)
- nSent += s.amount;
- nFee = allFee;
+ LOCK(cs_main);
+ startBlock = chainActive.FindEarliestAtLeast(startTime - TIMESTAMP_WINDOW);
+ WalletLogPrintf("%s: Rescanning last %i blocks\n", __func__, startBlock ? chainActive.Height() - startBlock->nHeight + 1 : 0);
}
- {
- LOCK(pwallet->cs_wallet);
- BOOST_FOREACH(const COutputEntry& r, listReceived)
- {
- if (pwallet->mapAddressBook.count(r.destination))
- {
- map<CTxDestination, CAddressBookData>::const_iterator mi = pwallet->mapAddressBook.find(r.destination);
- if (mi != pwallet->mapAddressBook.end() && (*mi).second.name == strAccount)
- nReceived += r.amount;
- }
- else if (strAccount.empty())
- {
- nReceived += r.amount;
- }
+
+ if (startBlock) {
+ const CBlockIndex* const failedBlock = ScanForWalletTransactions(startBlock, nullptr, reserver, update);
+ if (failedBlock) {
+ return failedBlock->GetBlockTimeMax() + TIMESTAMP_WINDOW + 1;
}
}
+ return startTime;
}
/**
* Scan the block chain (starting in pindexStart) for transactions
* from or to us. If fUpdate is true, found transactions that already
* exist in the wallet will be updated.
+ *
+ * Returns null if scan was successful. Otherwise, if a complete rescan was not
+ * possible (due to pruning or corruption), returns pointer to the most recent
+ * block that could not be scanned.
+ *
+ * If pindexStop is not a nullptr, the scan will stop at the block-index
+ * defined by pindexStop
+ *
+ * Caller needs to make sure pindexStop (and the optional pindexStart) are on
+ * the main chain after to the addition of any new keys you want to detect
+ * transactions for.
*/
-int CWallet::ScanForWalletTransactions(CBlockIndex* pindexStart, bool fUpdate)
+CBlockIndex* CWallet::ScanForWalletTransactions(CBlockIndex* pindexStart, CBlockIndex* pindexStop, const WalletRescanReserver &reserver, bool fUpdate)
{
- int ret = 0;
int64_t nNow = GetTime();
const CChainParams& chainParams = Params();
+ assert(reserver.isReserved());
+ if (pindexStop) {
+ assert(pindexStop->nHeight >= pindexStart->nHeight);
+ }
+
CBlockIndex* pindex = pindexStart;
- {
- LOCK2(cs_main, cs_wallet);
+ CBlockIndex* ret = nullptr;
- // no need to read and scan block, if block was created before
- // our wallet birthday (as adjusted for block time variability)
- while (pindex && nTimeFirstKey && (pindex->GetBlockTime() < (nTimeFirstKey - 7200)))
- pindex = chainActive.Next(pindex);
+ if (pindex) WalletLogPrintf("Rescan started from block %d...\n", pindex->nHeight);
- ShowProgress(_("Rescanning..."), 0); // show rescan progress in GUI as dialog or on splashscreen, if -rescan on startup
- double dProgressStart = Checkpoints::GuessVerificationProgress(chainParams.Checkpoints(), pindex, false);
- double dProgressTip = Checkpoints::GuessVerificationProgress(chainParams.Checkpoints(), chainActive.Tip(), false);
- while (pindex)
+ {
+ fAbortRescan = false;
+ ShowProgress(strprintf("%s " + _("Rescanning..."), GetDisplayName()), 0); // show rescan progress in GUI as dialog or on splashscreen, if -rescan on startup
+ CBlockIndex* tip = nullptr;
+ double progress_begin;
+ double progress_end;
{
- if (pindex->nHeight % 100 == 0 && dProgressTip - dProgressStart > 0.0)
- ShowProgress(_("Rescanning..."), std::max(1, std::min(99, (int)((Checkpoints::GuessVerificationProgress(chainParams.Checkpoints(), pindex, false) - dProgressStart) / (dProgressTip - dProgressStart) * 100))));
-
- CBlock block;
- ReadBlockFromDisk(block, pindex, Params().GetConsensus());
- int posInBlock;
- for (posInBlock = 0; posInBlock < (int)block.vtx.size(); posInBlock++)
- {
- if (AddToWalletIfInvolvingMe(block.vtx[posInBlock], pindex, posInBlock, fUpdate))
- ret++;
+ LOCK(cs_main);
+ progress_begin = GuessVerificationProgress(chainParams.TxData(), pindex);
+ if (pindexStop == nullptr) {
+ tip = chainActive.Tip();
+ progress_end = GuessVerificationProgress(chainParams.TxData(), tip);
+ } else {
+ progress_end = GuessVerificationProgress(chainParams.TxData(), pindexStop);
+ }
+ }
+ double progress_current = progress_begin;
+ while (pindex && !fAbortRescan && !ShutdownRequested())
+ {
+ if (pindex->nHeight % 100 == 0 && progress_end - progress_begin > 0.0) {
+ ShowProgress(strprintf("%s " + _("Rescanning..."), GetDisplayName()), std::max(1, std::min(99, (int)((progress_current - progress_begin) / (progress_end - progress_begin) * 100))));
}
- pindex = chainActive.Next(pindex);
if (GetTime() >= nNow + 60) {
nNow = GetTime();
- LogPrintf("Still rescanning. At block %d. Progress=%f\n", pindex->nHeight, Checkpoints::GuessVerificationProgress(chainParams.Checkpoints(), pindex));
+ WalletLogPrintf("Still rescanning. At block %d. Progress=%f\n", pindex->nHeight, progress_current);
+ }
+
+ CBlock block;
+ if (ReadBlockFromDisk(block, pindex, Params().GetConsensus())) {
+ LOCK2(cs_main, cs_wallet);
+ if (pindex && !chainActive.Contains(pindex)) {
+ // Abort scan if current block is no longer active, to prevent
+ // marking transactions as coming from the wrong block.
+ ret = pindex;
+ break;
+ }
+ for (size_t posInBlock = 0; posInBlock < block.vtx.size(); ++posInBlock) {
+ SyncTransaction(block.vtx[posInBlock], pindex, posInBlock, fUpdate);
+ }
+ } else {
+ ret = pindex;
+ }
+ if (pindex == pindexStop) {
+ break;
+ }
+ {
+ LOCK(cs_main);
+ pindex = chainActive.Next(pindex);
+ progress_current = GuessVerificationProgress(chainParams.TxData(), pindex);
+ if (pindexStop == nullptr && tip != chainActive.Tip()) {
+ tip = chainActive.Tip();
+ // in case the tip has changed, update progress max
+ progress_end = GuessVerificationProgress(chainParams.TxData(), tip);
+ }
}
}
- ShowProgress(_("Rescanning..."), 100); // hide progress dialog in GUI
+ if (pindex && fAbortRescan) {
+ WalletLogPrintf("Rescan aborted at block %d. Progress=%f\n", pindex->nHeight, progress_current);
+ } else if (pindex && ShutdownRequested()) {
+ WalletLogPrintf("Rescan interrupted by shutdown request at block %d. Progress=%f\n", pindex->nHeight, progress_current);
+ }
+ ShowProgress(strprintf("%s " + _("Rescanning..."), GetDisplayName()), 100); // hide progress dialog in GUI
}
return ret;
}
@@ -1430,7 +1805,7 @@ void CWallet::ReacceptWalletTransactions()
std::map<int64_t, CWalletTx*> mapSorted;
// Sort pending wallet transactions based on their initial wallet insertion order
- BOOST_FOREACH(PAIRTYPE(const uint256, CWalletTx)& item, mapWallet)
+ for (std::pair<const uint256, CWalletTx>& item : mapWallet)
{
const uint256& wtxid = item.first;
CWalletTx& wtx = item.second;
@@ -1444,33 +1819,39 @@ void CWallet::ReacceptWalletTransactions()
}
// Try to add wallet transactions to memory pool
- BOOST_FOREACH(PAIRTYPE(const int64_t, CWalletTx*)& item, mapSorted)
- {
+ for (std::pair<const int64_t, CWalletTx*>& item : mapSorted) {
CWalletTx& wtx = *(item.second);
-
- LOCK(mempool.cs);
- wtx.AcceptToMemoryPool(false, maxTxFee);
+ CValidationState state;
+ wtx.AcceptToMemoryPool(maxTxFee, state);
}
}
-bool CWalletTx::RelayWalletTransaction()
+bool CWalletTx::RelayWalletTransaction(CConnman* connman)
{
assert(pwallet->GetBroadcastTransactions());
- if (!IsCoinBase())
+ if (!IsCoinBase() && !isAbandoned() && GetDepthInMainChain() == 0)
{
- if (GetDepthInMainChain() == 0 && !isAbandoned() && InMempool()) {
- LogPrintf("Relaying wtx %s\n", GetHash().ToString());
- RelayTransaction((CTransaction)*this);
- return true;
+ CValidationState state;
+ /* GetDepthInMainChain already catches known conflicts. */
+ if (InMempool() || AcceptToMemoryPool(maxTxFee, state)) {
+ pwallet->WalletLogPrintf("Relaying wtx %s\n", GetHash().ToString());
+ if (connman) {
+ CInv inv(MSG_TX, GetHash());
+ connman->ForEachNode([&inv](CNode* pnode)
+ {
+ pnode->PushInventory(inv);
+ });
+ return true;
+ }
}
}
return false;
}
-set<uint256> CWalletTx::GetConflicts() const
+std::set<uint256> CWalletTx::GetConflicts() const
{
- set<uint256> result;
- if (pwallet != NULL)
+ std::set<uint256> result;
+ if (pwallet != nullptr)
{
uint256 myHash = GetHash();
result = pwallet->GetConflicts(myHash);
@@ -1481,7 +1862,7 @@ set<uint256> CWalletTx::GetConflicts() const
CAmount CWalletTx::GetDebit(const isminefilter& filter) const
{
- if (vin.empty())
+ if (tx->vin.empty())
return 0;
CAmount debit = 0;
@@ -1491,7 +1872,7 @@ CAmount CWalletTx::GetDebit(const isminefilter& filter) const
debit += nDebitCached;
else
{
- nDebitCached = pwallet->GetDebit(*this, ISMINE_SPENDABLE);
+ nDebitCached = pwallet->GetDebit(*tx, ISMINE_SPENDABLE);
fDebitCached = true;
debit += nDebitCached;
}
@@ -1502,7 +1883,7 @@ CAmount CWalletTx::GetDebit(const isminefilter& filter) const
debit += nWatchDebitCached;
else
{
- nWatchDebitCached = pwallet->GetDebit(*this, ISMINE_WATCH_ONLY);
+ nWatchDebitCached = pwallet->GetDebit(*tx, ISMINE_WATCH_ONLY);
fWatchDebitCached = true;
debit += nWatchDebitCached;
}
@@ -1516,7 +1897,7 @@ CAmount CWalletTx::GetCredit(const isminefilter& filter) const
if (IsCoinBase() && GetBlocksToMaturity() > 0)
return 0;
- int64_t credit = 0;
+ CAmount credit = 0;
if (filter & ISMINE_SPENDABLE)
{
// GetBalance can assume transactions in mapWallet won't change
@@ -1524,7 +1905,7 @@ CAmount CWalletTx::GetCredit(const isminefilter& filter) const
credit += nCreditCached;
else
{
- nCreditCached = pwallet->GetCredit(*this, ISMINE_SPENDABLE);
+ nCreditCached = pwallet->GetCredit(*tx, ISMINE_SPENDABLE);
fCreditCached = true;
credit += nCreditCached;
}
@@ -1535,7 +1916,7 @@ CAmount CWalletTx::GetCredit(const isminefilter& filter) const
credit += nWatchCreditCached;
else
{
- nWatchCreditCached = pwallet->GetCredit(*this, ISMINE_WATCH_ONLY);
+ nWatchCreditCached = pwallet->GetCredit(*tx, ISMINE_WATCH_ONLY);
fWatchCreditCached = true;
credit += nWatchCreditCached;
}
@@ -1549,7 +1930,7 @@ CAmount CWalletTx::GetImmatureCredit(bool fUseCache) const
{
if (fUseCache && fImmatureCreditCached)
return nImmatureCreditCached;
- nImmatureCreditCached = pwallet->GetCredit(*this, ISMINE_SPENDABLE);
+ nImmatureCreditCached = pwallet->GetCredit(*tx, ISMINE_SPENDABLE);
fImmatureCreditCached = true;
return nImmatureCreditCached;
}
@@ -1557,43 +1938,58 @@ CAmount CWalletTx::GetImmatureCredit(bool fUseCache) const
return 0;
}
-CAmount CWalletTx::GetAvailableCredit(bool fUseCache) const
+CAmount CWalletTx::GetAvailableCredit(bool fUseCache, const isminefilter& filter) const
{
- if (pwallet == 0)
+ if (pwallet == nullptr)
return 0;
// Must wait until coinbase is safely deep enough in the chain before valuing it
if (IsCoinBase() && GetBlocksToMaturity() > 0)
return 0;
- if (fUseCache && fAvailableCreditCached)
- return nAvailableCreditCached;
+ CAmount* cache = nullptr;
+ bool* cache_used = nullptr;
+
+ if (filter == ISMINE_SPENDABLE) {
+ cache = &nAvailableCreditCached;
+ cache_used = &fAvailableCreditCached;
+ } else if (filter == ISMINE_WATCH_ONLY) {
+ cache = &nAvailableWatchCreditCached;
+ cache_used = &fAvailableWatchCreditCached;
+ }
+
+ if (fUseCache && cache_used && *cache_used) {
+ return *cache;
+ }
CAmount nCredit = 0;
uint256 hashTx = GetHash();
- for (unsigned int i = 0; i < vout.size(); i++)
+ for (unsigned int i = 0; i < tx->vout.size(); i++)
{
if (!pwallet->IsSpent(hashTx, i))
{
- const CTxOut &txout = vout[i];
- nCredit += pwallet->GetCredit(txout, ISMINE_SPENDABLE);
+ const CTxOut &txout = tx->vout[i];
+ nCredit += pwallet->GetCredit(txout, filter);
if (!MoneyRange(nCredit))
- throw std::runtime_error("CWalletTx::GetAvailableCredit() : value out of range");
+ throw std::runtime_error(std::string(__func__) + " : value out of range");
}
}
- nAvailableCreditCached = nCredit;
- fAvailableCreditCached = true;
+ if (cache) {
+ *cache = nCredit;
+ assert(cache_used);
+ *cache_used = true;
+ }
return nCredit;
}
-CAmount CWalletTx::GetImmatureWatchOnlyCredit(const bool& fUseCache) const
+CAmount CWalletTx::GetImmatureWatchOnlyCredit(const bool fUseCache) const
{
if (IsCoinBase() && GetBlocksToMaturity() > 0 && IsInMainChain())
{
if (fUseCache && fImmatureWatchCreditCached)
return nImmatureWatchCreditCached;
- nImmatureWatchCreditCached = pwallet->GetCredit(*this, ISMINE_WATCH_ONLY);
+ nImmatureWatchCreditCached = pwallet->GetCredit(*tx, ISMINE_WATCH_ONLY);
fImmatureWatchCreditCached = true;
return nImmatureWatchCreditCached;
}
@@ -1601,64 +1997,31 @@ CAmount CWalletTx::GetImmatureWatchOnlyCredit(const bool& fUseCache) const
return 0;
}
-CAmount CWalletTx::GetAvailableWatchOnlyCredit(const bool& fUseCache) const
-{
- if (pwallet == 0)
- return 0;
-
- // Must wait until coinbase is safely deep enough in the chain before valuing it
- if (IsCoinBase() && GetBlocksToMaturity() > 0)
- return 0;
-
- if (fUseCache && fAvailableWatchCreditCached)
- return nAvailableWatchCreditCached;
-
- CAmount nCredit = 0;
- for (unsigned int i = 0; i < vout.size(); i++)
- {
- if (!pwallet->IsSpent(GetHash(), i))
- {
- const CTxOut &txout = vout[i];
- nCredit += pwallet->GetCredit(txout, ISMINE_WATCH_ONLY);
- if (!MoneyRange(nCredit))
- throw std::runtime_error("CWalletTx::GetAvailableCredit() : value out of range");
- }
- }
-
- nAvailableWatchCreditCached = nCredit;
- fAvailableWatchCreditCached = true;
- return nCredit;
-}
-
CAmount CWalletTx::GetChange() const
{
if (fChangeCached)
return nChangeCached;
- nChangeCached = pwallet->GetChange(*this);
+ nChangeCached = pwallet->GetChange(*tx);
fChangeCached = true;
return nChangeCached;
}
bool CWalletTx::InMempool() const
{
- LOCK(mempool.cs);
- if (mempool.exists(GetHash())) {
- return true;
- }
- return false;
+ return fInMempool;
}
bool CWalletTx::IsTrusted() const
{
// Quick answer in most cases
- if (!CheckFinalTx(*this))
+ if (!CheckFinalTx(*tx))
return false;
int nDepth = GetDepthInMainChain();
if (nDepth >= 1)
return true;
if (nDepth < 0)
return false;
- if (!bSpendZeroConfChange || !IsFromMe(ISMINE_ALL)) // using wtx's cached debit
+ if (!pwallet->m_spend_zero_conf_change || !IsFromMe(ISMINE_ALL)) // using wtx's cached debit
return false;
// Don't trust unconfirmed transactions from us unless they are in the mempool.
@@ -1666,53 +2029,54 @@ bool CWalletTx::IsTrusted() const
return false;
// Trusted if all inputs are from us and are in the mempool:
- BOOST_FOREACH(const CTxIn& txin, vin)
+ for (const CTxIn& txin : tx->vin)
{
// Transactions not sent by us: not trusted
const CWalletTx* parent = pwallet->GetWalletTx(txin.prevout.hash);
- if (parent == NULL)
+ if (parent == nullptr)
return false;
- const CTxOut& parentOut = parent->vout[txin.prevout.n];
+ const CTxOut& parentOut = parent->tx->vout[txin.prevout.n];
if (pwallet->IsMine(parentOut) != ISMINE_SPENDABLE)
return false;
}
return true;
}
-bool CWalletTx::IsEquivalentTo(const CWalletTx& tx) const
+bool CWalletTx::IsEquivalentTo(const CWalletTx& _tx) const
{
- CMutableTransaction tx1 = *this;
- CMutableTransaction tx2 = tx;
- for (unsigned int i = 0; i < tx1.vin.size(); i++) tx1.vin[i].scriptSig = CScript();
- for (unsigned int i = 0; i < tx2.vin.size(); i++) tx2.vin[i].scriptSig = CScript();
+ CMutableTransaction tx1 {*this->tx};
+ CMutableTransaction tx2 {*_tx.tx};
+ for (auto& txin : tx1.vin) txin.scriptSig = CScript();
+ for (auto& txin : tx2.vin) txin.scriptSig = CScript();
return CTransaction(tx1) == CTransaction(tx2);
}
-std::vector<uint256> CWallet::ResendWalletTransactionsBefore(int64_t nTime)
+std::vector<uint256> CWallet::ResendWalletTransactionsBefore(int64_t nTime, CConnman* connman)
{
std::vector<uint256> result;
LOCK(cs_wallet);
+
// Sort them in chronological order
- multimap<unsigned int, CWalletTx*> mapSorted;
- BOOST_FOREACH(PAIRTYPE(const uint256, CWalletTx)& item, mapWallet)
+ std::multimap<unsigned int, CWalletTx*> mapSorted;
+ for (std::pair<const uint256, CWalletTx>& item : mapWallet)
{
CWalletTx& wtx = item.second;
// Don't rebroadcast if newer than nTime:
if (wtx.nTimeReceived > nTime)
continue;
- mapSorted.insert(make_pair(wtx.nTimeReceived, &wtx));
+ mapSorted.insert(std::make_pair(wtx.nTimeReceived, &wtx));
}
- BOOST_FOREACH(PAIRTYPE(const unsigned int, CWalletTx*)& item, mapSorted)
+ for (std::pair<const unsigned int, CWalletTx*>& item : mapSorted)
{
CWalletTx& wtx = *item.second;
- if (wtx.RelayWalletTransaction())
+ if (wtx.RelayWalletTransaction(connman))
result.push_back(wtx.GetHash());
}
return result;
}
-void CWallet::ResendWalletTransactions(int64_t nBestBlockTime)
+void CWallet::ResendWalletTransactions(int64_t nBestBlockTime, CConnman* connman)
{
// Do this infrequently and randomly to avoid giving away
// that these are our transactions.
@@ -1730,9 +2094,9 @@ void CWallet::ResendWalletTransactions(int64_t nBestBlockTime)
// Rebroadcast unconfirmed txes older than 5 minutes before the last
// block was found:
- std::vector<uint256> relayed = ResendWalletTransactionsBefore(nBestBlockTime-5*60);
+ std::vector<uint256> relayed = ResendWalletTransactionsBefore(nBestBlockTime-5*60, connman);
if (!relayed.empty())
- LogPrintf("%s: rebroadcast %u unconfirmed transactions\n", __func__, relayed.size());
+ WalletLogPrintf("%s: rebroadcast %u unconfirmed transactions\n", __func__, relayed.size());
}
/** @} */ // end of mapWallet
@@ -1746,16 +2110,17 @@ void CWallet::ResendWalletTransactions(int64_t nBestBlockTime)
*/
-CAmount CWallet::GetBalance() const
+CAmount CWallet::GetBalance(const isminefilter& filter, const int min_depth) const
{
CAmount nTotal = 0;
{
LOCK2(cs_main, cs_wallet);
- for (map<uint256, CWalletTx>::const_iterator it = mapWallet.begin(); it != mapWallet.end(); ++it)
+ for (const auto& entry : mapWallet)
{
- const CWalletTx* pcoin = &(*it).second;
- if (pcoin->IsTrusted())
- nTotal += pcoin->GetAvailableCredit();
+ const CWalletTx* pcoin = &entry.second;
+ if (pcoin->IsTrusted() && pcoin->GetDepthInMainChain() >= min_depth) {
+ nTotal += pcoin->GetAvailableCredit(true, filter);
+ }
}
}
@@ -1767,9 +2132,9 @@ CAmount CWallet::GetUnconfirmedBalance() const
CAmount nTotal = 0;
{
LOCK2(cs_main, cs_wallet);
- for (map<uint256, CWalletTx>::const_iterator it = mapWallet.begin(); it != mapWallet.end(); ++it)
+ for (const auto& entry : mapWallet)
{
- const CWalletTx* pcoin = &(*it).second;
+ const CWalletTx* pcoin = &entry.second;
if (!pcoin->IsTrusted() && pcoin->GetDepthInMainChain() == 0 && pcoin->InMempool())
nTotal += pcoin->GetAvailableCredit();
}
@@ -1782,306 +2147,408 @@ CAmount CWallet::GetImmatureBalance() const
CAmount nTotal = 0;
{
LOCK2(cs_main, cs_wallet);
- for (map<uint256, CWalletTx>::const_iterator it = mapWallet.begin(); it != mapWallet.end(); ++it)
+ for (const auto& entry : mapWallet)
{
- const CWalletTx* pcoin = &(*it).second;
+ const CWalletTx* pcoin = &entry.second;
nTotal += pcoin->GetImmatureCredit();
}
}
return nTotal;
}
-CAmount CWallet::GetWatchOnlyBalance() const
+CAmount CWallet::GetUnconfirmedWatchOnlyBalance() const
{
CAmount nTotal = 0;
{
LOCK2(cs_main, cs_wallet);
- for (map<uint256, CWalletTx>::const_iterator it = mapWallet.begin(); it != mapWallet.end(); ++it)
+ for (const auto& entry : mapWallet)
{
- const CWalletTx* pcoin = &(*it).second;
- if (pcoin->IsTrusted())
- nTotal += pcoin->GetAvailableWatchOnlyCredit();
+ const CWalletTx* pcoin = &entry.second;
+ if (!pcoin->IsTrusted() && pcoin->GetDepthInMainChain() == 0 && pcoin->InMempool())
+ nTotal += pcoin->GetAvailableCredit(true, ISMINE_WATCH_ONLY);
}
}
-
return nTotal;
}
-CAmount CWallet::GetUnconfirmedWatchOnlyBalance() const
+CAmount CWallet::GetImmatureWatchOnlyBalance() const
{
CAmount nTotal = 0;
{
LOCK2(cs_main, cs_wallet);
- for (map<uint256, CWalletTx>::const_iterator it = mapWallet.begin(); it != mapWallet.end(); ++it)
+ for (const auto& entry : mapWallet)
{
- const CWalletTx* pcoin = &(*it).second;
- if (!pcoin->IsTrusted() && pcoin->GetDepthInMainChain() == 0 && pcoin->InMempool())
- nTotal += pcoin->GetAvailableWatchOnlyCredit();
+ const CWalletTx* pcoin = &entry.second;
+ nTotal += pcoin->GetImmatureWatchOnlyCredit();
}
}
return nTotal;
}
-CAmount CWallet::GetImmatureWatchOnlyBalance() const
+// Calculate total balance in a different way from GetBalance. The biggest
+// difference is that GetBalance sums up all unspent TxOuts paying to the
+// wallet, while this sums up both spent and unspent TxOuts paying to the
+// wallet, and then subtracts the values of TxIns spending from the wallet. This
+// also has fewer restrictions on which unconfirmed transactions are considered
+// trusted.
+CAmount CWallet::GetLegacyBalance(const isminefilter& filter, int minDepth, const std::string* account) const
{
- CAmount nTotal = 0;
- {
- LOCK2(cs_main, cs_wallet);
- for (map<uint256, CWalletTx>::const_iterator it = mapWallet.begin(); it != mapWallet.end(); ++it)
- {
- const CWalletTx* pcoin = &(*it).second;
- nTotal += pcoin->GetImmatureWatchOnlyCredit();
+ LOCK2(cs_main, cs_wallet);
+
+ CAmount balance = 0;
+ for (const auto& entry : mapWallet) {
+ const CWalletTx& wtx = entry.second;
+ const int depth = wtx.GetDepthInMainChain();
+ if (depth < 0 || !CheckFinalTx(*wtx.tx) || wtx.GetBlocksToMaturity() > 0) {
+ continue;
+ }
+
+ // Loop through tx outputs and add incoming payments. For outgoing txs,
+ // treat change outputs specially, as part of the amount debited.
+ CAmount debit = wtx.GetDebit(filter);
+ const bool outgoing = debit > 0;
+ for (const CTxOut& out : wtx.tx->vout) {
+ if (outgoing && IsChange(out)) {
+ debit -= out.nValue;
+ } else if (IsMine(out) & filter && depth >= minDepth && (!account || *account == GetLabelName(out.scriptPubKey))) {
+ balance += out.nValue;
+ }
+ }
+
+ // For outgoing txs, subtract amount debited.
+ if (outgoing && (!account || *account == wtx.strFromAccount)) {
+ balance -= debit;
}
}
- return nTotal;
+
+ if (account) {
+ balance += WalletBatch(*database).GetAccountCreditDebit(*account);
+ }
+
+ return balance;
}
-void CWallet::AvailableCoins(vector<COutput>& vCoins, bool fOnlyConfirmed, const CCoinControl *coinControl, bool fIncludeZeroValue) const
+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
+{
+ AssertLockHeld(cs_main);
+ AssertLockHeld(cs_wallet);
+
vCoins.clear();
+ CAmount nTotal = 0;
+ for (const auto& entry : mapWallet)
{
- LOCK2(cs_main, cs_wallet);
- for (map<uint256, CWalletTx>::const_iterator it = mapWallet.begin(); it != mapWallet.end(); ++it)
- {
- const uint256& wtxid = it->first;
- const CWalletTx* pcoin = &(*it).second;
+ const uint256& wtxid = entry.first;
+ const CWalletTx* pcoin = &entry.second;
- if (!CheckFinalTx(*pcoin))
- continue;
+ if (!CheckFinalTx(*pcoin->tx))
+ continue;
- if (fOnlyConfirmed && !pcoin->IsTrusted())
+ if (pcoin->IsCoinBase() && pcoin->GetBlocksToMaturity() > 0)
+ continue;
+
+ int nDepth = pcoin->GetDepthInMainChain();
+ if (nDepth < 0)
+ continue;
+
+ // We should not consider coins which aren't at least in our mempool
+ // It's possible for these to be conflicted via ancestors which we may never be able to detect
+ if (nDepth == 0 && !pcoin->InMempool())
+ continue;
+
+ bool safeTx = pcoin->IsTrusted();
+
+ // We should not consider coins from transactions that are replacing
+ // other transactions.
+ //
+ // Example: There is a transaction A which is replaced by bumpfee
+ // transaction B. In this case, we want to prevent creation of
+ // a transaction B' which spends an output of B.
+ //
+ // Reason: If transaction A were initially confirmed, transactions B
+ // and B' would no longer be valid, so the user would have to create
+ // a new transaction C to replace B'. However, in the case of a
+ // one-block reorg, transactions B' and C might BOTH be accepted,
+ // when the user only wanted one of them. Specifically, there could
+ // be a 1-block reorg away from the chain where transactions A and C
+ // were accepted to another chain where B, B', and C were all
+ // accepted.
+ if (nDepth == 0 && pcoin->mapValue.count("replaces_txid")) {
+ safeTx = false;
+ }
+
+ // Similarly, we should not consider coins from transactions that
+ // have been replaced. In the example above, we would want to prevent
+ // creation of a transaction A' spending an output of A, because if
+ // transaction B were initially confirmed, conflicting with A and
+ // A', we wouldn't want to the user to create a transaction D
+ // intending to replace A', but potentially resulting in a scenario
+ // where A, A', and D could all be accepted (instead of just B and
+ // D, or just A and A' like the user would want).
+ if (nDepth == 0 && pcoin->mapValue.count("replaced_by_txid")) {
+ safeTx = false;
+ }
+
+ if (fOnlySafe && !safeTx) {
+ continue;
+ }
+
+ if (nDepth < nMinDepth || nDepth > nMaxDepth)
+ continue;
+
+ for (unsigned int i = 0; i < pcoin->tx->vout.size(); i++) {
+ if (pcoin->tx->vout[i].nValue < nMinimumAmount || pcoin->tx->vout[i].nValue > nMaximumAmount)
continue;
- if (pcoin->IsCoinBase() && pcoin->GetBlocksToMaturity() > 0)
+ if (coinControl && coinControl->HasSelected() && !coinControl->fAllowOtherInputs && !coinControl->IsSelected(COutPoint(entry.first, i)))
continue;
- int nDepth = pcoin->GetDepthInMainChain();
- if (nDepth < 0)
+ if (IsLockedCoin(entry.first, i))
continue;
- // We should not consider coins which aren't at least in our mempool
- // It's possible for these to be conflicted via ancestors which we may never be able to detect
- if (nDepth == 0 && !pcoin->InMempool())
+ if (IsSpent(wtxid, i))
continue;
- for (unsigned int i = 0; i < pcoin->vout.size(); i++) {
- isminetype mine = IsMine(pcoin->vout[i]);
- if (!(IsSpent(wtxid, i)) && mine != ISMINE_NO &&
- !IsLockedCoin((*it).first, i) && (pcoin->vout[i].nValue > 0 || fIncludeZeroValue) &&
- (!coinControl || !coinControl->HasSelected() || coinControl->fAllowOtherInputs || coinControl->IsSelected(COutPoint((*it).first, i))))
- vCoins.push_back(COutput(pcoin, i, nDepth,
- ((mine & ISMINE_SPENDABLE) != ISMINE_NO) ||
- (coinControl && coinControl->fAllowWatchOnly && (mine & ISMINE_WATCH_SOLVABLE) != ISMINE_NO),
- (mine & (ISMINE_SPENDABLE | ISMINE_WATCH_SOLVABLE)) != ISMINE_NO));
+ isminetype mine = IsMine(pcoin->tx->vout[i]);
+
+ if (mine == ISMINE_NO) {
+ continue;
}
- }
- }
-}
-static void ApproximateBestSubset(vector<pair<CAmount, pair<const CWalletTx*,unsigned int> > >vValue, const CAmount& nTotalLower, const CAmount& nTargetValue,
- vector<char>& vfBest, CAmount& nBest, int iterations = 1000)
-{
- vector<char> vfIncluded;
+ bool solvable = IsSolvable(*this, pcoin->tx->vout[i].scriptPubKey);
+ bool spendable = ((mine & ISMINE_SPENDABLE) != ISMINE_NO) || (((mine & ISMINE_WATCH_ONLY) != ISMINE_NO) && (coinControl && coinControl->fAllowWatchOnly && solvable));
- vfBest.assign(vValue.size(), true);
- nBest = nTotalLower;
+ vCoins.push_back(COutput(pcoin, i, nDepth, spendable, solvable, safeTx, (coinControl && coinControl->fAllowWatchOnly)));
- seed_insecure_rand();
+ // Checks the sum amount of all UTXO's.
+ if (nMinimumSumAmount != MAX_MONEY) {
+ nTotal += pcoin->tx->vout[i].nValue;
- for (int nRep = 0; nRep < iterations && nBest != nTargetValue; nRep++)
- {
- vfIncluded.assign(vValue.size(), false);
- CAmount nTotal = 0;
- bool fReachedTarget = false;
- for (int nPass = 0; nPass < 2 && !fReachedTarget; nPass++)
- {
- for (unsigned int i = 0; i < vValue.size(); i++)
- {
- //The solver here uses a randomized algorithm,
- //the randomness serves no real security purpose but is just
- //needed to prevent degenerate behavior and it is important
- //that the rng is fast. We do not use a constant random sequence,
- //because there may be some privacy improvement by making
- //the selection random.
- if (nPass == 0 ? insecure_rand()&1 : !vfIncluded[i])
- {
- nTotal += vValue[i].first;
- vfIncluded[i] = true;
- if (nTotal >= nTargetValue)
- {
- fReachedTarget = true;
- if (nTotal < nBest)
- {
- nBest = nTotal;
- vfBest = vfIncluded;
- }
- nTotal -= vValue[i].first;
- vfIncluded[i] = false;
- }
+ if (nTotal >= nMinimumSumAmount) {
+ return;
}
}
+
+ // Checks the maximum number of UTXO's.
+ if (nMaximumCount > 0 && vCoins.size() >= nMaximumCount) {
+ return;
+ }
}
}
}
-bool CWallet::SelectCoinsMinConf(const CAmount& nTargetValue, int nConfMine, int nConfTheirs, vector<COutput> vCoins,
- set<pair<const CWalletTx*,unsigned int> >& setCoinsRet, CAmount& nValueRet) const
+std::map<CTxDestination, std::vector<COutput>> CWallet::ListCoins() const
{
- setCoinsRet.clear();
- nValueRet = 0;
-
- // List of values less than target
- pair<CAmount, pair<const CWalletTx*,unsigned int> > coinLowestLarger;
- coinLowestLarger.first = std::numeric_limits<CAmount>::max();
- coinLowestLarger.second.first = NULL;
- vector<pair<CAmount, pair<const CWalletTx*,unsigned int> > > vValue;
- CAmount nTotalLower = 0;
-
- random_shuffle(vCoins.begin(), vCoins.end(), GetRandInt);
-
- BOOST_FOREACH(const COutput &output, vCoins)
- {
- if (!output.fSpendable)
- continue;
-
- const CWalletTx *pcoin = output.tx;
-
- if (output.nDepth < (pcoin->IsFromMe(ISMINE_ALL) ? nConfMine : nConfTheirs))
- continue;
+ // 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.
- int i = output.i;
- CAmount n = pcoin->vout[i].nValue;
+ std::map<CTxDestination, std::vector<COutput>> result;
+ std::vector<COutput> availableCoins;
- pair<CAmount,pair<const CWalletTx*,unsigned int> > coin = make_pair(n,make_pair(pcoin, i));
+ LOCK2(cs_main, cs_wallet);
+ AvailableCoins(availableCoins);
- if (n == nTargetValue)
- {
- setCoinsRet.insert(coin.second);
- nValueRet += coin.first;
- return true;
- }
- else if (n < nTargetValue + MIN_CHANGE)
- {
- vValue.push_back(coin);
- nTotalLower += n;
- }
- else if (n < coinLowestLarger.first)
- {
- coinLowestLarger = coin;
+ 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));
}
}
- if (nTotalLower == nTargetValue)
- {
- for (unsigned int i = 0; i < vValue.size(); ++i)
- {
- setCoinsRet.insert(vValue[i].second);
- nValueRet += vValue[i].first;
+ 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 true;
}
- if (nTotalLower < nTargetValue)
- {
- if (coinLowestLarger.second.first == NULL)
- return false;
- setCoinsRet.insert(coinLowestLarger.second);
- nValueRet += coinLowestLarger.first;
- return true;
- }
+ return result;
+}
- // Solve subset sum by stochastic approximation
- std::sort(vValue.begin(), vValue.end(), CompareValueOnly());
- std::reverse(vValue.begin(), vValue.end());
- vector<char> vfBest;
- CAmount nBest;
+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];
+}
- ApproximateBestSubset(vValue, nTotalLower, nTargetValue, vfBest, nBest);
- if (nBest != nTargetValue && nTotalLower >= nTargetValue + MIN_CHANGE)
- ApproximateBestSubset(vValue, nTotalLower, nTargetValue + MIN_CHANGE, vfBest, nBest);
+bool CWallet::SelectCoinsMinConf(const CAmount& nTargetValue, const CoinEligibilityFilter& eligibility_filter, std::vector<OutputGroup> groups,
+ std::set<CInputCoin>& setCoinsRet, CAmount& nValueRet, const CoinSelectionParams& coin_selection_params, bool& bnb_used) const
+{
+ setCoinsRet.clear();
+ nValueRet = 0;
- // If we have a bigger coin and (either the stochastic approximation didn't find a good solution,
- // or the next bigger coin is closer), return the bigger coin
- if (coinLowestLarger.second.first &&
- ((nBest != nTargetValue && nBest < nTargetValue + MIN_CHANGE) || coinLowestLarger.first <= nBest))
- {
- setCoinsRet.insert(coinLowestLarger.second);
- nValueRet += coinLowestLarger.first;
- }
- else {
- for (unsigned int i = 0; i < vValue.size(); i++)
- if (vfBest[i])
- {
- setCoinsRet.insert(vValue[i].second);
- nValueRet += vValue[i].first;
+ std::vector<OutputGroup> utxo_pool;
+ if (coin_selection_params.use_bnb) {
+ // Get long term estimate
+ FeeCalculation feeCalc;
+ CCoinControl temp;
+ temp.m_confirm_target = 1008;
+ CFeeRate long_term_feerate = GetMinimumFeeRate(*this, temp, ::mempool, ::feeEstimator, &feeCalc);
+
+ // Calculate cost of change
+ CAmount cost_of_change = GetDiscardRate(*this, ::feeEstimator).GetFee(coin_selection_params.change_spend_size) + coin_selection_params.effective_fee.GetFee(coin_selection_params.change_output_size);
+
+ // Filter by the min conf specs and add to utxo_pool and calculate effective value
+ for (OutputGroup& group : groups) {
+ if (!group.EligibleForSpending(eligibility_filter)) continue;
+
+ group.fee = 0;
+ group.long_term_fee = 0;
+ group.effective_value = 0;
+ for (auto it = group.m_outputs.begin(); it != group.m_outputs.end(); ) {
+ const CInputCoin& coin = *it;
+ CAmount effective_value = coin.txout.nValue - (coin.m_input_bytes < 0 ? 0 : coin_selection_params.effective_fee.GetFee(coin.m_input_bytes));
+ // Only include outputs that are positive effective value (i.e. not dust)
+ if (effective_value > 0) {
+ group.fee += coin.m_input_bytes < 0 ? 0 : coin_selection_params.effective_fee.GetFee(coin.m_input_bytes);
+ group.long_term_fee += coin.m_input_bytes < 0 ? 0 : long_term_feerate.GetFee(coin.m_input_bytes);
+ group.effective_value += effective_value;
+ ++it;
+ } else {
+ it = group.Discard(coin);
+ }
}
-
- LogPrint("selectcoins", "SelectCoins() best subset: ");
- for (unsigned int i = 0; i < vValue.size(); i++)
- if (vfBest[i])
- LogPrint("selectcoins", "%s ", FormatMoney(vValue[i].first));
- LogPrint("selectcoins", "total %s\n", FormatMoney(nBest));
+ if (group.effective_value > 0) utxo_pool.push_back(group);
+ }
+ // Calculate the fees for things that aren't inputs
+ CAmount not_input_fees = coin_selection_params.effective_fee.GetFee(coin_selection_params.tx_noinputs_size);
+ bnb_used = true;
+ return SelectCoinsBnB(utxo_pool, nTargetValue, cost_of_change, setCoinsRet, nValueRet, not_input_fees);
+ } else {
+ // Filter by the min conf specs and add to utxo_pool
+ for (const OutputGroup& group : groups) {
+ if (!group.EligibleForSpending(eligibility_filter)) continue;
+ utxo_pool.push_back(group);
+ }
+ bnb_used = false;
+ return KnapsackSolver(nTargetValue, utxo_pool, setCoinsRet, nValueRet);
}
-
- return true;
}
-bool CWallet::SelectCoins(const vector<COutput>& vAvailableCoins, const CAmount& nTargetValue, set<pair<const CWalletTx*,unsigned int> >& setCoinsRet, CAmount& nValueRet, const CCoinControl* coinControl) const
+bool CWallet::SelectCoins(const std::vector<COutput>& vAvailableCoins, const CAmount& nTargetValue, std::set<CInputCoin>& setCoinsRet, CAmount& nValueRet, const CCoinControl& coin_control, CoinSelectionParams& coin_selection_params, bool& bnb_used) const
{
- vector<COutput> vCoins(vAvailableCoins);
+ std::vector<COutput> vCoins(vAvailableCoins);
// coin control -> return all selected outputs (we want all selected to go into the transaction for sure)
- if (coinControl && coinControl->HasSelected() && !coinControl->fAllowOtherInputs)
+ if (coin_control.HasSelected() && !coin_control.fAllowOtherInputs)
{
- BOOST_FOREACH(const COutput& out, vCoins)
+ // We didn't use BnB here, so set it to false.
+ bnb_used = false;
+
+ for (const COutput& out : vCoins)
{
if (!out.fSpendable)
continue;
- nValueRet += out.tx->vout[out.i].nValue;
- setCoinsRet.insert(make_pair(out.tx, out.i));
+ nValueRet += out.tx->tx->vout[out.i].nValue;
+ setCoinsRet.insert(out.GetInputCoin());
}
return (nValueRet >= nTargetValue);
}
// calculate value from preset inputs and store them
- set<pair<const CWalletTx*, uint32_t> > setPresetCoins;
+ std::set<CInputCoin> setPresetCoins;
CAmount nValueFromPresetInputs = 0;
std::vector<COutPoint> vPresetInputs;
- if (coinControl)
- coinControl->ListSelected(vPresetInputs);
- BOOST_FOREACH(const COutPoint& outpoint, vPresetInputs)
+ coin_control.ListSelected(vPresetInputs);
+ for (const COutPoint& outpoint : vPresetInputs)
{
- map<uint256, CWalletTx>::const_iterator it = mapWallet.find(outpoint.hash);
+ // For now, don't use BnB if preset inputs are selected. TODO: Enable this later
+ bnb_used = false;
+ coin_selection_params.use_bnb = false;
+
+ std::map<uint256, CWalletTx>::const_iterator it = mapWallet.find(outpoint.hash);
if (it != mapWallet.end())
{
const CWalletTx* pcoin = &it->second;
// Clearly invalid input, fail
- if (pcoin->vout.size() <= outpoint.n)
+ if (pcoin->tx->vout.size() <= outpoint.n)
return false;
- nValueFromPresetInputs += pcoin->vout[outpoint.n].nValue;
- setPresetCoins.insert(make_pair(pcoin, outpoint.n));
+ // Just to calculate the marginal byte size
+ nValueFromPresetInputs += pcoin->tx->vout[outpoint.n].nValue;
+ setPresetCoins.insert(CInputCoin(pcoin->tx, outpoint.n));
} else
return false; // TODO: Allow non-wallet inputs
}
// remove preset inputs from vCoins
- for (vector<COutput>::iterator it = vCoins.begin(); it != vCoins.end() && coinControl && coinControl->HasSelected();)
+ for (std::vector<COutput>::iterator it = vCoins.begin(); it != vCoins.end() && coin_control.HasSelected();)
{
- if (setPresetCoins.count(make_pair(it->tx, it->i)))
+ if (setPresetCoins.count(it->GetInputCoin()))
it = vCoins.erase(it);
else
++it;
}
+ // form groups from remaining coins; note that preset coins will not
+ // automatically have their associated (same address) coins included
+ if (coin_control.m_avoid_partial_spends && vCoins.size() > OUTPUT_GROUP_MAX_ENTRIES) {
+ // Cases where we have 11+ outputs all pointing to the same destination may result in
+ // privacy leaks as they will potentially be deterministically sorted. We solve that by
+ // explicitly shuffling the outputs before processing
+ std::shuffle(vCoins.begin(), vCoins.end(), FastRandomContext());
+ }
+ std::vector<OutputGroup> groups = GroupOutputs(vCoins, !coin_control.m_avoid_partial_spends);
+
+ size_t max_ancestors = (size_t)std::max<int64_t>(1, gArgs.GetArg("-limitancestorcount", DEFAULT_ANCESTOR_LIMIT));
+ size_t max_descendants = (size_t)std::max<int64_t>(1, gArgs.GetArg("-limitdescendantcount", DEFAULT_DESCENDANT_LIMIT));
+ bool fRejectLongChains = gArgs.GetBoolArg("-walletrejectlongchains", DEFAULT_WALLET_REJECT_LONG_CHAINS);
+
bool res = nTargetValue <= nValueFromPresetInputs ||
- SelectCoinsMinConf(nTargetValue - nValueFromPresetInputs, 1, 6, vCoins, setCoinsRet, nValueRet) ||
- SelectCoinsMinConf(nTargetValue - nValueFromPresetInputs, 1, 1, vCoins, setCoinsRet, nValueRet) ||
- (bSpendZeroConfChange && SelectCoinsMinConf(nTargetValue - nValueFromPresetInputs, 0, 1, vCoins, setCoinsRet, nValueRet));
+ SelectCoinsMinConf(nTargetValue - nValueFromPresetInputs, CoinEligibilityFilter(1, 6, 0), groups, setCoinsRet, nValueRet, coin_selection_params, bnb_used) ||
+ SelectCoinsMinConf(nTargetValue - nValueFromPresetInputs, CoinEligibilityFilter(1, 1, 0), groups, setCoinsRet, nValueRet, coin_selection_params, bnb_used) ||
+ (m_spend_zero_conf_change && SelectCoinsMinConf(nTargetValue - nValueFromPresetInputs, CoinEligibilityFilter(0, 1, 2), groups, setCoinsRet, nValueRet, coin_selection_params, bnb_used)) ||
+ (m_spend_zero_conf_change && SelectCoinsMinConf(nTargetValue - nValueFromPresetInputs, CoinEligibilityFilter(0, 1, std::min((size_t)4, max_ancestors/3), std::min((size_t)4, max_descendants/3)), groups, setCoinsRet, nValueRet, coin_selection_params, bnb_used)) ||
+ (m_spend_zero_conf_change && SelectCoinsMinConf(nTargetValue - nValueFromPresetInputs, CoinEligibilityFilter(0, 1, max_ancestors/2, max_descendants/2), groups, setCoinsRet, nValueRet, coin_selection_params, bnb_used)) ||
+ (m_spend_zero_conf_change && SelectCoinsMinConf(nTargetValue - nValueFromPresetInputs, CoinEligibilityFilter(0, 1, max_ancestors-1, max_descendants-1), groups, setCoinsRet, nValueRet, coin_selection_params, bnb_used)) ||
+ (m_spend_zero_conf_change && !fRejectLongChains && SelectCoinsMinConf(nTargetValue - nValueFromPresetInputs, CoinEligibilityFilter(0, 1, std::numeric_limits<uint64_t>::max()), groups, setCoinsRet, nValueRet, coin_selection_params, bnb_used));
// because SelectCoinsMinConf clears the setCoinsRet, we now add the possible inputs to the coinset
- setCoinsRet.insert(setPresetCoins.begin(), setPresetCoins.end());
+ util::insert(setCoinsRet, setPresetCoins);
// add preset inputs to the total value selected
nValueRet += nValueFromPresetInputs;
@@ -2089,46 +2556,76 @@ bool CWallet::SelectCoins(const vector<COutput>& vAvailableCoins, const CAmount&
return res;
}
-bool CWallet::FundTransaction(CMutableTransaction& tx, CAmount& nFeeRet, bool overrideEstimatedFeeRate, const CFeeRate& specificFeeRate, int& nChangePosInOut, std::string& strFailReason, bool includeWatching, bool lockUnspents, const CTxDestination& destChange)
+bool CWallet::SignTransaction(CMutableTransaction &tx)
{
- vector<CRecipient> vecSend;
+ AssertLockHeld(cs_wallet); // mapWallet
- // Turn the txout set into a CRecipient vector
- BOOST_FOREACH(const CTxOut& txOut, tx.vout)
- {
- CRecipient recipient = {txOut.scriptPubKey, txOut.nValue, false};
+ // sign the new tx
+ int nIn = 0;
+ for (auto& input : tx.vin) {
+ std::map<uint256, CWalletTx>::const_iterator mi = mapWallet.find(input.prevout.hash);
+ if(mi == mapWallet.end() || input.prevout.n >= mi->second.tx->vout.size()) {
+ return false;
+ }
+ const CScript& scriptPubKey = mi->second.tx->vout[input.prevout.n].scriptPubKey;
+ const CAmount& amount = mi->second.tx->vout[input.prevout.n].nValue;
+ SignatureData sigdata;
+ if (!ProduceSignature(*this, MutableTransactionSignatureCreator(&tx, nIn, amount, SIGHASH_ALL), scriptPubKey, sigdata)) {
+ return false;
+ }
+ UpdateInput(input, sigdata);
+ nIn++;
+ }
+ return true;
+}
+
+bool CWallet::FundTransaction(CMutableTransaction& tx, CAmount& nFeeRet, int& nChangePosInOut, std::string& strFailReason, bool lockUnspents, const std::set<int>& setSubtractFeeFromOutputs, CCoinControl coinControl)
+{
+ std::vector<CRecipient> vecSend;
+
+ // Turn the txout set into a CRecipient vector.
+ for (size_t idx = 0; idx < tx.vout.size(); idx++) {
+ const CTxOut& txOut = tx.vout[idx];
+ CRecipient recipient = {txOut.scriptPubKey, txOut.nValue, setSubtractFeeFromOutputs.count(idx) == 1};
vecSend.push_back(recipient);
}
- CCoinControl coinControl;
- coinControl.destChange = destChange;
coinControl.fAllowOtherInputs = true;
- coinControl.fAllowWatchOnly = includeWatching;
- coinControl.fOverrideFeeRate = overrideEstimatedFeeRate;
- coinControl.nFeeRate = specificFeeRate;
- BOOST_FOREACH(const CTxIn& txin, tx.vin)
+ for (const CTxIn& txin : tx.vin) {
coinControl.Select(txin.prevout);
+ }
+
+ // Acquire the locks to prevent races to the new locked unspents between the
+ // CreateTransaction call and LockCoin calls (when lockUnspents is true).
+ LOCK2(cs_main, cs_wallet);
CReserveKey reservekey(this);
- CWalletTx wtx;
- if (!CreateTransaction(vecSend, wtx, reservekey, nFeeRet, nChangePosInOut, strFailReason, &coinControl, false))
+ CTransactionRef tx_new;
+ if (!CreateTransaction(vecSend, tx_new, reservekey, nFeeRet, nChangePosInOut, strFailReason, coinControl, false)) {
return false;
+ }
- if (nChangePosInOut != -1)
- tx.vout.insert(tx.vout.begin() + nChangePosInOut, wtx.vout[nChangePosInOut]);
+ if (nChangePosInOut != -1) {
+ tx.vout.insert(tx.vout.begin() + nChangePosInOut, tx_new->vout[nChangePosInOut]);
+ // We don't have the normal Create/Commit cycle, and don't want to risk
+ // reusing change, so just remove the key from the keypool here.
+ reservekey.KeepKey();
+ }
- // Add new txins (keeping original txin scriptSig/order)
- BOOST_FOREACH(const CTxIn& txin, wtx.vin)
- {
- if (!coinControl.IsSelected(txin.prevout))
- {
+ // Copy output sizes from new transaction; they may have had the fee
+ // subtracted from them.
+ for (unsigned int idx = 0; idx < tx.vout.size(); idx++) {
+ tx.vout[idx].nValue = tx_new->vout[idx].nValue;
+ }
+
+ // Add new txins while keeping original txin scriptSig/order.
+ for (const CTxIn& txin : tx_new->vin) {
+ if (!coinControl.IsSelected(txin.prevout)) {
tx.vin.push_back(txin);
- if (lockUnspents)
- {
- LOCK2(cs_main, cs_wallet);
- LockCoin(txin.prevout);
+ if (lockUnspents) {
+ LockCoin(txin.prevout);
}
}
}
@@ -2136,17 +2633,45 @@ bool CWallet::FundTransaction(CMutableTransaction& tx, CAmount& nFeeRet, bool ov
return true;
}
-bool CWallet::CreateTransaction(const vector<CRecipient>& vecSend, CWalletTx& wtxNew, CReserveKey& reservekey, CAmount& nFeeRet,
- int& nChangePosInOut, std::string& strFailReason, const CCoinControl* coinControl, bool sign)
+OutputType CWallet::TransactionChangeType(OutputType change_type, const std::vector<CRecipient>& vecSend)
+{
+ // If -changetype is specified, always use that change type.
+ if (change_type != OutputType::CHANGE_AUTO) {
+ return change_type;
+ }
+
+ // if m_default_address_type is legacy, use legacy address as change (even
+ // if some of the outputs are P2WPKH or P2WSH).
+ if (m_default_address_type == OutputType::LEGACY) {
+ return OutputType::LEGACY;
+ }
+
+ // if any destination is P2WPKH or P2WSH, use P2WPKH for the change
+ // output.
+ for (const auto& recipient : vecSend) {
+ // Check if any destination contains a witness program:
+ int witnessversion = 0;
+ std::vector<unsigned char> witnessprogram;
+ if (recipient.scriptPubKey.IsWitnessProgram(witnessversion, witnessprogram)) {
+ return OutputType::BECH32;
+ }
+ }
+
+ // else use m_default_address_type for change
+ return m_default_address_type;
+}
+
+bool CWallet::CreateTransaction(const std::vector<CRecipient>& vecSend, CTransactionRef& tx, CReserveKey& reservekey, CAmount& nFeeRet,
+ int& nChangePosInOut, std::string& strFailReason, const CCoinControl& coin_control, bool sign)
{
CAmount nValue = 0;
int nChangePosRequest = nChangePosInOut;
unsigned int nSubtractFeeFromAmount = 0;
- BOOST_FOREACH (const CRecipient& recipient, vecSend)
+ for (const auto& recipient : vecSend)
{
if (nValue < 0 || recipient.nAmount < 0)
{
- strFailReason = _("Transaction amounts must be positive");
+ strFailReason = _("Transaction amounts must not be negative");
return false;
}
nValue += recipient.nAmount;
@@ -2154,14 +2679,12 @@ bool CWallet::CreateTransaction(const vector<CRecipient>& vecSend, CWalletTx& wt
if (recipient.fSubtractFeeFromAmount)
nSubtractFeeFromAmount++;
}
- if (vecSend.empty() || nValue < 0)
+ if (vecSend.empty())
{
- strFailReason = _("Transaction amounts must be positive");
+ strFailReason = _("Transaction must have at least one recipient");
return false;
}
- wtxNew.fTimeReceivedIsTxTime = true;
- wtxNew.BindWallet(this);
CMutableTransaction txNew;
// Discourage fee sniping.
@@ -2195,34 +2718,88 @@ bool CWallet::CreateTransaction(const vector<CRecipient>& vecSend, CWalletTx& wt
assert(txNew.nLockTime <= (unsigned int)chainActive.Height());
assert(txNew.nLockTime < LOCKTIME_THRESHOLD);
-
+ FeeCalculation feeCalc;
+ CAmount nFeeNeeded;
+ int nBytes;
{
+ std::set<CInputCoin> setCoins;
LOCK2(cs_main, cs_wallet);
{
std::vector<COutput> vAvailableCoins;
- AvailableCoins(vAvailableCoins, true, coinControl);
+ AvailableCoins(vAvailableCoins, true, &coin_control);
+ CoinSelectionParams coin_selection_params; // Parameters for coin selection, init with dummy
+
+ // Create change script that will be used if we need change
+ // TODO: pass in scriptChange instead of reservekey so
+ // change transaction isn't always pay-to-bitcoin-address
+ CScript scriptChange;
+
+ // coin control: send change to custom address
+ if (!boost::get<CNoDestination>(&coin_control.destChange)) {
+ scriptChange = GetScriptForDestination(coin_control.destChange);
+ } else { // no coin control: send change to newly generated address
+ // Note: We use a new key here to keep it from being obvious which side is the change.
+ // The drawback is that by not reusing a previous key, the change may be lost if a
+ // backup is restored, if the backup doesn't have the new private key for the change.
+ // If we reused the old key, it would be possible to add code to look for and
+ // rediscover unknown transactions that were written with keys of ours to recover
+ // post-backup change.
+
+ // Reserve a new key pair from key pool
+ if (IsWalletFlagSet(WALLET_FLAG_DISABLE_PRIVATE_KEYS)) {
+ strFailReason = _("Can't generate a change-address key. Private keys are disabled for this wallet.");
+ return false;
+ }
+ CPubKey vchPubKey;
+ bool ret;
+ ret = reservekey.GetReservedKey(vchPubKey, true);
+ if (!ret)
+ {
+ strFailReason = _("Keypool ran out, please call keypoolrefill first");
+ return false;
+ }
+
+ const OutputType change_type = TransactionChangeType(coin_control.m_change_type ? *coin_control.m_change_type : m_default_change_type, vecSend);
+
+ LearnRelatedScripts(vchPubKey, change_type);
+ scriptChange = GetScriptForDestination(GetDestinationForKey(vchPubKey, change_type));
+ }
+ CTxOut change_prototype_txout(0, scriptChange);
+ coin_selection_params.change_output_size = GetSerializeSize(change_prototype_txout, SER_DISK, 0);
+
+ CFeeRate discard_rate = GetDiscardRate(*this, ::feeEstimator);
+
+ // Get the fee rate to use effective values in coin selection
+ CFeeRate nFeeRateNeeded = GetMinimumFeeRate(*this, coin_control, ::mempool, ::feeEstimator, &feeCalc);
nFeeRet = 0;
+ bool pick_new_inputs = true;
+ CAmount nValueIn = 0;
+
+ // BnB selector is the only selector used when this is true.
+ // That should only happen on the first pass through the loop.
+ coin_selection_params.use_bnb = nSubtractFeeFromAmount == 0; // If we are doing subtract fee from recipient, then don't use BnB
// Start with no fee and loop until there is enough fee
while (true)
{
nChangePosInOut = nChangePosRequest;
txNew.vin.clear();
txNew.vout.clear();
- wtxNew.fFromMe = true;
bool fFirst = true;
CAmount nValueToSelect = nValue;
if (nSubtractFeeFromAmount == 0)
nValueToSelect += nFeeRet;
- double dPriority = 0;
+
// vouts to the payees
- BOOST_FOREACH (const CRecipient& recipient, vecSend)
+ coin_selection_params.tx_noinputs_size = 11; // Static vsize overhead + outputs vsize. 4 nVersion, 4 nLocktime, 1 input count, 1 output count, 1 witness overhead (dummy, flag, stack size)
+ for (const auto& recipient : vecSend)
{
CTxOut txout(recipient.nAmount, recipient.scriptPubKey);
if (recipient.fSubtractFeeFromAmount)
{
+ assert(nSubtractFeeFromAmount != 0);
txout.nValue -= nFeeRet / nSubtractFeeFromAmount; // Subtract fee equally from each selected recipient
if (fFirst) // first receiver pays the remainder not divisible by output count
@@ -2231,8 +2808,10 @@ bool CWallet::CreateTransaction(const vector<CRecipient>& vecSend, CWalletTx& wt
txout.nValue -= nFeeRet % nSubtractFeeFromAmount;
}
}
+ // Include the fee cost for outputs. Note this is only used for BnB right now
+ coin_selection_params.tx_noinputs_size += ::GetSerializeSize(txout, SER_NETWORK, PROTOCOL_VERSION);
- if (txout.IsDust(::minRelayTxFee))
+ if (IsDust(txout, ::dustRelayFee))
{
if (recipient.fSubtractFeeFromAmount && nFeeRet > 0)
{
@@ -2249,89 +2828,39 @@ bool CWallet::CreateTransaction(const vector<CRecipient>& vecSend, CWalletTx& wt
}
// Choose coins to use
- set<pair<const CWalletTx*,unsigned int> > setCoins;
- CAmount nValueIn = 0;
- if (!SelectCoins(vAvailableCoins, nValueToSelect, setCoins, nValueIn, coinControl))
- {
- strFailReason = _("Insufficient funds");
- return false;
- }
- BOOST_FOREACH(PAIRTYPE(const CWalletTx*, unsigned int) pcoin, setCoins)
- {
- CAmount nCredit = pcoin.first->vout[pcoin.second].nValue;
- //The coin age after the next block (depth+1) is used instead of the current,
- //reflecting an assumption the user would accept a bit more delay for
- //a chance at a free transaction.
- //But mempool inputs might still be in the mempool, so their age stays 0
- int age = pcoin.first->GetDepthInMainChain();
- assert(age >= 0);
- if (age != 0)
- age += 1;
- dPriority += (double)nCredit * age;
+ bool bnb_used;
+ if (pick_new_inputs) {
+ nValueIn = 0;
+ setCoins.clear();
+ coin_selection_params.change_spend_size = CalculateMaximumSignedInputSize(change_prototype_txout, this);
+ coin_selection_params.effective_fee = nFeeRateNeeded;
+ if (!SelectCoins(vAvailableCoins, nValueToSelect, setCoins, nValueIn, coin_control, coin_selection_params, bnb_used))
+ {
+ // If BnB was used, it was the first pass. No longer the first pass and continue loop with knapsack.
+ if (bnb_used) {
+ coin_selection_params.use_bnb = false;
+ continue;
+ }
+ else {
+ strFailReason = _("Insufficient funds");
+ return false;
+ }
+ }
}
const CAmount nChange = nValueIn - nValueToSelect;
if (nChange > 0)
{
// Fill a vout to ourself
- // TODO: pass in scriptChange instead of reservekey so
- // change transaction isn't always pay-to-bitcoin-address
- CScript scriptChange;
-
- // coin control: send change to custom address
- if (coinControl && !boost::get<CNoDestination>(&coinControl->destChange))
- scriptChange = GetScriptForDestination(coinControl->destChange);
-
- // no coin control: send change to newly generated address
- else
- {
- // Note: We use a new key here to keep it from being obvious which side is the change.
- // The drawback is that by not reusing a previous key, the change may be lost if a
- // backup is restored, if the backup doesn't have the new private key for the change.
- // If we reused the old key, it would be possible to add code to look for and
- // rediscover unknown transactions that were written with keys of ours to recover
- // post-backup change.
-
- // Reserve a new key pair from key pool
- CPubKey vchPubKey;
- bool ret;
- ret = reservekey.GetReservedKey(vchPubKey);
- assert(ret); // should never fail, as we just unlocked
-
- scriptChange = GetScriptForDestination(vchPubKey.GetID());
- }
-
CTxOut newTxOut(nChange, scriptChange);
- // We do not move dust-change to fees, because the sender would end up paying more than requested.
- // This would be against the purpose of the all-inclusive feature.
- // So instead we raise the change and deduct from the recipient.
- if (nSubtractFeeFromAmount > 0 && newTxOut.IsDust(::minRelayTxFee))
- {
- CAmount nDust = newTxOut.GetDustThreshold(::minRelayTxFee) - newTxOut.nValue;
- newTxOut.nValue += nDust; // raise change until no more dust
- for (unsigned int i = 0; i < vecSend.size(); i++) // subtract from first recipient
- {
- if (vecSend[i].fSubtractFeeFromAmount)
- {
- txNew.vout[i].nValue -= nDust;
- if (txNew.vout[i].IsDust(::minRelayTxFee))
- {
- strFailReason = _("The transaction amount is too small to send after the fee has been deducted");
- return false;
- }
- break;
- }
- }
- }
-
// Never create dust outputs; if we would, just
// add the dust to the fee.
- if (newTxOut.IsDust(::minRelayTxFee))
+ // The nChange when BnB is used is always going to go to fees.
+ if (IsDust(newTxOut, discard_rate) || bnb_used)
{
nChangePosInOut = -1;
nFeeRet += nChange;
- reservekey.ReturnKey();
}
else
{
@@ -2346,82 +2875,31 @@ bool CWallet::CreateTransaction(const vector<CRecipient>& vecSend, CWalletTx& wt
return false;
}
- vector<CTxOut>::iterator position = txNew.vout.begin()+nChangePosInOut;
+ std::vector<CTxOut>::iterator position = txNew.vout.begin()+nChangePosInOut;
txNew.vout.insert(position, newTxOut);
}
+ } else {
+ nChangePosInOut = -1;
}
- else
- reservekey.ReturnKey();
- // Fill vin
+ // Dummy fill vin for maximum size estimation
//
- // Note how the sequence number is set to max()-1 so that the
- // nLockTime set above actually works.
- BOOST_FOREACH(const PAIRTYPE(const CWalletTx*,unsigned int)& coin, setCoins)
- txNew.vin.push_back(CTxIn(coin.first->GetHash(),coin.second,CScript(),
- std::numeric_limits<unsigned int>::max()-1));
-
- // Sign
- int nIn = 0;
- CTransaction txNewConst(txNew);
- BOOST_FOREACH(const PAIRTYPE(const CWalletTx*,unsigned int)& coin, setCoins)
- {
- bool signSuccess;
- const CScript& scriptPubKey = coin.first->vout[coin.second].scriptPubKey;
- SignatureData sigdata;
- if (sign)
- signSuccess = ProduceSignature(TransactionSignatureCreator(this, &txNewConst, nIn, coin.first->vout[coin.second].nValue, SIGHASH_ALL), scriptPubKey, sigdata);
- else
- signSuccess = ProduceSignature(DummySignatureCreator(this), scriptPubKey, sigdata);
-
- if (!signSuccess)
- {
- strFailReason = _("Signing transaction failed");
- return false;
- } else {
- UpdateTransaction(txNew, nIn, sigdata);
- }
-
- nIn++;
+ for (const auto& coin : setCoins) {
+ txNew.vin.push_back(CTxIn(coin.outpoint,CScript()));
}
- unsigned int nBytes = GetVirtualTransactionSize(txNew);
-
- // Remove scriptSigs if we used dummy signatures for fee calculation
- if (!sign) {
- BOOST_FOREACH (CTxIn& vin, txNew.vin)
- vin.scriptSig = CScript();
- txNew.wit.SetNull();
- }
-
- // Embed the constructed transaction data in wtxNew.
- *static_cast<CTransaction*>(&wtxNew) = CTransaction(txNew);
-
- // Limit size
- if (GetTransactionWeight(txNew) >= MAX_STANDARD_TX_WEIGHT)
- {
- strFailReason = _("Transaction too large");
+ nBytes = CalculateMaximumSignedTxSize(txNew, this, coin_control.fAllowWatchOnly);
+ if (nBytes < 0) {
+ strFailReason = _("Signing transaction failed");
return false;
}
- dPriority = wtxNew.ComputePriority(dPriority, nBytes);
-
- // Can we complete this as a free transaction?
- if (fSendFreeTransactions && nBytes <= MAX_FREE_TRANSACTION_CREATE_SIZE)
- {
- // Not enough fee: enough priority?
- double dPriorityNeeded = mempool.estimateSmartPriority(nTxConfirmTarget);
- // Require at least hard-coded AllowFree.
- if (dPriority >= dPriorityNeeded && AllowFree(dPriority))
- break;
- }
-
- CAmount nFeeNeeded = GetMinimumFee(nBytes, nTxConfirmTarget, mempool);
- if (coinControl && nFeeNeeded > 0 && coinControl->nMinimumTotalFee > nFeeNeeded) {
- nFeeNeeded = coinControl->nMinimumTotalFee;
+ nFeeNeeded = GetMinimumFee(*this, nBytes, coin_control, ::mempool, ::feeEstimator, &feeCalc);
+ if (feeCalc.reason == FeeReason::FALLBACK && !m_allow_fallback_fee) {
+ // eventually allow a fallback fee
+ strFailReason = _("Fee estimation failed. Fallbackfee is disabled. Wait a few blocks or enable -fallbackfee.");
+ return false;
}
- if (coinControl && coinControl->fOverrideFeeRate)
- nFeeNeeded = coinControl->nFeeRate.GetFee(nBytes);
// If we made it here and we aren't even able to meet the relay fee on the next pass, give up
// because we must be at the maximum allowed fee.
@@ -2431,27 +2909,166 @@ bool CWallet::CreateTransaction(const vector<CRecipient>& vecSend, CWalletTx& wt
return false;
}
- if (nFeeRet >= nFeeNeeded)
+ if (nFeeRet >= nFeeNeeded) {
+ // Reduce fee to only the needed amount if possible. This
+ // prevents potential overpayment in fees if the coins
+ // selected to meet nFeeNeeded result in a transaction that
+ // requires less fee than the prior iteration.
+
+ // If we have no change and a big enough excess fee, then
+ // try to construct transaction again only without picking
+ // new inputs. We now know we only need the smaller fee
+ // (because of reduced tx size) and so we should add a
+ // change output. Only try this once.
+ if (nChangePosInOut == -1 && nSubtractFeeFromAmount == 0 && pick_new_inputs) {
+ unsigned int tx_size_with_change = nBytes + coin_selection_params.change_output_size + 2; // Add 2 as a buffer in case increasing # of outputs changes compact size
+ CAmount fee_needed_with_change = GetMinimumFee(*this, tx_size_with_change, coin_control, ::mempool, ::feeEstimator, nullptr);
+ CAmount minimum_value_for_change = GetDustThreshold(change_prototype_txout, discard_rate);
+ if (nFeeRet >= fee_needed_with_change + minimum_value_for_change) {
+ pick_new_inputs = false;
+ nFeeRet = fee_needed_with_change;
+ continue;
+ }
+ }
+
+ // If we have change output already, just increase it
+ if (nFeeRet > nFeeNeeded && nChangePosInOut != -1 && nSubtractFeeFromAmount == 0) {
+ CAmount extraFeePaid = nFeeRet - nFeeNeeded;
+ std::vector<CTxOut>::iterator change_position = txNew.vout.begin()+nChangePosInOut;
+ change_position->nValue += extraFeePaid;
+ nFeeRet -= extraFeePaid;
+ }
break; // Done, enough fee included.
+ }
+ else if (!pick_new_inputs) {
+ // This shouldn't happen, we should have had enough excess
+ // fee to pay for the new output and still meet nFeeNeeded
+ // Or we should have just subtracted fee from recipients and
+ // nFeeNeeded should not have changed
+ strFailReason = _("Transaction fee and change calculation failed");
+ return false;
+ }
+
+ // Try to reduce change to include necessary fee
+ if (nChangePosInOut != -1 && nSubtractFeeFromAmount == 0) {
+ CAmount additionalFeeNeeded = nFeeNeeded - nFeeRet;
+ std::vector<CTxOut>::iterator change_position = txNew.vout.begin()+nChangePosInOut;
+ // Only reduce change if remaining amount is still a large enough output.
+ if (change_position->nValue >= MIN_FINAL_CHANGE + additionalFeeNeeded) {
+ change_position->nValue -= additionalFeeNeeded;
+ nFeeRet += additionalFeeNeeded;
+ break; // Done, able to increase fee from change
+ }
+ }
+
+ // If subtracting fee from recipients, we now know what fee we
+ // need to subtract, we have no reason to reselect inputs
+ if (nSubtractFeeFromAmount > 0) {
+ pick_new_inputs = false;
+ }
// Include more fee and try again.
nFeeRet = nFeeNeeded;
+ coin_selection_params.use_bnb = false;
continue;
}
}
+
+ if (nChangePosInOut == -1) reservekey.ReturnKey(); // Return any reserved key if we don't have change
+
+ // Shuffle selected coins and fill in final vin
+ txNew.vin.clear();
+ std::vector<CInputCoin> selected_coins(setCoins.begin(), setCoins.end());
+ std::shuffle(selected_coins.begin(), selected_coins.end(), FastRandomContext());
+
+ // Note how the sequence number is set to non-maxint so that
+ // the nLockTime set above actually works.
+ //
+ // BIP125 defines opt-in RBF as any nSequence < maxint-1, so
+ // we use the highest possible value in that range (maxint-2)
+ // to avoid conflicting with other possible uses of nSequence,
+ // and in the spirit of "smallest possible change from prior
+ // behavior."
+ const uint32_t nSequence = coin_control.m_signal_bip125_rbf.get_value_or(m_signal_rbf) ? MAX_BIP125_RBF_SEQUENCE : (CTxIn::SEQUENCE_FINAL - 1);
+ for (const auto& coin : selected_coins) {
+ txNew.vin.push_back(CTxIn(coin.outpoint, CScript(), nSequence));
+ }
+
+ if (sign)
+ {
+ int nIn = 0;
+ for (const auto& coin : selected_coins)
+ {
+ const CScript& scriptPubKey = coin.txout.scriptPubKey;
+ SignatureData sigdata;
+
+ if (!ProduceSignature(*this, MutableTransactionSignatureCreator(&txNew, nIn, coin.txout.nValue, SIGHASH_ALL), scriptPubKey, sigdata))
+ {
+ strFailReason = _("Signing transaction failed");
+ return false;
+ } else {
+ UpdateInput(txNew.vin.at(nIn), sigdata);
+ }
+
+ nIn++;
+ }
+ }
+
+ // Return the constructed transaction data.
+ tx = MakeTransactionRef(std::move(txNew));
+
+ // Limit size
+ if (GetTransactionWeight(*tx) > MAX_STANDARD_TX_WEIGHT)
+ {
+ strFailReason = _("Transaction too large");
+ return false;
+ }
+ }
+
+ if (gArgs.GetBoolArg("-walletrejectlongchains", DEFAULT_WALLET_REJECT_LONG_CHAINS)) {
+ // Lastly, ensure this tx will pass the mempool's chain limits
+ LockPoints lp;
+ CTxMemPoolEntry entry(tx, 0, 0, 0, false, 0, lp);
+ CTxMemPool::setEntries setAncestors;
+ size_t nLimitAncestors = gArgs.GetArg("-limitancestorcount", DEFAULT_ANCESTOR_LIMIT);
+ size_t nLimitAncestorSize = gArgs.GetArg("-limitancestorsize", DEFAULT_ANCESTOR_SIZE_LIMIT)*1000;
+ size_t nLimitDescendants = gArgs.GetArg("-limitdescendantcount", DEFAULT_DESCENDANT_LIMIT);
+ size_t nLimitDescendantSize = gArgs.GetArg("-limitdescendantsize", DEFAULT_DESCENDANT_SIZE_LIMIT)*1000;
+ std::string errString;
+ LOCK(::mempool.cs);
+ if (!::mempool.CalculateMemPoolAncestors(entry, setAncestors, nLimitAncestors, nLimitAncestorSize, nLimitDescendants, nLimitDescendantSize, errString)) {
+ strFailReason = _("Transaction has too long of a mempool chain");
+ return false;
+ }
}
+ WalletLogPrintf("Fee Calculation: Fee:%d Bytes:%u Needed:%d Tgt:%d (requested %d) Reason:\"%s\" Decay %.5f: Estimation: (%g - %g) %.2f%% %.1f/(%.1f %d mem %.1f out) Fail: (%g - %g) %.2f%% %.1f/(%.1f %d mem %.1f out)\n",
+ nFeeRet, nBytes, nFeeNeeded, feeCalc.returnedTarget, feeCalc.desiredTarget, StringForFeeReason(feeCalc.reason), feeCalc.est.decay,
+ feeCalc.est.pass.start, feeCalc.est.pass.end,
+ 100 * feeCalc.est.pass.withinTarget / (feeCalc.est.pass.totalConfirmed + feeCalc.est.pass.inMempool + feeCalc.est.pass.leftMempool),
+ feeCalc.est.pass.withinTarget, feeCalc.est.pass.totalConfirmed, feeCalc.est.pass.inMempool, feeCalc.est.pass.leftMempool,
+ feeCalc.est.fail.start, feeCalc.est.fail.end,
+ 100 * feeCalc.est.fail.withinTarget / (feeCalc.est.fail.totalConfirmed + feeCalc.est.fail.inMempool + feeCalc.est.fail.leftMempool),
+ feeCalc.est.fail.withinTarget, feeCalc.est.fail.totalConfirmed, feeCalc.est.fail.inMempool, feeCalc.est.fail.leftMempool);
return true;
}
/**
* Call after CreateTransaction unless you want to abort
*/
-bool CWallet::CommitTransaction(CWalletTx& wtxNew, CReserveKey& reservekey)
+bool CWallet::CommitTransaction(CTransactionRef tx, mapValue_t mapValue, std::vector<std::pair<std::string, std::string>> orderForm, std::string fromAccount, CReserveKey& reservekey, CConnman* connman, CValidationState& state)
{
{
LOCK2(cs_main, cs_wallet);
- LogPrintf("CommitTransaction:\n%s", wtxNew.ToString());
+
+ CWalletTx wtxNew(this, std::move(tx));
+ wtxNew.mapValue = std::move(mapValue);
+ wtxNew.vOrderForm = std::move(orderForm);
+ wtxNew.strFromAccount = std::move(fromAccount);
+ wtxNew.fTimeReceivedIsTxTime = true;
+ wtxNew.fFromMe = true;
+
+ WalletLogPrintf("CommitTransaction:\n%s", wtxNew.tx->ToString()); /* Continued */
{
// Take key pair from key pool so it won't be used again
reservekey.KeepKey();
@@ -2461,150 +3078,145 @@ bool CWallet::CommitTransaction(CWalletTx& wtxNew, CReserveKey& reservekey)
AddToWallet(wtxNew);
// Notify that old coins are spent
- BOOST_FOREACH(const CTxIn& txin, wtxNew.vin)
+ for (const CTxIn& txin : wtxNew.tx->vin)
{
- CWalletTx &coin = mapWallet[txin.prevout.hash];
+ CWalletTx &coin = mapWallet.at(txin.prevout.hash);
coin.BindWallet(this);
NotifyTransactionChanged(this, coin.GetHash(), CT_UPDATED);
}
}
- // Track how many getdata requests our transaction gets
- mapRequestCount[wtxNew.GetHash()] = 0;
+ // Get the inserted-CWalletTx from mapWallet so that the
+ // fInMempool flag is cached properly
+ CWalletTx& wtx = mapWallet.at(wtxNew.GetHash());
if (fBroadcastTransactions)
{
// Broadcast
- if (!wtxNew.AcceptToMemoryPool(false, maxTxFee))
- {
- // This must not fail. The transaction has already been signed and recorded.
- LogPrintf("CommitTransaction(): Error: Transaction not valid\n");
- return false;
+ if (!wtx.AcceptToMemoryPool(maxTxFee, state)) {
+ WalletLogPrintf("CommitTransaction(): Transaction cannot be broadcast immediately, %s\n", FormatStateMessage(state));
+ // TODO: if we expect the failure to be long term or permanent, instead delete wtx from the wallet and return failure.
+ } else {
+ wtx.RelayWalletTransaction(connman);
}
- wtxNew.RelayWalletTransaction();
}
}
return true;
}
-bool CWallet::AddAccountingEntry(const CAccountingEntry& acentry, CWalletDB & pwalletdb)
-{
- if (!pwalletdb.WriteAccountingEntry_Backend(acentry))
- return false;
-
- laccentries.push_back(acentry);
- CAccountingEntry & entry = laccentries.back();
- wtxOrdered.insert(make_pair(entry.nOrderPos, TxPair((CWalletTx*)0, &entry)));
-
- return true;
+void CWallet::ListAccountCreditDebit(const std::string& strAccount, std::list<CAccountingEntry>& entries) {
+ WalletBatch batch(*database);
+ return batch.ListAccountCreditDebit(strAccount, entries);
}
-CAmount CWallet::GetRequiredFee(unsigned int nTxBytes)
+bool CWallet::AddAccountingEntry(const CAccountingEntry& acentry)
{
- return std::max(minTxFee.GetFee(nTxBytes), ::minRelayTxFee.GetFee(nTxBytes));
+ WalletBatch batch(*database);
+
+ return AddAccountingEntry(acentry, &batch);
}
-CAmount CWallet::GetMinimumFee(unsigned int nTxBytes, unsigned int nConfirmTarget, const CTxMemPool& pool)
+bool CWallet::AddAccountingEntry(const CAccountingEntry& acentry, WalletBatch *batch)
{
- // payTxFee is user-set "I want to pay this much"
- CAmount nFeeNeeded = payTxFee.GetFee(nTxBytes);
- // User didn't set: use -txconfirmtarget to estimate...
- if (nFeeNeeded == 0) {
- int estimateFoundTarget = nConfirmTarget;
- nFeeNeeded = pool.estimateSmartFee(nConfirmTarget, &estimateFoundTarget).GetFee(nTxBytes);
- // ... unless we don't have enough mempool data for estimatefee, then use fallbackFee
- if (nFeeNeeded == 0)
- nFeeNeeded = fallbackFee.GetFee(nTxBytes);
+ if (!batch->WriteAccountingEntry(++nAccountingEntryNumber, acentry)) {
+ return false;
}
- // prevent user from paying a fee below minRelayTxFee or minTxFee
- nFeeNeeded = std::max(nFeeNeeded, GetRequiredFee(nTxBytes));
- // But always obey the maximum
- if (nFeeNeeded > maxTxFee)
- nFeeNeeded = maxTxFee;
- return nFeeNeeded;
-}
-
+ laccentries.push_back(acentry);
+ CAccountingEntry & entry = laccentries.back();
+ wtxOrdered.insert(std::make_pair(entry.nOrderPos, TxPair(nullptr, &entry)));
+ return true;
+}
DBErrors CWallet::LoadWallet(bool& fFirstRunRet)
{
- if (!fFileBacked)
- return DB_LOAD_OK;
+ LOCK2(cs_main, cs_wallet);
+
fFirstRunRet = false;
- DBErrors nLoadWalletRet = CWalletDB(strWalletFile,"cr+").LoadWallet(this);
- if (nLoadWalletRet == DB_NEED_REWRITE)
+ DBErrors nLoadWalletRet = WalletBatch(*database,"cr+").LoadWallet(this);
+ if (nLoadWalletRet == DBErrors::NEED_REWRITE)
{
- if (CDB::Rewrite(strWalletFile, "\x04pool"))
+ if (database->Rewrite("\x04pool"))
{
- LOCK(cs_wallet);
- setKeyPool.clear();
+ setInternalKeyPool.clear();
+ setExternalKeyPool.clear();
+ m_pool_key_to_index.clear();
// Note: can't top-up keypool here, because wallet is locked.
// User will be prompted to unlock wallet the next operation
// that requires a new key.
}
}
- if (nLoadWalletRet != DB_LOAD_OK)
- return nLoadWalletRet;
- fFirstRunRet = !vchDefaultKey.IsValid();
+ {
+ LOCK(cs_KeyStore);
+ // This wallet is in its first run if all of these are empty
+ fFirstRunRet = mapKeys.empty() && mapCryptedKeys.empty() && mapWatchKeys.empty() && setWatchOnly.empty() && mapScripts.empty() && !IsWalletFlagSet(WALLET_FLAG_DISABLE_PRIVATE_KEYS);
+ }
- uiInterface.LoadWallet(this);
+ if (nLoadWalletRet != DBErrors::LOAD_OK)
+ return nLoadWalletRet;
- return DB_LOAD_OK;
+ return DBErrors::LOAD_OK;
}
-DBErrors CWallet::ZapSelectTx(vector<uint256>& vHashIn, vector<uint256>& vHashOut)
+DBErrors CWallet::ZapSelectTx(std::vector<uint256>& vHashIn, std::vector<uint256>& vHashOut)
{
- if (!fFileBacked)
- return DB_LOAD_OK;
- DBErrors nZapSelectTxRet = CWalletDB(strWalletFile,"cr+").ZapSelectTx(this, vHashIn, vHashOut);
- if (nZapSelectTxRet == DB_NEED_REWRITE)
+ AssertLockHeld(cs_wallet); // mapWallet
+ DBErrors nZapSelectTxRet = WalletBatch(*database,"cr+").ZapSelectTx(vHashIn, vHashOut);
+ for (uint256 hash : vHashOut) {
+ const auto& it = mapWallet.find(hash);
+ wtxOrdered.erase(it->second.m_it_wtxOrdered);
+ mapWallet.erase(it);
+ }
+
+ if (nZapSelectTxRet == DBErrors::NEED_REWRITE)
{
- if (CDB::Rewrite(strWalletFile, "\x04pool"))
+ if (database->Rewrite("\x04pool"))
{
- LOCK(cs_wallet);
- setKeyPool.clear();
+ setInternalKeyPool.clear();
+ setExternalKeyPool.clear();
+ m_pool_key_to_index.clear();
// Note: can't top-up keypool here, because wallet is locked.
// User will be prompted to unlock wallet the next operation
// that requires a new key.
}
}
- if (nZapSelectTxRet != DB_LOAD_OK)
+ if (nZapSelectTxRet != DBErrors::LOAD_OK)
return nZapSelectTxRet;
MarkDirty();
- return DB_LOAD_OK;
+ return DBErrors::LOAD_OK;
}
DBErrors CWallet::ZapWalletTx(std::vector<CWalletTx>& vWtx)
{
- if (!fFileBacked)
- return DB_LOAD_OK;
- DBErrors nZapWalletTxRet = CWalletDB(strWalletFile,"cr+").ZapWalletTx(this, vWtx);
- if (nZapWalletTxRet == DB_NEED_REWRITE)
+ DBErrors nZapWalletTxRet = WalletBatch(*database,"cr+").ZapWalletTx(vWtx);
+ if (nZapWalletTxRet == DBErrors::NEED_REWRITE)
{
- if (CDB::Rewrite(strWalletFile, "\x04pool"))
+ if (database->Rewrite("\x04pool"))
{
LOCK(cs_wallet);
- setKeyPool.clear();
+ setInternalKeyPool.clear();
+ setExternalKeyPool.clear();
+ m_pool_key_to_index.clear();
// Note: can't top-up keypool here, because wallet is locked.
// User will be prompted to unlock wallet the next operation
// that requires a new key.
}
}
- if (nZapWalletTxRet != DB_LOAD_OK)
+ if (nZapWalletTxRet != DBErrors::LOAD_OK)
return nZapWalletTxRet;
- return DB_LOAD_OK;
+ return DBErrors::LOAD_OK;
}
-bool CWallet::SetAddressBook(const CTxDestination& address, const string& strName, const string& strPurpose)
+bool CWallet::SetAddressBook(const CTxDestination& address, const std::string& strName, const std::string& strPurpose)
{
bool fUpdated = false;
{
@@ -2617,11 +3229,9 @@ bool CWallet::SetAddressBook(const CTxDestination& address, const string& strNam
}
NotifyAddressBookChanged(this, address, strName, ::IsMine(*this, address) != ISMINE_NO,
strPurpose, (fUpdated ? CT_UPDATED : CT_NEW) );
- if (!fFileBacked)
+ if (!strPurpose.empty() && !WalletBatch(*database).WritePurpose(EncodeDestination(address), strPurpose))
return false;
- if (!strPurpose.empty() && !CWalletDB(strWalletFile).WritePurpose(CBitcoinAddress(address).ToString(), strPurpose))
- return false;
- return CWalletDB(strWalletFile).WriteName(CBitcoinAddress(address).ToString(), strName);
+ return WalletBatch(*database).WriteName(EncodeDestination(address), strName);
}
bool CWallet::DelAddressBook(const CTxDestination& address)
@@ -2629,97 +3239,160 @@ bool CWallet::DelAddressBook(const CTxDestination& address)
{
LOCK(cs_wallet); // mapAddressBook
- if(fFileBacked)
+ // Delete destdata tuples associated with address
+ std::string strAddress = EncodeDestination(address);
+ for (const std::pair<const std::string, std::string> &item : mapAddressBook[address].destdata)
{
- // Delete destdata tuples associated with address
- std::string strAddress = CBitcoinAddress(address).ToString();
- BOOST_FOREACH(const PAIRTYPE(string, string) &item, mapAddressBook[address].destdata)
- {
- CWalletDB(strWalletFile).EraseDestData(strAddress, item.first);
- }
+ WalletBatch(*database).EraseDestData(strAddress, item.first);
}
mapAddressBook.erase(address);
}
NotifyAddressBookChanged(this, address, "", ::IsMine(*this, address) != ISMINE_NO, "", CT_DELETED);
- if (!fFileBacked)
- return false;
- CWalletDB(strWalletFile).ErasePurpose(CBitcoinAddress(address).ToString());
- return CWalletDB(strWalletFile).EraseName(CBitcoinAddress(address).ToString());
+ WalletBatch(*database).ErasePurpose(EncodeDestination(address));
+ return WalletBatch(*database).EraseName(EncodeDestination(address));
}
-bool CWallet::SetDefaultKey(const CPubKey &vchPubKey)
+const std::string& CWallet::GetLabelName(const CScript& scriptPubKey) const
{
- if (fFileBacked)
- {
- if (!CWalletDB(strWalletFile).WriteDefaultKey(vchPubKey))
- return false;
+ CTxDestination address;
+ if (ExtractDestination(scriptPubKey, address) && !scriptPubKey.IsUnspendable()) {
+ auto mi = mapAddressBook.find(address);
+ if (mi != mapAddressBook.end()) {
+ return mi->second.name;
+ }
}
- vchDefaultKey = vchPubKey;
- return true;
+ // A scriptPubKey that doesn't have an entry in the address book is
+ // associated with the default label ("").
+ const static std::string DEFAULT_LABEL_NAME;
+ return DEFAULT_LABEL_NAME;
}
/**
* Mark old keypool keys as used,
- * and generate all new keys
+ * and generate all new keys
*/
bool CWallet::NewKeyPool()
{
+ if (IsWalletFlagSet(WALLET_FLAG_DISABLE_PRIVATE_KEYS)) {
+ return false;
+ }
{
LOCK(cs_wallet);
- CWalletDB walletdb(strWalletFile);
- BOOST_FOREACH(int64_t nIndex, setKeyPool)
- walletdb.ErasePool(nIndex);
- setKeyPool.clear();
+ WalletBatch batch(*database);
- if (IsLocked())
- return false;
+ for (int64_t nIndex : setInternalKeyPool) {
+ batch.ErasePool(nIndex);
+ }
+ setInternalKeyPool.clear();
- int64_t nKeys = max(GetArg("-keypool", DEFAULT_KEYPOOL_SIZE), (int64_t)0);
- for (int i = 0; i < nKeys; i++)
- {
- int64_t nIndex = i+1;
- walletdb.WritePool(nIndex, CKeyPool(GenerateNewKey()));
- setKeyPool.insert(nIndex);
+ for (int64_t nIndex : setExternalKeyPool) {
+ batch.ErasePool(nIndex);
+ }
+ setExternalKeyPool.clear();
+
+ for (int64_t nIndex : set_pre_split_keypool) {
+ batch.ErasePool(nIndex);
+ }
+ set_pre_split_keypool.clear();
+
+ m_pool_key_to_index.clear();
+
+ if (!TopUpKeyPool()) {
+ return false;
}
- LogPrintf("CWallet::NewKeyPool wrote %d new keys\n", nKeys);
+ WalletLogPrintf("CWallet::NewKeyPool rewrote keypool\n");
}
return true;
}
+size_t CWallet::KeypoolCountExternalKeys()
+{
+ AssertLockHeld(cs_wallet); // setExternalKeyPool
+ return setExternalKeyPool.size() + set_pre_split_keypool.size();
+}
+
+void CWallet::LoadKeyPool(int64_t nIndex, const CKeyPool &keypool)
+{
+ AssertLockHeld(cs_wallet);
+ if (keypool.m_pre_split) {
+ set_pre_split_keypool.insert(nIndex);
+ } else if (keypool.fInternal) {
+ setInternalKeyPool.insert(nIndex);
+ } else {
+ setExternalKeyPool.insert(nIndex);
+ }
+ m_max_keypool_index = std::max(m_max_keypool_index, nIndex);
+ m_pool_key_to_index[keypool.vchPubKey.GetID()] = nIndex;
+
+ // If no metadata exists yet, create a default with the pool key's
+ // creation time. Note that this may be overwritten by actually
+ // stored metadata for that key later, which is fine.
+ CKeyID keyid = keypool.vchPubKey.GetID();
+ if (mapKeyMetadata.count(keyid) == 0)
+ mapKeyMetadata[keyid] = CKeyMetadata(keypool.nTime);
+}
+
bool CWallet::TopUpKeyPool(unsigned int kpSize)
{
+ if (IsWalletFlagSet(WALLET_FLAG_DISABLE_PRIVATE_KEYS)) {
+ return false;
+ }
{
LOCK(cs_wallet);
if (IsLocked())
return false;
- CWalletDB walletdb(strWalletFile);
-
// Top up key pool
unsigned int nTargetSize;
if (kpSize > 0)
nTargetSize = kpSize;
else
- nTargetSize = max(GetArg("-keypool", DEFAULT_KEYPOOL_SIZE), (int64_t) 0);
+ nTargetSize = std::max(gArgs.GetArg("-keypool", DEFAULT_KEYPOOL_SIZE), (int64_t) 0);
+
+ // count amount of available keys (internal, external)
+ // make sure the keypool of external and internal keys fits the user selected target (-keypool)
+ int64_t missingExternal = std::max(std::max((int64_t) nTargetSize, (int64_t) 1) - (int64_t)setExternalKeyPool.size(), (int64_t) 0);
+ int64_t missingInternal = std::max(std::max((int64_t) nTargetSize, (int64_t) 1) - (int64_t)setInternalKeyPool.size(), (int64_t) 0);
- while (setKeyPool.size() < (nTargetSize + 1))
+ if (!IsHDEnabled() || !CanSupportFeature(FEATURE_HD_SPLIT))
{
- int64_t nEnd = 1;
- if (!setKeyPool.empty())
- nEnd = *(--setKeyPool.end()) + 1;
- if (!walletdb.WritePool(nEnd, CKeyPool(GenerateNewKey())))
- throw runtime_error(std::string(__func__) + ": writing generated key failed");
- setKeyPool.insert(nEnd);
- LogPrintf("keypool added key %d, size=%u\n", nEnd, setKeyPool.size());
+ // don't create extra internal keys
+ missingInternal = 0;
+ }
+ bool internal = false;
+ WalletBatch batch(*database);
+ for (int64_t i = missingInternal + missingExternal; i--;)
+ {
+ if (i < missingInternal) {
+ internal = true;
+ }
+
+ assert(m_max_keypool_index < std::numeric_limits<int64_t>::max()); // How in the hell did you use so many keys?
+ int64_t index = ++m_max_keypool_index;
+
+ CPubKey pubkey(GenerateNewKey(batch, internal));
+ if (!batch.WritePool(index, CKeyPool(pubkey, internal))) {
+ throw std::runtime_error(std::string(__func__) + ": writing generated key failed");
+ }
+
+ if (internal) {
+ setInternalKeyPool.insert(index);
+ } else {
+ setExternalKeyPool.insert(index);
+ }
+ m_pool_key_to_index[pubkey.GetID()] = index;
+ }
+ if (missingInternal + missingExternal > 0) {
+ WalletLogPrintf("keypool added %d keys (%d internal), size=%u (%u internal)\n", missingInternal + missingExternal, missingInternal, setInternalKeyPool.size() + setExternalKeyPool.size() + set_pre_split_keypool.size(), setInternalKeyPool.size());
}
}
return true;
}
-void CWallet::ReserveKeyFromKeyPool(int64_t& nIndex, CKeyPool& keypool)
+bool CWallet::ReserveKeyFromKeyPool(int64_t& nIndex, CKeyPool& keypool, bool fRequestedInternal)
{
nIndex = -1;
keypool.vchPubKey = CPubKey();
@@ -2729,55 +3402,79 @@ void CWallet::ReserveKeyFromKeyPool(int64_t& nIndex, CKeyPool& keypool)
if (!IsLocked())
TopUpKeyPool();
+ bool fReturningInternal = IsHDEnabled() && CanSupportFeature(FEATURE_HD_SPLIT) && fRequestedInternal;
+ bool use_split_keypool = set_pre_split_keypool.empty();
+ std::set<int64_t>& setKeyPool = use_split_keypool ? (fReturningInternal ? setInternalKeyPool : setExternalKeyPool) : set_pre_split_keypool;
+
// Get the oldest key
- if(setKeyPool.empty())
- return;
+ if (setKeyPool.empty()) {
+ return false;
+ }
+
+ WalletBatch batch(*database);
- CWalletDB walletdb(strWalletFile);
+ auto it = setKeyPool.begin();
+ nIndex = *it;
+ setKeyPool.erase(it);
+ if (!batch.ReadPool(nIndex, keypool)) {
+ throw std::runtime_error(std::string(__func__) + ": read failed");
+ }
+ if (!HaveKey(keypool.vchPubKey.GetID())) {
+ throw std::runtime_error(std::string(__func__) + ": unknown key in key pool");
+ }
+ // If the key was pre-split keypool, we don't care about what type it is
+ if (use_split_keypool && keypool.fInternal != fReturningInternal) {
+ throw std::runtime_error(std::string(__func__) + ": keypool entry misclassified");
+ }
+ if (!keypool.vchPubKey.IsValid()) {
+ throw std::runtime_error(std::string(__func__) + ": keypool entry invalid");
+ }
- nIndex = *(setKeyPool.begin());
- setKeyPool.erase(setKeyPool.begin());
- if (!walletdb.ReadPool(nIndex, keypool))
- throw runtime_error(std::string(__func__) + ": read failed");
- if (!HaveKey(keypool.vchPubKey.GetID()))
- throw runtime_error(std::string(__func__) + ": unknown key in key pool");
- assert(keypool.vchPubKey.IsValid());
- LogPrintf("keypool reserve %d\n", nIndex);
+ m_pool_key_to_index.erase(keypool.vchPubKey.GetID());
+ WalletLogPrintf("keypool reserve %d\n", nIndex);
}
+ return true;
}
void CWallet::KeepKey(int64_t nIndex)
{
// Remove from key pool
- if (fFileBacked)
- {
- CWalletDB walletdb(strWalletFile);
- walletdb.ErasePool(nIndex);
- }
- LogPrintf("keypool keep %d\n", nIndex);
+ WalletBatch batch(*database);
+ batch.ErasePool(nIndex);
+ WalletLogPrintf("keypool keep %d\n", nIndex);
}
-void CWallet::ReturnKey(int64_t nIndex)
+void CWallet::ReturnKey(int64_t nIndex, bool fInternal, const CPubKey& pubkey)
{
// Return to key pool
{
LOCK(cs_wallet);
- setKeyPool.insert(nIndex);
+ if (fInternal) {
+ setInternalKeyPool.insert(nIndex);
+ } else if (!set_pre_split_keypool.empty()) {
+ set_pre_split_keypool.insert(nIndex);
+ } else {
+ setExternalKeyPool.insert(nIndex);
+ }
+ m_pool_key_to_index[pubkey.GetID()] = nIndex;
}
- LogPrintf("keypool return %d\n", nIndex);
+ WalletLogPrintf("keypool return %d\n", nIndex);
}
-bool CWallet::GetKeyFromPool(CPubKey& result)
+bool CWallet::GetKeyFromPool(CPubKey& result, bool internal)
{
- int64_t nIndex = 0;
+ if (IsWalletFlagSet(WALLET_FLAG_DISABLE_PRIVATE_KEYS)) {
+ return false;
+ }
+
CKeyPool keypool;
{
LOCK(cs_wallet);
- ReserveKeyFromKeyPool(nIndex, keypool);
- if (nIndex == -1)
- {
+ int64_t nIndex;
+ if (!ReserveKeyFromKeyPool(nIndex, keypool, internal)) {
if (IsLocked()) return false;
- result = GenerateNewKey();
+ WalletBatch batch(*database);
+ result = GenerateNewKey(batch, internal);
return true;
}
KeepKey(nIndex);
@@ -2786,35 +3483,49 @@ bool CWallet::GetKeyFromPool(CPubKey& result)
return true;
}
-int64_t CWallet::GetOldestKeyPoolTime()
-{
- LOCK(cs_wallet);
-
- // if the keypool is empty, return <NOW>
- if (setKeyPool.empty())
+static int64_t GetOldestKeyTimeInPool(const std::set<int64_t>& setKeyPool, WalletBatch& batch) {
+ if (setKeyPool.empty()) {
return GetTime();
+ }
- // load oldest key from keypool, get time and return
CKeyPool keypool;
- CWalletDB walletdb(strWalletFile);
int64_t nIndex = *(setKeyPool.begin());
- if (!walletdb.ReadPool(nIndex, keypool))
- throw runtime_error(std::string(__func__) + ": read oldest key in keypool failed");
+ if (!batch.ReadPool(nIndex, keypool)) {
+ throw std::runtime_error(std::string(__func__) + ": read oldest key in keypool failed");
+ }
assert(keypool.vchPubKey.IsValid());
return keypool.nTime;
}
+int64_t CWallet::GetOldestKeyPoolTime()
+{
+ LOCK(cs_wallet);
+
+ WalletBatch batch(*database);
+
+ // load oldest key from keypool, get time and return
+ int64_t oldestKey = GetOldestKeyTimeInPool(setExternalKeyPool, batch);
+ if (IsHDEnabled() && CanSupportFeature(FEATURE_HD_SPLIT)) {
+ oldestKey = std::max(GetOldestKeyTimeInPool(setInternalKeyPool, batch), oldestKey);
+ if (!set_pre_split_keypool.empty()) {
+ oldestKey = std::max(GetOldestKeyTimeInPool(set_pre_split_keypool, batch), oldestKey);
+ }
+ }
+
+ return oldestKey;
+}
+
std::map<CTxDestination, CAmount> CWallet::GetAddressBalances()
{
- map<CTxDestination, CAmount> balances;
+ std::map<CTxDestination, CAmount> balances;
{
LOCK(cs_wallet);
- BOOST_FOREACH(PAIRTYPE(uint256, CWalletTx) walletEntry, mapWallet)
+ for (const auto& walletEntry : mapWallet)
{
- CWalletTx *pcoin = &walletEntry.second;
+ const CWalletTx *pcoin = &walletEntry.second;
- if (!CheckFinalTx(*pcoin) || !pcoin->IsTrusted())
+ if (!pcoin->IsTrusted())
continue;
if (pcoin->IsCoinBase() && pcoin->GetBlocksToMaturity() > 0)
@@ -2824,15 +3535,15 @@ std::map<CTxDestination, CAmount> CWallet::GetAddressBalances()
if (nDepth < (pcoin->IsFromMe(ISMINE_ALL) ? 0 : 1))
continue;
- for (unsigned int i = 0; i < pcoin->vout.size(); i++)
+ for (unsigned int i = 0; i < pcoin->tx->vout.size(); i++)
{
CTxDestination addr;
- if (!IsMine(pcoin->vout[i]))
+ if (!IsMine(pcoin->tx->vout[i]))
continue;
- if(!ExtractDestination(pcoin->vout[i].scriptPubKey, addr))
+ if(!ExtractDestination(pcoin->tx->vout[i].scriptPubKey, addr))
continue;
- CAmount n = IsSpent(walletEntry.first, i) ? 0 : pcoin->vout[i].nValue;
+ CAmount n = IsSpent(walletEntry.first, i) ? 0 : pcoin->tx->vout[i].nValue;
if (!balances.count(addr))
balances[addr] = 0;
@@ -2844,26 +3555,26 @@ std::map<CTxDestination, CAmount> CWallet::GetAddressBalances()
return balances;
}
-set< set<CTxDestination> > CWallet::GetAddressGroupings()
+std::set< std::set<CTxDestination> > CWallet::GetAddressGroupings()
{
AssertLockHeld(cs_wallet); // mapWallet
- set< set<CTxDestination> > groupings;
- set<CTxDestination> grouping;
+ std::set< std::set<CTxDestination> > groupings;
+ std::set<CTxDestination> grouping;
- BOOST_FOREACH(PAIRTYPE(uint256, CWalletTx) walletEntry, mapWallet)
+ for (const auto& walletEntry : mapWallet)
{
- CWalletTx *pcoin = &walletEntry.second;
+ const CWalletTx *pcoin = &walletEntry.second;
- if (pcoin->vin.size() > 0)
+ if (pcoin->tx->vin.size() > 0)
{
bool any_mine = false;
// group all input addresses with each other
- BOOST_FOREACH(CTxIn txin, pcoin->vin)
+ for (CTxIn txin : pcoin->tx->vin)
{
CTxDestination address;
if(!IsMine(txin)) /* If this input isn't mine, ignore it */
continue;
- if(!ExtractDestination(mapWallet[txin.prevout.hash].vout[txin.prevout.n].scriptPubKey, address))
+ if(!ExtractDestination(mapWallet.at(txin.prevout.hash).tx->vout[txin.prevout.n].scriptPubKey, address))
continue;
grouping.insert(address);
any_mine = true;
@@ -2872,7 +3583,7 @@ set< set<CTxDestination> > CWallet::GetAddressGroupings()
// group change with input addresses
if (any_mine)
{
- BOOST_FOREACH(CTxOut txout, pcoin->vout)
+ for (CTxOut txout : pcoin->tx->vout)
if (IsChange(txout))
{
CTxDestination txoutAddr;
@@ -2889,11 +3600,11 @@ set< set<CTxDestination> > CWallet::GetAddressGroupings()
}
// group lone addrs by themselves
- for (unsigned int i = 0; i < pcoin->vout.size(); i++)
- if (IsMine(pcoin->vout[i]))
+ for (const auto& txout : pcoin->tx->vout)
+ if (IsMine(txout))
{
CTxDestination address;
- if(!ExtractDestination(pcoin->vout[i].scriptPubKey, address))
+ if(!ExtractDestination(txout.scriptPubKey, address))
continue;
grouping.insert(address);
groupings.insert(grouping);
@@ -2901,20 +3612,20 @@ set< set<CTxDestination> > CWallet::GetAddressGroupings()
}
}
- set< set<CTxDestination>* > uniqueGroupings; // a set of pointers to groups of addresses
- map< CTxDestination, set<CTxDestination>* > setmap; // map addresses to the unique group containing it
- BOOST_FOREACH(set<CTxDestination> grouping, groupings)
+ std::set< std::set<CTxDestination>* > uniqueGroupings; // a set of pointers to groups of addresses
+ std::map< CTxDestination, std::set<CTxDestination>* > setmap; // map addresses to the unique group containing it
+ for (std::set<CTxDestination> _grouping : groupings)
{
// make a set of all the groups hit by this new group
- set< set<CTxDestination>* > hits;
- map< CTxDestination, set<CTxDestination>* >::iterator it;
- BOOST_FOREACH(CTxDestination address, grouping)
+ std::set< std::set<CTxDestination>* > hits;
+ std::map< CTxDestination, std::set<CTxDestination>* >::iterator it;
+ for (CTxDestination address : _grouping)
if ((it = setmap.find(address)) != setmap.end())
hits.insert((*it).second);
// merge all hit groups into a new single group and delete old groups
- set<CTxDestination>* merged = new set<CTxDestination>(grouping);
- BOOST_FOREACH(set<CTxDestination>* hit, hits)
+ std::set<CTxDestination>* merged = new std::set<CTxDestination>(_grouping);
+ for (std::set<CTxDestination>* hit : hits)
{
merged->insert(hit->begin(), hit->end());
uniqueGroupings.erase(hit);
@@ -2923,12 +3634,12 @@ set< set<CTxDestination> > CWallet::GetAddressGroupings()
uniqueGroupings.insert(merged);
// update setmap
- BOOST_FOREACH(CTxDestination element, *merged)
+ for (CTxDestination element : *merged)
setmap[element] = merged;
}
- set< set<CTxDestination> > ret;
- BOOST_FOREACH(set<CTxDestination>* uniqueGrouping, uniqueGroupings)
+ std::set< std::set<CTxDestination> > ret;
+ for (std::set<CTxDestination>* uniqueGrouping : uniqueGroupings)
{
ret.insert(*uniqueGrouping);
delete uniqueGrouping;
@@ -2937,62 +3648,36 @@ set< set<CTxDestination> > CWallet::GetAddressGroupings()
return ret;
}
-CAmount CWallet::GetAccountBalance(const std::string& strAccount, int nMinDepth, const isminefilter& filter)
-{
- CWalletDB walletdb(strWalletFile);
- return GetAccountBalance(walletdb, strAccount, nMinDepth, filter);
-}
-
-CAmount CWallet::GetAccountBalance(CWalletDB& walletdb, const std::string& strAccount, int nMinDepth, const isminefilter& filter)
-{
- CAmount nBalance = 0;
-
- // Tally wallet transactions
- for (map<uint256, CWalletTx>::iterator it = mapWallet.begin(); it != mapWallet.end(); ++it)
- {
- const CWalletTx& wtx = (*it).second;
- if (!CheckFinalTx(wtx) || wtx.GetBlocksToMaturity() > 0 || wtx.GetDepthInMainChain() < 0)
- continue;
-
- CAmount nReceived, nSent, nFee;
- wtx.GetAccountAmounts(strAccount, nReceived, nSent, nFee, filter);
-
- if (nReceived != 0 && wtx.GetDepthInMainChain() >= nMinDepth)
- nBalance += nReceived;
- nBalance -= nSent + nFee;
- }
-
- // Tally internal accounting entries
- nBalance += walletdb.GetAccountCreditDebit(strAccount);
-
- return nBalance;
-}
-
-std::set<CTxDestination> CWallet::GetAccountAddresses(const std::string& strAccount) const
+std::set<CTxDestination> CWallet::GetLabelAddresses(const std::string& label) const
{
LOCK(cs_wallet);
- set<CTxDestination> result;
- BOOST_FOREACH(const PAIRTYPE(CTxDestination, CAddressBookData)& item, mapAddressBook)
+ std::set<CTxDestination> result;
+ for (const std::pair<const CTxDestination, CAddressBookData>& item : mapAddressBook)
{
const CTxDestination& address = item.first;
- const string& strName = item.second.name;
- if (strName == strAccount)
+ const std::string& strName = item.second.name;
+ if (strName == label)
result.insert(address);
}
return result;
}
-bool CReserveKey::GetReservedKey(CPubKey& pubkey)
+void CWallet::DeleteLabel(const std::string& label)
+{
+ WalletBatch batch(*database);
+ batch.EraseAccount(label);
+}
+
+bool CReserveKey::GetReservedKey(CPubKey& pubkey, bool internal)
{
if (nIndex == -1)
{
CKeyPool keypool;
- pwallet->ReserveKeyFromKeyPool(nIndex, keypool);
- if (nIndex != -1)
- vchPubKey = keypool.vchPubKey;
- else {
+ if (!pwallet->ReserveKeyFromKeyPool(nIndex, keypool, internal)) {
return false;
}
+ vchPubKey = keypool.vchPubKey;
+ fInternal = keypool.fInternal;
}
assert(vchPubKey.IsValid());
pubkey = vchPubKey;
@@ -3009,46 +3694,40 @@ void CReserveKey::KeepKey()
void CReserveKey::ReturnKey()
{
- if (nIndex != -1)
- pwallet->ReturnKey(nIndex);
+ if (nIndex != -1) {
+ pwallet->ReturnKey(nIndex, fInternal, vchPubKey);
+ }
nIndex = -1;
vchPubKey = CPubKey();
}
-void CWallet::GetAllReserveKeys(set<CKeyID>& setAddress) const
+void CWallet::MarkReserveKeysAsUsed(int64_t keypool_id)
{
- setAddress.clear();
+ AssertLockHeld(cs_wallet);
+ bool internal = setInternalKeyPool.count(keypool_id);
+ if (!internal) assert(setExternalKeyPool.count(keypool_id) || set_pre_split_keypool.count(keypool_id));
+ std::set<int64_t> *setKeyPool = internal ? &setInternalKeyPool : (set_pre_split_keypool.empty() ? &setExternalKeyPool : &set_pre_split_keypool);
+ auto it = setKeyPool->begin();
- CWalletDB walletdb(strWalletFile);
+ WalletBatch batch(*database);
+ while (it != std::end(*setKeyPool)) {
+ const int64_t& index = *(it);
+ if (index > keypool_id) break; // set*KeyPool is ordered
- LOCK2(cs_main, cs_wallet);
- BOOST_FOREACH(const int64_t& id, setKeyPool)
- {
CKeyPool keypool;
- if (!walletdb.ReadPool(id, keypool))
- throw runtime_error(std::string(__func__) + ": read failed");
- assert(keypool.vchPubKey.IsValid());
- CKeyID keyID = keypool.vchPubKey.GetID();
- if (!HaveKey(keyID))
- throw runtime_error(std::string(__func__) + ": unknown key in key pool");
- setAddress.insert(keyID);
- }
-}
-
-void CWallet::UpdatedTransaction(const uint256 &hashTx)
-{
- {
- LOCK(cs_wallet);
- // Only notify UI if this transaction is in this wallet
- map<uint256, CWalletTx>::const_iterator mi = mapWallet.find(hashTx);
- if (mi != mapWallet.end())
- NotifyTransactionChanged(this, hashTx, CT_UPDATED);
+ if (batch.ReadPool(index, keypool)) { //TODO: This should be unnecessary
+ m_pool_key_to_index.erase(keypool.vchPubKey.GetID());
+ }
+ LearnAllRelatedScripts(keypool.vchPubKey);
+ batch.ErasePool(index);
+ WalletLogPrintf("keypool index %d removed\n", index);
+ it = setKeyPool->erase(it);
}
}
-void CWallet::GetScriptForMining(boost::shared_ptr<CReserveScript> &script)
+void CWallet::GetScriptForMining(std::shared_ptr<CReserveScript> &script)
{
- boost::shared_ptr<CReserveKey> rKey(new CReserveKey(this));
+ std::shared_ptr<CReserveKey> rKey = std::make_shared<CReserveKey>(this);
CPubKey pubkey;
if (!rKey->GetReservedKey(pubkey))
return;
@@ -3083,7 +3762,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();
@@ -3095,57 +3774,24 @@ void CWallet::ListLockedCoins(std::vector<COutPoint>& vOutpts)
/** @} */ // end of Actions
-class CAffectedKeysVisitor : public boost::static_visitor<void> {
-private:
- const CKeyStore &keystore;
- std::vector<CKeyID> &vKeys;
-
-public:
- CAffectedKeysVisitor(const CKeyStore &keystoreIn, std::vector<CKeyID> &vKeysIn) : keystore(keystoreIn), vKeys(vKeysIn) {}
-
- void Process(const CScript &script) {
- txnouttype type;
- std::vector<CTxDestination> vDest;
- int nRequired;
- if (ExtractDestinations(script, type, vDest, nRequired)) {
- BOOST_FOREACH(const CTxDestination &dest, vDest)
- boost::apply_visitor(*this, dest);
- }
- }
-
- void operator()(const CKeyID &keyId) {
- if (keystore.HaveKey(keyId))
- vKeys.push_back(keyId);
- }
-
- void operator()(const CScriptID &scriptId) {
- CScript script;
- if (keystore.GetCScript(scriptId, script))
- Process(script);
- }
-
- void operator()(const CNoDestination &none) {}
-};
-
-void CWallet::GetKeyBirthTimes(std::map<CKeyID, int64_t> &mapKeyBirth) const {
+void CWallet::GetKeyBirthTimes(std::map<CTxDestination, int64_t> &mapKeyBirth) const {
AssertLockHeld(cs_wallet); // mapKeyMetadata
mapKeyBirth.clear();
// get birth times for keys with metadata
- for (std::map<CKeyID, CKeyMetadata>::const_iterator it = mapKeyMetadata.begin(); it != mapKeyMetadata.end(); it++)
- if (it->second.nCreateTime)
- mapKeyBirth[it->first] = it->second.nCreateTime;
+ for (const auto& entry : mapKeyMetadata) {
+ if (entry.second.nCreateTime) {
+ mapKeyBirth[entry.first] = entry.second.nCreateTime;
+ }
+ }
// map in which we'll infer heights of other keys
CBlockIndex *pindexMax = chainActive[std::max(0, chainActive.Height() - 144)]; // the tip can be reorganized; use a 144-block safety margin
std::map<CKeyID, CBlockIndex*> mapKeyFirstBlock;
- std::set<CKeyID> setKeys;
- GetKeys(setKeys);
- BOOST_FOREACH(const CKeyID &keyid, setKeys) {
+ for (const CKeyID &keyid : GetKeys()) {
if (mapKeyBirth.count(keyid) == 0)
mapKeyFirstBlock[keyid] = pindexMax;
}
- setKeys.clear();
// if there are no such keys, we're done
if (mapKeyFirstBlock.empty())
@@ -3153,21 +3799,21 @@ void CWallet::GetKeyBirthTimes(std::map<CKeyID, int64_t> &mapKeyBirth) const {
// find first block that affects those keys, if there are any left
std::vector<CKeyID> vAffected;
- for (std::map<uint256, CWalletTx>::const_iterator it = mapWallet.begin(); it != mapWallet.end(); it++) {
+ for (const auto& entry : mapWallet) {
// iterate over all wallet transactions...
- const CWalletTx &wtx = (*it).second;
- BlockMap::const_iterator blit = mapBlockIndex.find(wtx.hashBlock);
- if (blit != mapBlockIndex.end() && chainActive.Contains(blit->second)) {
+ const CWalletTx &wtx = entry.second;
+ CBlockIndex* pindex = LookupBlockIndex(wtx.hashBlock);
+ if (pindex && chainActive.Contains(pindex)) {
// ... which are already in a block
- int nHeight = blit->second->nHeight;
- BOOST_FOREACH(const CTxOut &txout, wtx.vout) {
+ int nHeight = pindex->nHeight;
+ for (const CTxOut &txout : wtx.tx->vout) {
// iterate over all their outputs
CAffectedKeysVisitor(*this, vAffected).Process(txout.scriptPubKey);
- BOOST_FOREACH(const CKeyID &keyid, vAffected) {
+ for (const CKeyID &keyid : vAffected) {
// ... and all their affected keys
std::map<CKeyID, CBlockIndex*>::iterator rit = mapKeyFirstBlock.find(keyid);
if (rit != mapKeyFirstBlock.end() && nHeight < rit->second->nHeight)
- rit->second = blit->second;
+ rit->second = pindex;
}
vAffected.clear();
}
@@ -3175,8 +3821,73 @@ void CWallet::GetKeyBirthTimes(std::map<CKeyID, int64_t> &mapKeyBirth) const {
}
// Extract block timestamps for those keys
- for (std::map<CKeyID, CBlockIndex*>::const_iterator it = mapKeyFirstBlock.begin(); it != mapKeyFirstBlock.end(); it++)
- mapKeyBirth[it->first] = it->second->GetBlockTime() - 7200; // block times can be 2h off
+ for (const auto& entry : mapKeyFirstBlock)
+ mapKeyBirth[entry.first] = entry.second->GetBlockTime() - TIMESTAMP_WINDOW; // block times can be 2h off
+}
+
+/**
+ * Compute smart timestamp for a transaction being added to the wallet.
+ *
+ * Logic:
+ * - If sending a transaction, assign its timestamp to the current time.
+ * - If receiving a transaction outside a block, assign its timestamp to the
+ * current time.
+ * - If receiving a block with a future timestamp, assign all its (not already
+ * known) transactions' timestamps to the current time.
+ * - If receiving a block with a past timestamp, before the most recent known
+ * transaction (that we care about), assign all its (not already known)
+ * transactions' timestamps to the same timestamp as that most-recent-known
+ * transaction.
+ * - If receiving a block with a past timestamp, but after the most recent known
+ * transaction, assign all its (not already known) transactions' timestamps to
+ * the block time.
+ *
+ * For more information see CWalletTx::nTimeSmart,
+ * https://bitcointalk.org/?topic=54527, or
+ * https://github.com/bitcoin/bitcoin/pull/1393.
+ */
+unsigned int CWallet::ComputeTimeSmart(const CWalletTx& wtx) const
+{
+ unsigned int nTimeSmart = wtx.nTimeReceived;
+ if (!wtx.hashUnset()) {
+ if (const CBlockIndex* pindex = LookupBlockIndex(wtx.hashBlock)) {
+ int64_t latestNow = wtx.nTimeReceived;
+ int64_t latestEntry = 0;
+
+ // Tolerate times up to the last timestamp in the wallet not more than 5 minutes into the future
+ int64_t latestTolerated = latestNow + 300;
+ const TxItems& txOrdered = wtxOrdered;
+ for (auto it = txOrdered.rbegin(); it != txOrdered.rend(); ++it) {
+ CWalletTx* const pwtx = it->second.first;
+ if (pwtx == &wtx) {
+ continue;
+ }
+ CAccountingEntry* const pacentry = it->second.second;
+ int64_t nSmartTime;
+ if (pwtx) {
+ nSmartTime = pwtx->nTimeSmart;
+ if (!nSmartTime) {
+ nSmartTime = pwtx->nTimeReceived;
+ }
+ } else {
+ nSmartTime = pacentry->nTime;
+ }
+ if (nSmartTime <= latestTolerated) {
+ latestEntry = nSmartTime;
+ if (nSmartTime > latestNow) {
+ latestNow = nSmartTime;
+ }
+ break;
+ }
+ }
+
+ int64_t blocktime = pindex->GetBlockTime();
+ nTimeSmart = std::max(latestEntry, std::min(blocktime, latestNow));
+ } else {
+ WalletLogPrintf("%s: found %s in block %s not in index\n", __func__, wtx.GetHash().ToString(), wtx.hashBlock.ToString());
+ }
+ }
+ return nTimeSmart;
}
bool CWallet::AddDestData(const CTxDestination &dest, const std::string &key, const std::string &value)
@@ -3185,24 +3896,19 @@ bool CWallet::AddDestData(const CTxDestination &dest, const std::string &key, co
return false;
mapAddressBook[dest].destdata.insert(std::make_pair(key, value));
- if (!fFileBacked)
- return true;
- return CWalletDB(strWalletFile).WriteDestData(CBitcoinAddress(dest).ToString(), key, value);
+ return WalletBatch(*database).WriteDestData(EncodeDestination(dest), key, value);
}
bool CWallet::EraseDestData(const CTxDestination &dest, const std::string &key)
{
if (!mapAddressBook[dest].destdata.erase(key))
return false;
- if (!fFileBacked)
- return true;
- return CWalletDB(strWalletFile).EraseDestData(CBitcoinAddress(dest).ToString(), key);
+ return WalletBatch(*database).EraseDestData(EncodeDestination(dest), key);
}
-bool CWallet::LoadDestData(const CTxDestination &dest, const std::string &key, const std::string &value)
+void CWallet::LoadDestData(const CTxDestination &dest, const std::string &key, const std::string &value)
{
mapAddressBook[dest].destdata.insert(std::make_pair(key, value));
- return true;
}
bool CWallet::GetDestData(const CTxDestination &dest, const std::string &key, std::string *value) const
@@ -3221,154 +3927,341 @@ bool CWallet::GetDestData(const CTxDestination &dest, const std::string &key, st
return false;
}
-std::string CWallet::GetWalletHelpString(bool showDebug)
+std::vector<std::string> CWallet::GetDestValues(const std::string& prefix) const
{
- std::string strUsage = HelpMessageGroup(_("Wallet options:"));
- strUsage += HelpMessageOpt("-disablewallet", _("Do not load the wallet and disable wallet RPC calls"));
- strUsage += HelpMessageOpt("-keypool=<n>", strprintf(_("Set key pool size to <n> (default: %u)"), DEFAULT_KEYPOOL_SIZE));
- strUsage += HelpMessageOpt("-fallbackfee=<amt>", strprintf(_("A fee rate (in %s/kB) that will be used when fee estimation has insufficient data (default: %s)"),
- CURRENCY_UNIT, FormatMoney(DEFAULT_FALLBACK_FEE)));
- strUsage += HelpMessageOpt("-mintxfee=<amt>", strprintf(_("Fees (in %s/kB) smaller than this are considered zero fee for transaction creation (default: %s)"),
- CURRENCY_UNIT, FormatMoney(DEFAULT_TRANSACTION_MINFEE)));
- strUsage += HelpMessageOpt("-paytxfee=<amt>", strprintf(_("Fee (in %s/kB) to add to transactions you send (default: %s)"),
- CURRENCY_UNIT, FormatMoney(payTxFee.GetFeePerK())));
- strUsage += HelpMessageOpt("-rescan", _("Rescan the block chain for missing wallet transactions on startup"));
- strUsage += HelpMessageOpt("-salvagewallet", _("Attempt to recover private keys from a corrupt wallet on startup"));
- if (showDebug)
- strUsage += HelpMessageOpt("-sendfreetransactions", strprintf(_("Send transactions as zero-fee transactions if possible (default: %u)"), DEFAULT_SEND_FREE_TRANSACTIONS));
- strUsage += HelpMessageOpt("-spendzeroconfchange", strprintf(_("Spend unconfirmed change when sending transactions (default: %u)"), DEFAULT_SPEND_ZEROCONF_CHANGE));
- strUsage += HelpMessageOpt("-txconfirmtarget=<n>", strprintf(_("If paytxfee is not set, include enough fee so transactions begin confirmation on average within n blocks (default: %u)"), DEFAULT_TX_CONFIRM_TARGET));
- strUsage += HelpMessageOpt("-usehd", _("Use hierarchical deterministic key generation (HD) after BIP32. Only has effect during wallet creation/first start") + " " + strprintf(_("(default: %u)"), DEFAULT_USE_HD_WALLET));
- strUsage += HelpMessageOpt("-upgradewallet", _("Upgrade wallet to latest format on startup"));
- strUsage += HelpMessageOpt("-wallet=<file>", _("Specify wallet file (within data directory)") + " " + strprintf(_("(default: %s)"), DEFAULT_WALLET_DAT));
- strUsage += HelpMessageOpt("-walletbroadcast", _("Make the wallet broadcast transactions") + " " + strprintf(_("(default: %u)"), DEFAULT_WALLETBROADCAST));
- strUsage += HelpMessageOpt("-walletnotify=<cmd>", _("Execute command when a wallet transaction changes (%s in cmd is replaced by TxID)"));
- strUsage += HelpMessageOpt("-zapwallettxes=<mode>", _("Delete all wallet transactions and only recover those parts of the blockchain through -rescan on startup") +
- " " + _("(1 = keep tx meta data e.g. account owner and payment request information, 2 = drop tx meta data)"));
+ 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;
+}
- if (showDebug)
- {
- strUsage += HelpMessageGroup(_("Wallet debugging/testing options:"));
+void CWallet::MarkPreSplitKeys()
+{
+ WalletBatch batch(*database);
+ for (auto it = setExternalKeyPool.begin(); it != setExternalKeyPool.end();) {
+ int64_t index = *it;
+ CKeyPool keypool;
+ if (!batch.ReadPool(index, keypool)) {
+ throw std::runtime_error(std::string(__func__) + ": read keypool entry failed");
+ }
+ keypool.m_pre_split = true;
+ if (!batch.WritePool(index, keypool)) {
+ throw std::runtime_error(std::string(__func__) + ": writing modified keypool entry failed");
+ }
+ set_pre_split_keypool.insert(index);
+ it = setExternalKeyPool.erase(it);
+ }
+}
- strUsage += HelpMessageOpt("-dblogsize=<n>", strprintf("Flush wallet database activity from memory to disk log every <n> megabytes (default: %u)", DEFAULT_WALLET_DBLOGSIZE));
- strUsage += HelpMessageOpt("-flushwallet", strprintf("Run a thread to flush wallet periodically (default: %u)", DEFAULT_FLUSHWALLET));
- strUsage += HelpMessageOpt("-privdb", strprintf("Sets the DB_PRIVATE flag in the wallet db environment (default: %u)", DEFAULT_WALLET_PRIVDB));
+bool CWallet::Verify(std::string wallet_file, bool salvage_wallet, std::string& error_string, std::string& warning_string)
+{
+ // Do some checking on wallet path. It should be either a:
+ //
+ // 1. Path where a directory can be created.
+ // 2. Path to an existing directory.
+ // 3. Path to a symlink to a directory.
+ // 4. For backwards compatibility, the name of a data file in -walletdir.
+ LOCK(cs_wallets);
+ fs::path wallet_path = fs::absolute(wallet_file, GetWalletDir());
+ fs::file_type path_type = fs::symlink_status(wallet_path).type();
+ if (!(path_type == fs::file_not_found || path_type == fs::directory_file ||
+ (path_type == fs::symlink_file && fs::is_directory(wallet_path)) ||
+ (path_type == fs::regular_file && fs::path(wallet_file).filename() == wallet_file))) {
+ error_string = strprintf(
+ "Invalid -wallet path '%s'. -wallet path should point to a directory where wallet.dat and "
+ "database/log.?????????? files can be stored, a location where such a directory could be created, "
+ "or (for backwards compatibility) the name of an existing data file in -walletdir (%s)",
+ wallet_file, GetWalletDir());
+ return false;
}
- return strUsage;
+ // Make sure that the wallet path doesn't clash with an existing wallet path
+ for (auto wallet : GetWallets()) {
+ if (fs::absolute(wallet->GetName(), GetWalletDir()) == wallet_path) {
+ error_string = strprintf("Error loading wallet %s. Duplicate -wallet filename specified.", wallet_file);
+ return false;
+ }
+ }
+
+ try {
+ if (!WalletBatch::VerifyEnvironment(wallet_path, error_string)) {
+ return false;
+ }
+ } catch (const fs::filesystem_error& e) {
+ error_string = strprintf("Error loading wallet %s. %s", wallet_file, e.what());
+ return false;
+ }
+
+ if (salvage_wallet) {
+ // Recover readable keypairs:
+ CWallet dummyWallet("dummy", WalletDatabase::CreateDummy());
+ std::string backup_filename;
+ if (!WalletBatch::Recover(wallet_path, (void *)&dummyWallet, WalletBatch::RecoverKeysOnlyFilter, backup_filename)) {
+ return false;
+ }
+ }
+
+ return WalletBatch::VerifyDatabaseFile(wallet_path, warning_string, error_string);
}
-bool CWallet::InitLoadWallet()
+std::shared_ptr<CWallet> CWallet::CreateWalletFromFile(const std::string& name, const fs::path& path, uint64_t wallet_creation_flags)
{
- std::string walletFile = GetArg("-wallet", DEFAULT_WALLET_DAT);
+ const std::string& walletFile = name;
// needed to restore wallet transaction meta data after -zapwallettxes
std::vector<CWalletTx> vWtx;
- if (GetBoolArg("-zapwallettxes", false)) {
+ if (gArgs.GetBoolArg("-zapwallettxes", false)) {
uiInterface.InitMessage(_("Zapping all transactions from wallet..."));
- CWallet *tempWallet = new CWallet(walletFile);
+ std::unique_ptr<CWallet> tempWallet = MakeUnique<CWallet>(name, WalletDatabase::Create(path));
DBErrors nZapWalletRet = tempWallet->ZapWalletTx(vWtx);
- if (nZapWalletRet != DB_LOAD_OK) {
- return InitError(strprintf(_("Error loading %s: Wallet corrupted"), walletFile));
+ if (nZapWalletRet != DBErrors::LOAD_OK) {
+ InitError(strprintf(_("Error loading %s: Wallet corrupted"), walletFile));
+ return nullptr;
}
-
- delete tempWallet;
- tempWallet = NULL;
}
uiInterface.InitMessage(_("Loading wallet..."));
int64_t nStart = GetTimeMillis();
bool fFirstRun = true;
- CWallet *walletInstance = new CWallet(walletFile);
+ // TODO: Can't use std::make_shared because we need a custom deleter but
+ // should be possible to use std::allocate_shared.
+ std::shared_ptr<CWallet> walletInstance(new CWallet(name, WalletDatabase::Create(path)), ReleaseWallet);
DBErrors nLoadWalletRet = walletInstance->LoadWallet(fFirstRun);
- if (nLoadWalletRet != DB_LOAD_OK)
+ if (nLoadWalletRet != DBErrors::LOAD_OK)
{
- if (nLoadWalletRet == DB_CORRUPT)
- return InitError(strprintf(_("Error loading %s: Wallet corrupted"), walletFile));
- else if (nLoadWalletRet == DB_NONCRITICAL_ERROR)
+ if (nLoadWalletRet == DBErrors::CORRUPT) {
+ InitError(strprintf(_("Error loading %s: Wallet corrupted"), walletFile));
+ return nullptr;
+ }
+ else if (nLoadWalletRet == DBErrors::NONCRITICAL_ERROR)
{
InitWarning(strprintf(_("Error reading %s! All keys read correctly, but transaction data"
" or address book entries might be missing or incorrect."),
walletFile));
}
- else if (nLoadWalletRet == DB_TOO_NEW)
- return InitError(strprintf(_("Error loading %s: Wallet requires newer version of %s"),
- walletFile, _(PACKAGE_NAME)));
- else if (nLoadWalletRet == DB_NEED_REWRITE)
+ else if (nLoadWalletRet == DBErrors::TOO_NEW) {
+ InitError(strprintf(_("Error loading %s: Wallet requires newer version of %s"), walletFile, _(PACKAGE_NAME)));
+ return nullptr;
+ }
+ else if (nLoadWalletRet == DBErrors::NEED_REWRITE)
{
- return InitError(strprintf(_("Wallet needed to be rewritten: restart %s to complete"), _(PACKAGE_NAME)));
+ InitError(strprintf(_("Wallet needed to be rewritten: restart %s to complete"), _(PACKAGE_NAME)));
+ return nullptr;
+ }
+ else {
+ InitError(strprintf(_("Error loading %s"), walletFile));
+ return nullptr;
}
- else
- return InitError(strprintf(_("Error loading %s"), walletFile));
}
- if (GetBoolArg("-upgradewallet", fFirstRun))
+ int prev_version = walletInstance->nWalletVersion;
+ if (gArgs.GetBoolArg("-upgradewallet", fFirstRun))
{
- int nMaxVersion = GetArg("-upgradewallet", 0);
+ int nMaxVersion = gArgs.GetArg("-upgradewallet", 0);
if (nMaxVersion == 0) // the -upgradewallet without argument case
{
- LogPrintf("Performing wallet upgrade to %i\n", FEATURE_LATEST);
- nMaxVersion = CLIENT_VERSION;
+ walletInstance->WalletLogPrintf("Performing wallet upgrade to %i\n", FEATURE_LATEST);
+ nMaxVersion = FEATURE_LATEST;
walletInstance->SetMinVersion(FEATURE_LATEST); // permanently upgrade the wallet immediately
}
else
- LogPrintf("Allowing wallet upgrade up to %i\n", nMaxVersion);
+ walletInstance->WalletLogPrintf("Allowing wallet upgrade up to %i\n", nMaxVersion);
if (nMaxVersion < walletInstance->GetVersion())
{
- return InitError(_("Cannot downgrade wallet"));
+ InitError(_("Cannot downgrade wallet"));
+ return nullptr;
}
walletInstance->SetMaxVersion(nMaxVersion);
}
+ // Upgrade to HD if explicit upgrade
+ if (gArgs.GetBoolArg("-upgradewallet", false)) {
+ LOCK(walletInstance->cs_wallet);
+
+ // Do not upgrade versions to any version between HD_SPLIT and FEATURE_PRE_SPLIT_KEYPOOL unless already supporting HD_SPLIT
+ int max_version = walletInstance->nWalletVersion;
+ if (!walletInstance->CanSupportFeature(FEATURE_HD_SPLIT) && max_version >=FEATURE_HD_SPLIT && max_version < FEATURE_PRE_SPLIT_KEYPOOL) {
+ InitError(_("Cannot upgrade a non HD split wallet without upgrading to support pre split keypool. Please use -upgradewallet=169900 or -upgradewallet with no version specified."));
+ return nullptr;
+ }
+
+ bool hd_upgrade = false;
+ bool split_upgrade = false;
+ if (walletInstance->CanSupportFeature(FEATURE_HD) && !walletInstance->IsHDEnabled()) {
+ walletInstance->WalletLogPrintf("Upgrading wallet to HD\n");
+ walletInstance->SetMinVersion(FEATURE_HD);
+
+ // generate a new master key
+ CPubKey masterPubKey = walletInstance->GenerateNewSeed();
+ walletInstance->SetHDSeed(masterPubKey);
+ hd_upgrade = true;
+ }
+ // Upgrade to HD chain split if necessary
+ if (walletInstance->CanSupportFeature(FEATURE_HD_SPLIT)) {
+ walletInstance->WalletLogPrintf("Upgrading wallet to use HD chain split\n");
+ walletInstance->SetMinVersion(FEATURE_PRE_SPLIT_KEYPOOL);
+ split_upgrade = FEATURE_HD_SPLIT > prev_version;
+ }
+ // Mark all keys currently in the keypool as pre-split
+ if (split_upgrade) {
+ walletInstance->MarkPreSplitKeys();
+ }
+ // Regenerate the keypool if upgraded to HD
+ if (hd_upgrade) {
+ if (!walletInstance->TopUpKeyPool()) {
+ InitError(_("Unable to generate keys"));
+ return nullptr;
+ }
+ }
+ }
+
if (fFirstRun)
{
- // Create new keyUser and set as default key
- if (GetBoolArg("-usehd", DEFAULT_USE_HD_WALLET) && !walletInstance->IsHDEnabled()) {
- // generate a new master key
- CPubKey masterPubKey = walletInstance->GenerateNewHDMasterKey();
- if (!walletInstance->SetHDMasterKey(masterPubKey))
- throw std::runtime_error(std::string(__func__) + ": Storing master key failed");
+ // ensure this wallet.dat can only be opened by clients supporting HD with chain split and expects no default key
+ if (!gArgs.GetBoolArg("-usehd", true)) {
+ InitError(strprintf(_("Error creating %s: You can't create non-HD wallets with this version."), walletFile));
+ return nullptr;
}
- CPubKey newDefaultKey;
- if (walletInstance->GetKeyFromPool(newDefaultKey)) {
- walletInstance->SetDefaultKey(newDefaultKey);
- if (!walletInstance->SetAddressBook(walletInstance->vchDefaultKey.GetID(), "", "receive"))
- return InitError(_("Cannot write default address") += "\n");
+ walletInstance->SetMinVersion(FEATURE_LATEST);
+
+ if ((wallet_creation_flags & WALLET_FLAG_DISABLE_PRIVATE_KEYS)) {
+ //selective allow to set flags
+ walletInstance->SetWalletFlag(WALLET_FLAG_DISABLE_PRIVATE_KEYS);
+ } else {
+ // generate a new seed
+ CPubKey seed = walletInstance->GenerateNewSeed();
+ walletInstance->SetHDSeed(seed);
}
- walletInstance->SetBestChain(chainActive.GetLocator());
+ // Top up the keypool
+ if (!walletInstance->IsWalletFlagSet(WALLET_FLAG_DISABLE_PRIVATE_KEYS) && !walletInstance->TopUpKeyPool()) {
+ InitError(_("Unable to generate initial keys"));
+ return nullptr;
+ }
+
+ walletInstance->ChainStateFlushed(chainActive.GetLocator());
+ } else if (wallet_creation_flags & WALLET_FLAG_DISABLE_PRIVATE_KEYS) {
+ // Make it impossible to disable private keys after creation
+ InitError(strprintf(_("Error loading %s: Private keys can only be disabled during creation"), walletFile));
+ return NULL;
+ } else if (walletInstance->IsWalletFlagSet(WALLET_FLAG_DISABLE_PRIVATE_KEYS)) {
+ LOCK(walletInstance->cs_KeyStore);
+ if (!walletInstance->mapKeys.empty() || !walletInstance->mapCryptedKeys.empty()) {
+ InitWarning(strprintf(_("Warning: Private keys detected in wallet {%s} with disabled private keys"), walletFile));
+ }
+ } else if (gArgs.IsArgSet("-usehd")) {
+ bool useHD = gArgs.GetBoolArg("-usehd", true);
+ if (walletInstance->IsHDEnabled() && !useHD) {
+ InitError(strprintf(_("Error loading %s: You can't disable HD on an already existing HD wallet"), walletFile));
+ return nullptr;
+ }
+ if (!walletInstance->IsHDEnabled() && useHD) {
+ InitError(strprintf(_("Error loading %s: You can't enable HD on an already existing non-HD wallet"), walletFile));
+ return nullptr;
+ }
}
- else if (mapArgs.count("-usehd")) {
- bool useHD = GetBoolArg("-usehd", DEFAULT_USE_HD_WALLET);
- if (walletInstance->IsHDEnabled() && !useHD)
- return InitError(strprintf(_("Error loading %s: You can't disable HD on a already existing HD wallet"), walletFile));
- if (!walletInstance->IsHDEnabled() && useHD)
- return InitError(strprintf(_("Error loading %s: You can't enable HD on a already existing non-HD wallet"), walletFile));
+
+ if (!gArgs.GetArg("-addresstype", "").empty() && !ParseOutputType(gArgs.GetArg("-addresstype", ""), walletInstance->m_default_address_type)) {
+ InitError(strprintf("Unknown address type '%s'", gArgs.GetArg("-addresstype", "")));
+ return nullptr;
}
- LogPrintf(" wallet %15dms\n", GetTimeMillis() - nStart);
+ if (!gArgs.GetArg("-changetype", "").empty() && !ParseOutputType(gArgs.GetArg("-changetype", ""), walletInstance->m_default_change_type)) {
+ InitError(strprintf("Unknown change type '%s'", gArgs.GetArg("-changetype", "")));
+ return nullptr;
+ }
- RegisterValidationInterface(walletInstance);
+ if (gArgs.IsArgSet("-mintxfee")) {
+ CAmount n = 0;
+ if (!ParseMoney(gArgs.GetArg("-mintxfee", ""), n) || 0 == n) {
+ InitError(AmountErrMsg("mintxfee", gArgs.GetArg("-mintxfee", "")));
+ return nullptr;
+ }
+ if (n > HIGH_TX_FEE_PER_KB) {
+ InitWarning(AmountHighWarn("-mintxfee") + " " +
+ _("This is the minimum transaction fee you pay on every transaction."));
+ }
+ walletInstance->m_min_fee = CFeeRate(n);
+ }
- CBlockIndex *pindexRescan = chainActive.Tip();
- if (GetBoolArg("-rescan", false))
- pindexRescan = chainActive.Genesis();
- else
+ walletInstance->m_allow_fallback_fee = Params().IsFallbackFeeEnabled();
+ if (gArgs.IsArgSet("-fallbackfee")) {
+ CAmount nFeePerK = 0;
+ if (!ParseMoney(gArgs.GetArg("-fallbackfee", ""), nFeePerK)) {
+ InitError(strprintf(_("Invalid amount for -fallbackfee=<amount>: '%s'"), gArgs.GetArg("-fallbackfee", "")));
+ return nullptr;
+ }
+ if (nFeePerK > HIGH_TX_FEE_PER_KB) {
+ InitWarning(AmountHighWarn("-fallbackfee") + " " +
+ _("This is the transaction fee you may pay when fee estimates are not available."));
+ }
+ walletInstance->m_fallback_fee = CFeeRate(nFeePerK);
+ walletInstance->m_allow_fallback_fee = nFeePerK != 0; //disable fallback fee in case value was set to 0, enable if non-null value
+ }
+ if (gArgs.IsArgSet("-discardfee")) {
+ CAmount nFeePerK = 0;
+ if (!ParseMoney(gArgs.GetArg("-discardfee", ""), nFeePerK)) {
+ InitError(strprintf(_("Invalid amount for -discardfee=<amount>: '%s'"), gArgs.GetArg("-discardfee", "")));
+ return nullptr;
+ }
+ if (nFeePerK > HIGH_TX_FEE_PER_KB) {
+ InitWarning(AmountHighWarn("-discardfee") + " " +
+ _("This is the transaction fee you may discard if change is smaller than dust at this level"));
+ }
+ walletInstance->m_discard_rate = CFeeRate(nFeePerK);
+ }
+ if (gArgs.IsArgSet("-paytxfee")) {
+ CAmount nFeePerK = 0;
+ if (!ParseMoney(gArgs.GetArg("-paytxfee", ""), nFeePerK)) {
+ InitError(AmountErrMsg("paytxfee", gArgs.GetArg("-paytxfee", "")));
+ return nullptr;
+ }
+ if (nFeePerK > HIGH_TX_FEE_PER_KB) {
+ InitWarning(AmountHighWarn("-paytxfee") + " " +
+ _("This is the transaction fee you will pay if you send a transaction."));
+ }
+ walletInstance->m_pay_tx_fee = CFeeRate(nFeePerK, 1000);
+ if (walletInstance->m_pay_tx_fee < ::minRelayTxFee) {
+ InitError(strprintf(_("Invalid amount for -paytxfee=<amount>: '%s' (must be at least %s)"),
+ gArgs.GetArg("-paytxfee", ""), ::minRelayTxFee.ToString()));
+ return nullptr;
+ }
+ }
+ walletInstance->m_confirm_target = gArgs.GetArg("-txconfirmtarget", DEFAULT_TX_CONFIRM_TARGET);
+ walletInstance->m_spend_zero_conf_change = gArgs.GetBoolArg("-spendzeroconfchange", DEFAULT_SPEND_ZEROCONF_CHANGE);
+ walletInstance->m_signal_rbf = gArgs.GetBoolArg("-walletrbf", DEFAULT_WALLET_RBF);
+
+ walletInstance->WalletLogPrintf("Wallet completed loading in %15dms\n", GetTimeMillis() - nStart);
+
+ // Try to top up keypool. No-op if the wallet is locked.
+ walletInstance->TopUpKeyPool();
+
+ LOCK(cs_main);
+
+ CBlockIndex *pindexRescan = chainActive.Genesis();
+ if (!gArgs.GetBoolArg("-rescan", false))
{
- CWalletDB walletdb(walletFile);
+ WalletBatch batch(*walletInstance->database);
CBlockLocator locator;
- if (walletdb.ReadBestBlock(locator))
+ if (batch.ReadBestBlock(locator))
pindexRescan = FindForkInGlobalIndex(chainActive, locator);
- else
- pindexRescan = chainActive.Genesis();
}
+
+ walletInstance->m_last_block_processed = chainActive.Tip();
+
if (chainActive.Tip() && chainActive.Tip() != pindexRescan)
{
//We can't rescan beyond non-pruned blocks, stop and throw an error
- //this might happen if a user uses a old wallet within a pruned node
+ //this might happen if a user uses an old wallet within a pruned node
// or if he ran -disablewallet for a longer time, then decided to re-enable
if (fPruneMode)
{
@@ -3376,24 +4269,40 @@ bool CWallet::InitLoadWallet()
while (block && block->pprev && (block->pprev->nStatus & BLOCK_HAVE_DATA) && block->pprev->nTx > 0 && pindexRescan != block)
block = block->pprev;
- if (pindexRescan != block)
- return InitError(_("Prune: last wallet synchronisation goes beyond pruned data. You need to -reindex (download the whole blockchain again in case of pruned node)"));
+ if (pindexRescan != block) {
+ InitError(_("Prune: last wallet synchronisation goes beyond pruned data. You need to -reindex (download the whole blockchain again in case of pruned node)"));
+ return nullptr;
+ }
}
uiInterface.InitMessage(_("Rescanning..."));
- LogPrintf("Rescanning last %i blocks (from block %i)...\n", chainActive.Height() - pindexRescan->nHeight, pindexRescan->nHeight);
+ walletInstance->WalletLogPrintf("Rescanning last %i blocks (from block %i)...\n", chainActive.Height() - pindexRescan->nHeight, pindexRescan->nHeight);
+
+ // No need to read and scan block if block was created before
+ // our wallet birthday (as adjusted for block time variability)
+ while (pindexRescan && walletInstance->nTimeFirstKey && (pindexRescan->GetBlockTime() < (walletInstance->nTimeFirstKey - TIMESTAMP_WINDOW))) {
+ pindexRescan = chainActive.Next(pindexRescan);
+ }
+
nStart = GetTimeMillis();
- walletInstance->ScanForWalletTransactions(pindexRescan, true);
- LogPrintf(" rescan %15dms\n", GetTimeMillis() - nStart);
- walletInstance->SetBestChain(chainActive.GetLocator());
- nWalletDBUpdated++;
+ {
+ WalletRescanReserver reserver(walletInstance.get());
+ if (!reserver.reserve()) {
+ InitError(_("Failed to rescan the wallet during initialization"));
+ return nullptr;
+ }
+ walletInstance->ScanForWalletTransactions(pindexRescan, nullptr, reserver, true);
+ }
+ walletInstance->WalletLogPrintf("Rescan completed in %15dms\n", GetTimeMillis() - nStart);
+ walletInstance->ChainStateFlushed(chainActive.GetLocator());
+ walletInstance->database->IncrementUpdateCounter();
// Restore wallet transaction metadata after -zapwallettxes=1
- if (GetBoolArg("-zapwallettxes", false) && GetArg("-zapwallettxes", "1") != "2")
+ if (gArgs.GetBoolArg("-zapwallettxes", false) && gArgs.GetArg("-zapwallettxes", "1") != "2")
{
- CWalletDB walletdb(walletFile);
+ WalletBatch batch(*walletInstance->database);
- BOOST_FOREACH(const CWalletTx& wtxOld, vWtx)
+ for (const CWalletTx& wtxOld : vWtx)
{
uint256 hash = wtxOld.GetHash();
std::map<uint256, CWalletTx>::iterator mi = walletInstance->mapWallet.find(hash);
@@ -3408,130 +4317,54 @@ bool CWallet::InitLoadWallet()
copyTo->fFromMe = copyFrom->fFromMe;
copyTo->strFromAccount = copyFrom->strFromAccount;
copyTo->nOrderPos = copyFrom->nOrderPos;
- walletdb.WriteTx(*copyTo);
+ batch.WriteTx(*copyTo);
}
}
}
}
- walletInstance->SetBroadcastTransactions(GetBoolArg("-walletbroadcast", DEFAULT_WALLETBROADCAST));
+
+ uiInterface.LoadWallet(walletInstance);
+
+ // Register with the validation interface. It's ok to do this after rescan since we're still holding cs_main.
+ RegisterValidationInterface(walletInstance.get());
+
+ walletInstance->SetBroadcastTransactions(gArgs.GetBoolArg("-walletbroadcast", DEFAULT_WALLETBROADCAST));
{
LOCK(walletInstance->cs_wallet);
- LogPrintf("setKeyPool.size() = %u\n", walletInstance->GetKeyPoolSize());
- LogPrintf("mapWallet.size() = %u\n", walletInstance->mapWallet.size());
- LogPrintf("mapAddressBook.size() = %u\n", walletInstance->mapAddressBook.size());
+ walletInstance->WalletLogPrintf("setKeyPool.size() = %u\n", walletInstance->GetKeyPoolSize());
+ walletInstance->WalletLogPrintf("mapWallet.size() = %u\n", walletInstance->mapWallet.size());
+ walletInstance->WalletLogPrintf("mapAddressBook.size() = %u\n", walletInstance->mapAddressBook.size());
}
- // Add wallet transactions that aren't already in a block to mapTransactions
- walletInstance->ReacceptWalletTransactions();
-
- pwalletMain = walletInstance;
- return true;
+ return walletInstance;
}
-bool CWallet::ParameterInteraction()
+void CWallet::postInitProcess()
{
- if (mapArgs.count("-mintxfee"))
- {
- CAmount n = 0;
- if (ParseMoney(mapArgs["-mintxfee"], n) && n > 0)
- CWallet::minTxFee = CFeeRate(n);
- else
- return InitError(AmountErrMsg("mintxfee", mapArgs["-mintxfee"]));
- }
- if (mapArgs.count("-fallbackfee"))
- {
- CAmount nFeePerK = 0;
- if (!ParseMoney(mapArgs["-fallbackfee"], nFeePerK))
- return InitError(strprintf(_("Invalid amount for -fallbackfee=<amount>: '%s'"), mapArgs["-fallbackfee"]));
- if (nFeePerK > HIGH_TX_FEE_PER_KB)
- InitWarning(_("-fallbackfee is set very high! This is the transaction fee you may pay when fee estimates are not available."));
- CWallet::fallbackFee = CFeeRate(nFeePerK);
- }
- if (mapArgs.count("-paytxfee"))
- {
- CAmount nFeePerK = 0;
- if (!ParseMoney(mapArgs["-paytxfee"], nFeePerK))
- return InitError(AmountErrMsg("paytxfee", mapArgs["-paytxfee"]));
- if (nFeePerK > HIGH_TX_FEE_PER_KB)
- InitWarning(_("-paytxfee is set very high! This is the transaction fee you will pay if you send a transaction."));
- payTxFee = CFeeRate(nFeePerK, 1000);
- if (payTxFee < ::minRelayTxFee)
- {
- return InitError(strprintf(_("Invalid amount for -paytxfee=<amount>: '%s' (must be at least %s)"),
- mapArgs["-paytxfee"], ::minRelayTxFee.ToString()));
- }
- }
- if (mapArgs.count("-maxtxfee"))
- {
- CAmount nMaxFee = 0;
- if (!ParseMoney(mapArgs["-maxtxfee"], nMaxFee))
- return InitError(AmountErrMsg("maxtxfee", mapArgs["-maxtxfee"]));
- if (nMaxFee > HIGH_MAX_TX_FEE)
- InitWarning(_("-maxtxfee is set very high! Fees this large could be paid on a single transaction."));
- maxTxFee = nMaxFee;
- if (CFeeRate(maxTxFee, 1000) < ::minRelayTxFee)
- {
- return InitError(strprintf(_("Invalid amount for -maxtxfee=<amount>: '%s' (must be at least the minrelay fee of %s to prevent stuck transactions)"),
- mapArgs["-maxtxfee"], ::minRelayTxFee.ToString()));
- }
- }
- nTxConfirmTarget = GetArg("-txconfirmtarget", DEFAULT_TX_CONFIRM_TARGET);
- bSpendZeroConfChange = GetBoolArg("-spendzeroconfchange", DEFAULT_SPEND_ZEROCONF_CHANGE);
- fSendFreeTransactions = GetBoolArg("-sendfreetransactions", DEFAULT_SEND_FREE_TRANSACTIONS);
-
- return true;
+ // Add wallet transactions that aren't already in a block to mempool
+ // Do this here as mempool requires genesis block to be loaded
+ ReacceptWalletTransactions();
}
bool CWallet::BackupWallet(const std::string& strDest)
{
- if (!fFileBacked)
- return false;
- while (true)
- {
- {
- LOCK(bitdb.cs_db);
- if (!bitdb.mapFileUseCount.count(strWalletFile) || bitdb.mapFileUseCount[strWalletFile] == 0)
- {
- // Flush log data to the dat file
- bitdb.CloseDb(strWalletFile);
- bitdb.CheckpointLSN(strWalletFile);
- bitdb.mapFileUseCount.erase(strWalletFile);
-
- // Copy wallet file
- boost::filesystem::path pathSrc = GetDataDir() / strWalletFile;
- boost::filesystem::path pathDest(strDest);
- if (boost::filesystem::is_directory(pathDest))
- pathDest /= strWalletFile;
-
- try {
-#if BOOST_VERSION >= 104000
- boost::filesystem::copy_file(pathSrc, pathDest, boost::filesystem::copy_option::overwrite_if_exists);
-#else
- boost::filesystem::copy_file(pathSrc, pathDest);
-#endif
- LogPrintf("copied %s to %s\n", strWalletFile, pathDest.string());
- return true;
- } catch (const boost::filesystem::filesystem_error& e) {
- LogPrintf("error copying %s to %s - %s\n", strWalletFile, pathDest.string(), e.what());
- return false;
- }
- }
- }
- MilliSleep(100);
- }
- return false;
+ return database->Backup(strDest);
}
CKeyPool::CKeyPool()
{
nTime = GetTime();
+ fInternal = false;
+ m_pre_split = false;
}
-CKeyPool::CKeyPool(const CPubKey& vchPubKeyIn)
+CKeyPool::CKeyPool(const CPubKey& vchPubKeyIn, bool internalIn)
{
nTime = GetTime();
vchPubKey = vchPubKeyIn;
+ fInternal = internalIn;
+ m_pre_split = false;
}
CWalletKey::CWalletKey(int64_t nExpires)
@@ -3540,24 +4373,16 @@ CWalletKey::CWalletKey(int64_t nExpires)
nTimeExpires = nExpires;
}
-int CMerkleTx::SetMerkleBranch(const CBlockIndex* pindex, int posInBlock)
+void CMerkleTx::SetMerkleBranch(const CBlockIndex* pindex, int posInBlock)
{
- AssertLockHeld(cs_main);
-
// Update the tx's hashBlock
hashBlock = pindex->GetBlockHash();
// set the position of the transaction in the block
nIndex = posInBlock;
-
- // Is the tx in a block that's in the main chain
- if (!chainActive.Contains(pindex))
- return 0;
-
- return chainActive.Height() - pindex->nHeight + 1;
}
-int CMerkleTx::GetDepthInMainChain(const CBlockIndex* &pindexRet) const
+int CMerkleTx::GetDepthInMainChain() const
{
if (hashUnset())
return 0;
@@ -3565,14 +4390,10 @@ int CMerkleTx::GetDepthInMainChain(const CBlockIndex* &pindexRet) const
AssertLockHeld(cs_main);
// Find the block it claims to be in
- BlockMap::iterator mi = mapBlockIndex.find(hashBlock);
- if (mi == mapBlockIndex.end())
- return 0;
- CBlockIndex* pindex = (*mi).second;
+ CBlockIndex* pindex = LookupBlockIndex(hashBlock);
if (!pindex || !chainActive.Contains(pindex))
return 0;
- pindexRet = pindex;
return ((nIndex == -1) ? (-1) : 1) * (chainActive.Height() - pindex->nHeight + 1);
}
@@ -3580,12 +4401,68 @@ int CMerkleTx::GetBlocksToMaturity() const
{
if (!IsCoinBase())
return 0;
- return max(0, (COINBASE_MATURITY+1) - GetDepthInMainChain());
+ int chain_depth = GetDepthInMainChain();
+ assert(chain_depth >= 0); // coinbase tx should not be conflicted
+ return std::max(0, (COINBASE_MATURITY+1) - chain_depth);
}
-bool CMerkleTx::AcceptToMemoryPool(bool fLimitFree, CAmount nAbsurdFee)
+bool CWalletTx::AcceptToMemoryPool(const CAmount& nAbsurdFee, CValidationState& state)
{
- CValidationState state;
- return ::AcceptToMemoryPool(mempool, state, *this, fLimitFree, NULL, false, nAbsurdFee);
+ // We must set fInMempool here - while it will be re-set to true by the
+ // entered-mempool callback, if we did not there would be a race where a
+ // user could call sendmoney in a loop and hit spurious out of funds errors
+ // because we think that this newly generated transaction's change is
+ // unavailable as we're not yet aware that it is in the mempool.
+ bool ret = ::AcceptToMemoryPool(mempool, state, tx, nullptr /* pfMissingInputs */,
+ nullptr /* plTxnReplaced */, false /* bypass_limits */, nAbsurdFee);
+ fInMempool |= ret;
+ return ret;
+}
+
+void CWallet::LearnRelatedScripts(const CPubKey& key, OutputType type)
+{
+ if (key.IsCompressed() && (type == OutputType::P2SH_SEGWIT || type == OutputType::BECH32)) {
+ CTxDestination witdest = WitnessV0KeyHash(key.GetID());
+ CScript witprog = GetScriptForDestination(witdest);
+ // Make sure the resulting program is solvable.
+ assert(IsSolvable(*this, witprog));
+ AddCScript(witprog);
+ }
+}
+
+void CWallet::LearnAllRelatedScripts(const CPubKey& key)
+{
+ // OutputType::P2SH_SEGWIT always adds all necessary scripts for all types.
+ LearnRelatedScripts(key, OutputType::P2SH_SEGWIT);
+}
+
+std::vector<OutputGroup> CWallet::GroupOutputs(const std::vector<COutput>& outputs, bool single_coin) const {
+ std::vector<OutputGroup> groups;
+ std::map<CTxDestination, OutputGroup> gmap;
+ CTxDestination dst;
+ for (const auto& output : outputs) {
+ if (output.fSpendable) {
+ CInputCoin input_coin = output.GetInputCoin();
+
+ size_t ancestors, descendants;
+ mempool.GetTransactionAncestry(output.tx->GetHash(), ancestors, descendants);
+ if (!single_coin && ExtractDestination(output.tx->tx->vout[output.i].scriptPubKey, dst)) {
+ // Limit output groups to no more than 10 entries, to protect
+ // against inadvertently creating a too-large transaction
+ // when using -avoidpartialspends
+ if (gmap[dst].m_outputs.size() >= OUTPUT_GROUP_MAX_ENTRIES) {
+ groups.push_back(gmap[dst]);
+ gmap.erase(dst);
+ }
+ gmap[dst].Insert(input_coin, output.nDepth, output.tx->IsFromMe(ISMINE_ALL), ancestors, descendants);
+ } else {
+ groups.emplace_back(input_coin, output.nDepth, output.tx->IsFromMe(ISMINE_ALL), ancestors, descendants);
+ }
+ }
+ }
+ if (!single_coin) {
+ for (const auto& it : gmap) groups.push_back(it.second);
+ }
+ return groups;
}
diff --git a/src/wallet/wallet.h b/src/wallet/wallet.h
index c06513650c..57b22c0e49 100644
--- a/src/wallet/wallet.h
+++ b/src/wallet/wallet.h
@@ -1,24 +1,31 @@
// Copyright (c) 2009-2010 Satoshi Nakamoto
-// Copyright (c) 2009-2015 The Bitcoin Core developers
+// Copyright (c) 2009-2018 The Bitcoin Core developers
// Distributed under the MIT software license, see the accompanying
// file COPYING or http://www.opensource.org/licenses/mit-license.php.
#ifndef BITCOIN_WALLET_WALLET_H
#define BITCOIN_WALLET_WALLET_H
-#include "amount.h"
-#include "streams.h"
-#include "tinyformat.h"
-#include "ui_interface.h"
-#include "utilstrencodings.h"
-#include "validationinterface.h"
-#include "script/ismine.h"
-#include "wallet/crypter.h"
-#include "wallet/walletdb.h"
-#include "wallet/rpcwallet.h"
+#include <amount.h>
+#include <outputtype.h>
+#include <policy/feerate.h>
+#include <streams.h>
+#include <tinyformat.h>
+#include <ui_interface.h>
+#include <utilstrencodings.h>
+#include <validationinterface.h>
+#include <script/ismine.h>
+#include <script/sign.h>
+#include <util.h>
+#include <wallet/crypter.h>
+#include <wallet/coinselection.h>
+#include <wallet/walletdb.h>
+#include <wallet/rpcwallet.h>
#include <algorithm>
+#include <atomic>
#include <map>
+#include <memory>
#include <set>
#include <stdexcept>
#include <stdint.h>
@@ -26,41 +33,36 @@
#include <utility>
#include <vector>
-#include <boost/shared_ptr.hpp>
+bool AddWallet(const std::shared_ptr<CWallet>& wallet);
+bool RemoveWallet(const std::shared_ptr<CWallet>& wallet);
+bool HasWallets();
+std::vector<std::shared_ptr<CWallet>> GetWallets();
+std::shared_ptr<CWallet> GetWallet(const std::string& name);
-extern CWallet* pwalletMain;
-
-/**
- * Settings
- */
-extern CFeeRate payTxFee;
-extern unsigned int nTxConfirmTarget;
-extern bool bSpendZeroConfChange;
-extern bool fSendFreeTransactions;
-
-static const unsigned int DEFAULT_KEYPOOL_SIZE = 100;
+//! Default for -keypool
+static const unsigned int DEFAULT_KEYPOOL_SIZE = 1000;
//! -paytxfee default
-static const CAmount DEFAULT_TRANSACTION_FEE = 0;
+constexpr CAmount DEFAULT_PAY_TX_FEE = 0;
//! -fallbackfee default
static const CAmount DEFAULT_FALLBACK_FEE = 20000;
+//! -discardfee default
+static const CAmount DEFAULT_DISCARD_FEE = 10000;
//! -mintxfee default
static const CAmount DEFAULT_TRANSACTION_MINFEE = 1000;
-//! minimum change amount
-static const CAmount MIN_CHANGE = CENT;
+//! minimum recommended increment for BIP 125 replacement txs
+static const CAmount WALLET_INCREMENTAL_RELAY_FEE = 5000;
//! Default for -spendzeroconfchange
static const bool DEFAULT_SPEND_ZEROCONF_CHANGE = true;
-//! Default for -sendfreetransactions
-static const bool DEFAULT_SEND_FREE_TRANSACTIONS = false;
+//! Default for -walletrejectlongchains
+static const bool DEFAULT_WALLET_REJECT_LONG_CHAINS = false;
+//! Default for -avoidpartialspends
+static const bool DEFAULT_AVOIDPARTIALSPENDS = false;
//! -txconfirmtarget default
-static const unsigned int DEFAULT_TX_CONFIRM_TARGET = 2;
-//! Largest (in bytes) free transaction we're willing to create
-static const unsigned int MAX_FREE_TRANSACTION_CREATE_SIZE = 1000;
+static const unsigned int DEFAULT_TX_CONFIRM_TARGET = 6;
+//! -walletrbf default
+static const bool DEFAULT_WALLET_RBF = false;
static const bool DEFAULT_WALLETBROADCAST = true;
-
-//! if set, all keys will be derived by using BIP32
-static const bool DEFAULT_USE_HD_WALLET = true;
-
-extern const char * DEFAULT_WALLET_DAT;
+static const bool DEFAULT_DISABLE_WALLET = false;
class CBlockIndex;
class CCoinControl;
@@ -68,20 +70,45 @@ class COutput;
class CReserveKey;
class CScript;
class CTxMemPool;
+class CBlockPolicyEstimator;
class CWalletTx;
+struct FeeCalculation;
+enum class FeeEstimateMode;
/** (client) version numbers for particular wallet features */
enum WalletFeature
{
- FEATURE_BASE = 10500, // the earliest version new wallets supports (only useful for getinfo's clientversion output)
+ FEATURE_BASE = 10500, // the earliest version new wallets supports (only useful for getwalletinfo's clientversion output)
FEATURE_WALLETCRYPT = 40000, // wallet encryption
FEATURE_COMPRPUBKEY = 60000, // compressed public keys
FEATURE_HD = 130000, // Hierarchical key derivation after BIP32 (HD Wallet)
- FEATURE_LATEST = FEATURE_COMPRPUBKEY // HD is optional, use FEATURE_COMPRPUBKEY as latest version
+
+ FEATURE_HD_SPLIT = 139900, // Wallet with HD chain split (change outputs will use m/0'/1'/k)
+
+ FEATURE_NO_DEFAULT_KEY = 159900, // Wallet without a default key written
+
+ FEATURE_PRE_SPLIT_KEYPOOL = 169900, // Upgraded to HD SPLIT and can have a pre-split keypool
+
+ FEATURE_LATEST = FEATURE_PRE_SPLIT_KEYPOOL
+};
+
+//! Default for -addresstype
+constexpr OutputType DEFAULT_ADDRESS_TYPE{OutputType::P2SH_SEGWIT};
+
+//! Default for -changetype
+constexpr OutputType DEFAULT_CHANGE_TYPE{OutputType::CHANGE_AUTO};
+
+enum WalletFlags : uint64_t {
+ // wallet flags in the upper section (> 1 << 31) will lead to not opening the wallet if flag is unknown
+ // unknown wallet flags in the lower section <= (1 << 31) will be tolerated
+
+ // will enforce the rule that the wallet can't contain any private keys (only watch-only/pubkeys)
+ WALLET_FLAG_DISABLE_PRIVATE_KEYS = (1ULL << 32),
};
+static constexpr uint64_t g_known_wallet_flags = WALLET_FLAG_DISABLE_PRIVATE_KEYS;
/** A key pool entry */
class CKeyPool
@@ -89,18 +116,43 @@ class CKeyPool
public:
int64_t nTime;
CPubKey vchPubKey;
+ bool fInternal; // for change outputs
+ bool m_pre_split; // For keys generated before keypool split upgrade
CKeyPool();
- CKeyPool(const CPubKey& vchPubKeyIn);
+ CKeyPool(const CPubKey& vchPubKeyIn, bool internalIn);
ADD_SERIALIZE_METHODS;
template <typename Stream, typename Operation>
- inline void SerializationOp(Stream& s, Operation ser_action, int nType, int nVersion) {
- if (!(nType & SER_GETHASH))
+ inline void SerializationOp(Stream& s, Operation ser_action) {
+ int nVersion = s.GetVersion();
+ if (!(s.GetType() & SER_GETHASH))
READWRITE(nVersion);
READWRITE(nTime);
READWRITE(vchPubKey);
+ if (ser_action.ForRead()) {
+ try {
+ READWRITE(fInternal);
+ }
+ catch (std::ios_base::failure&) {
+ /* flag as external address if we can't read the internal boolean
+ (this will be the case for any wallet before the HD chain split version) */
+ fInternal = false;
+ }
+ try {
+ READWRITE(m_pre_split);
+ }
+ catch (std::ios_base::failure&) {
+ /* flag as postsplit address if we can't read the m_pre_split boolean
+ (this will be the case for any wallet that upgrades to HD chain split)*/
+ m_pre_split = false;
+ }
+ }
+ else {
+ READWRITE(fInternal);
+ READWRITE(m_pre_split);
+ }
}
};
@@ -111,10 +163,7 @@ public:
std::string name;
std::string purpose;
- CAddressBookData()
- {
- purpose = "unknown";
- }
+ CAddressBookData() : purpose("unknown") {}
typedef std::map<std::string, std::string> StringMap;
StringMap destdata;
@@ -130,7 +179,7 @@ struct CRecipient
typedef std::map<std::string, std::string> mapValue_t;
-static void ReadOrderPos(int64_t& nOrderPos, mapValue_t& mapValue)
+static inline void ReadOrderPos(int64_t& nOrderPos, mapValue_t& mapValue)
{
if (!mapValue.count("n"))
{
@@ -141,7 +190,7 @@ static void ReadOrderPos(int64_t& nOrderPos, mapValue_t& mapValue)
}
-static void WriteOrderPos(const int64_t& nOrderPos, mapValue_t& mapValue)
+static inline void WriteOrderPos(const int64_t& nOrderPos, mapValue_t& mapValue)
{
if (nOrderPos == -1)
return;
@@ -156,13 +205,14 @@ struct COutputEntry
};
/** A transaction with a merkle branch linking it to the block chain. */
-class CMerkleTx : public CTransaction
+class CMerkleTx
{
private:
/** Constant used in hashBlock to indicate tx has been abandoned */
static const uint256 ABANDON_HASH;
public:
+ CTransactionRef tx;
uint256 hashBlock;
/* An nIndex == -1 means that hashBlock (in nonzero) refers to the earliest
@@ -174,11 +224,13 @@ public:
CMerkleTx()
{
+ SetTx(MakeTransactionRef());
Init();
}
- CMerkleTx(const CTransaction& txIn) : CTransaction(txIn)
+ explicit CMerkleTx(CTransactionRef arg)
{
+ SetTx(std::move(arg));
Init();
}
@@ -188,19 +240,23 @@ public:
nIndex = -1;
}
+ void SetTx(CTransactionRef arg)
+ {
+ tx = std::move(arg);
+ }
+
ADD_SERIALIZE_METHODS;
template <typename Stream, typename Operation>
- inline void SerializationOp(Stream& s, Operation ser_action, int nType, int nVersion) {
+ inline void SerializationOp(Stream& s, Operation ser_action) {
std::vector<uint256> vMerkleBranch; // For compatibility with older versions.
- READWRITE(*(CTransaction*)this);
- nVersion = this->nVersion;
+ READWRITE(tx);
READWRITE(hashBlock);
READWRITE(vMerkleBranch);
READWRITE(nIndex);
}
- int SetMerkleBranch(const CBlockIndex* pIndex, int posInBlock);
+ void SetMerkleBranch(const CBlockIndex* pIndex, int posInBlock);
/**
* Return depth of transaction in blockchain:
@@ -208,18 +264,21 @@ public:
* 0 : in memory pool, waiting to be included in a block
* >=1 : this many blocks deep in the main chain
*/
- int GetDepthInMainChain(const CBlockIndex* &pindexRet) const;
- int GetDepthInMainChain() const { const CBlockIndex *pindexRet; return GetDepthInMainChain(pindexRet); }
- bool IsInMainChain() const { const CBlockIndex *pindexRet; return GetDepthInMainChain(pindexRet) > 0; }
+ int GetDepthInMainChain() const;
+ bool IsInMainChain() const { return GetDepthInMainChain() > 0; }
int GetBlocksToMaturity() const;
- /** Pass this transaction to the mempool. Fails if absolute fee exceeds absurd fee. */
- bool AcceptToMemoryPool(bool fLimitFree, const CAmount nAbsurdFee);
bool hashUnset() const { return (hashBlock.IsNull() || hashBlock == ABANDON_HASH); }
bool isAbandoned() const { return (hashBlock == ABANDON_HASH); }
void setAbandoned() { hashBlock = ABANDON_HASH; }
+
+ const uint256& GetHash() const { return tx->GetHash(); }
+ bool IsCoinBase() const { return tx->IsCoinBase(); }
};
-/**
+//Get the marginal bytes of spending the specified output
+int CalculateMaximumSignedInputSize(const CTxOut& txout, const CWallet* pwallet, bool use_max_sig = false);
+
+/**
* A transaction with a bunch of additional info that only the owner cares about.
* It includes any unrecorded transactions needed to link it back to the block chain.
*/
@@ -229,14 +288,54 @@ private:
const CWallet* pwallet;
public:
+ /**
+ * Key/value map with information about the transaction.
+ *
+ * The following keys can be read and written through the map and are
+ * serialized in the wallet database:
+ *
+ * "comment", "to" - comment strings provided to sendtoaddress,
+ * sendfrom, sendmany wallet RPCs
+ * "replaces_txid" - txid (as HexStr) of transaction replaced by
+ * bumpfee on transaction created by bumpfee
+ * "replaced_by_txid" - txid (as HexStr) of transaction created by
+ * bumpfee on transaction replaced by bumpfee
+ * "from", "message" - obsolete fields that could be set in UI prior to
+ * 2011 (removed in commit 4d9b223)
+ *
+ * The following keys are serialized in the wallet database, but shouldn't
+ * be read or written through the map (they will be temporarily added and
+ * removed from the map during serialization):
+ *
+ * "fromaccount" - serialized strFromAccount value
+ * "n" - serialized nOrderPos value
+ * "timesmart" - serialized nTimeSmart value
+ * "spent" - serialized vfSpent value that existed prior to
+ * 2014 (removed in commit 93a18a3)
+ */
mapValue_t mapValue;
std::vector<std::pair<std::string, std::string> > vOrderForm;
unsigned int fTimeReceivedIsTxTime;
unsigned int nTimeReceived; //!< time received by this node
+ /**
+ * Stable timestamp that never changes, and reflects the order a transaction
+ * was added to the wallet. Timestamp is based on the block time for a
+ * transaction added as part of a block, or else the time when the
+ * transaction was received if it wasn't part of a block, with the timestamp
+ * adjusted in both cases so timestamp order matches the order transactions
+ * were added to the wallet. More details can be found in
+ * CWallet::ComputeTimeSmart().
+ */
unsigned int nTimeSmart;
+ /**
+ * From me flag is set to 1 for transactions that were created by the wallet
+ * on this bitcoin node, and set to 0 for transactions that were created
+ * externally and came in through the network or sendrawtransaction RPC.
+ */
char fFromMe;
std::string strFromAccount;
int64_t nOrderPos; //!< position in ordered transaction list
+ std::multimap<int64_t, std::pair<CWalletTx*, CAccountingEntry*>>::const_iterator m_it_wtxOrdered;
// memory only
mutable bool fDebitCached;
@@ -248,6 +347,7 @@ public:
mutable bool fImmatureWatchCreditCached;
mutable bool fAvailableWatchCreditCached;
mutable bool fChangeCached;
+ mutable bool fInMempool;
mutable CAmount nDebitCached;
mutable CAmount nCreditCached;
mutable CAmount nImmatureCreditCached;
@@ -258,22 +358,7 @@ public:
mutable CAmount nAvailableWatchCreditCached;
mutable CAmount nChangeCached;
- CWalletTx()
- {
- Init(NULL);
- }
-
- CWalletTx(const CWallet* pwalletIn)
- {
- Init(pwalletIn);
- }
-
- CWalletTx(const CWallet* pwalletIn, const CMerkleTx& txIn) : CMerkleTx(txIn)
- {
- Init(pwalletIn);
- }
-
- CWalletTx(const CWallet* pwalletIn, const CTransaction& txIn) : CMerkleTx(txIn)
+ CWalletTx(const CWallet* pwalletIn, CTransactionRef arg) : CMerkleTx(std::move(arg))
{
Init(pwalletIn);
}
@@ -297,6 +382,7 @@ public:
fImmatureWatchCreditCached = false;
fAvailableWatchCreditCached = false;
fChangeCached = false;
+ fInMempool = false;
nDebitCached = 0;
nCreditCached = 0;
nImmatureCreditCached = 0;
@@ -309,45 +395,38 @@ public:
nOrderPos = -1;
}
- ADD_SERIALIZE_METHODS;
-
- template <typename Stream, typename Operation>
- inline void SerializationOp(Stream& s, Operation ser_action, int nType, int nVersion) {
- if (ser_action.ForRead())
- Init(NULL);
+ template<typename Stream>
+ void Serialize(Stream& s) const
+ {
char fSpent = false;
+ mapValue_t mapValueCopy = mapValue;
- if (!ser_action.ForRead())
- {
- mapValue["fromaccount"] = strFromAccount;
-
- WriteOrderPos(nOrderPos, mapValue);
-
- if (nTimeSmart)
- mapValue["timesmart"] = strprintf("%u", nTimeSmart);
+ mapValueCopy["fromaccount"] = strFromAccount;
+ WriteOrderPos(nOrderPos, mapValueCopy);
+ if (nTimeSmart) {
+ mapValueCopy["timesmart"] = strprintf("%u", nTimeSmart);
}
- READWRITE(*(CMerkleTx*)this);
+ s << static_cast<const CMerkleTx&>(*this);
std::vector<CMerkleTx> vUnused; //!< Used to be vtxPrev
- READWRITE(vUnused);
- READWRITE(mapValue);
- READWRITE(vOrderForm);
- READWRITE(fTimeReceivedIsTxTime);
- READWRITE(nTimeReceived);
- READWRITE(fFromMe);
- READWRITE(fSpent);
+ s << vUnused << mapValueCopy << vOrderForm << fTimeReceivedIsTxTime << nTimeReceived << fFromMe << fSpent;
+ }
- if (ser_action.ForRead())
- {
- strFromAccount = mapValue["fromaccount"];
+ template<typename Stream>
+ void Unserialize(Stream& s)
+ {
+ Init(nullptr);
+ char fSpent;
- ReadOrderPos(nOrderPos, mapValue);
+ s >> static_cast<CMerkleTx&>(*this);
+ std::vector<CMerkleTx> vUnused; //!< Used to be vtxPrev
+ s >> vUnused >> mapValue >> vOrderForm >> fTimeReceivedIsTxTime >> nTimeReceived >> fFromMe >> fSpent;
- nTimeSmart = mapValue.count("timesmart") ? (unsigned int)atoi64(mapValue["timesmart"]) : 0;
- }
+ strFromAccount = std::move(mapValue["fromaccount"]);
+ ReadOrderPos(nOrderPos, mapValue);
+ nTimeSmart = mapValue.count("timesmart") ? (unsigned int)atoi64(mapValue["timesmart"]) : 0;
mapValue.erase("fromaccount");
- mapValue.erase("version");
mapValue.erase("spent");
mapValue.erase("n");
mapValue.erase("timesmart");
@@ -358,6 +437,7 @@ public:
{
fCreditCached = false;
fAvailableCreditCached = false;
+ fImmatureCreditCached = false;
fWatchDebitCached = false;
fWatchCreditCached = false;
fAvailableWatchCreditCached = false;
@@ -376,17 +456,19 @@ public:
CAmount GetDebit(const isminefilter& filter) const;
CAmount GetCredit(const isminefilter& filter) const;
CAmount GetImmatureCredit(bool fUseCache=true) const;
- CAmount GetAvailableCredit(bool fUseCache=true) const;
- CAmount GetImmatureWatchOnlyCredit(const bool& fUseCache=true) const;
- CAmount GetAvailableWatchOnlyCredit(const bool& fUseCache=true) const;
+ CAmount GetAvailableCredit(bool fUseCache=true, const isminefilter& filter=ISMINE_SPENDABLE) const;
+ CAmount GetImmatureWatchOnlyCredit(const bool fUseCache=true) const;
CAmount GetChange() const;
+ // Get the marginal bytes if spending the specified output from this transaction
+ int GetSpendSize(unsigned int out, bool use_max_sig = false) const
+ {
+ return CalculateMaximumSignedInputSize(tx->vout[out], pwallet, use_max_sig);
+ }
+
void GetAmounts(std::list<COutputEntry>& listReceived,
std::list<COutputEntry>& listSent, CAmount& nFee, std::string& strSentAccount, const isminefilter& filter) const;
- void GetAccountAmounts(const std::string& strAccount, CAmount& nReceived,
- CAmount& nSent, CAmount& nFee, const isminefilter& filter) const;
-
bool IsFromMe(const isminefilter& filter) const
{
return (GetDebit(filter) > 0);
@@ -399,35 +481,59 @@ public:
bool IsTrusted() const;
int64_t GetTxTime() const;
- int GetRequestCount() const;
- bool RelayWalletTransaction();
+ // RelayWalletTransaction may only be called if fBroadcastTransactions!
+ bool RelayWalletTransaction(CConnman* connman);
+
+ /** Pass this transaction to the mempool. Fails if absolute fee exceeds absurd fee. */
+ bool AcceptToMemoryPool(const CAmount& nAbsurdFee, CValidationState& state);
std::set<uint256> GetConflicts() const;
};
-
-
-
class COutput
{
public:
const CWalletTx *tx;
int i;
int nDepth;
+
+ /** Pre-computed estimated size of this output as a fully-signed input in a transaction. Can be -1 if it could not be calculated */
+ int nInputBytes;
+
+ /** Whether we have the private keys to spend this output */
bool fSpendable;
+
+ /** Whether we know how to spend this output, ignoring the lack of keys */
bool fSolvable;
- COutput(const CWalletTx *txIn, int iIn, int nDepthIn, bool fSpendableIn, bool fSolvableIn)
+ /** Whether to use the maximum sized, 72 byte signature when calculating the size of the input spend. This should only be set when watch-only outputs are allowed */
+ bool use_max_sig;
+
+ /**
+ * Whether this output is considered safe to spend. Unconfirmed transactions
+ * from outside keys and unconfirmed replacement transactions are considered
+ * unsafe and will not be used to fund new spending transactions.
+ */
+ bool fSafe;
+
+ COutput(const CWalletTx *txIn, int iIn, int nDepthIn, bool fSpendableIn, bool fSolvableIn, bool fSafeIn, bool use_max_sig_in = false)
{
- tx = txIn; i = iIn; nDepth = nDepthIn; fSpendable = fSpendableIn; fSolvable = fSolvableIn;
+ tx = txIn; i = iIn; nDepth = nDepthIn; fSpendable = fSpendableIn; fSolvable = fSolvableIn; fSafe = fSafeIn; nInputBytes = -1; use_max_sig = use_max_sig_in;
+ // If known and signable by the given wallet, compute nInputBytes
+ // Failure will keep this value -1
+ if (fSpendable && tx) {
+ nInputBytes = tx->GetSpendSize(i, use_max_sig);
+ }
}
std::string ToString() const;
-};
-
-
+ inline CInputCoin GetInputCoin() const
+ {
+ return CInputCoin(tx->tx, i, nInputBytes);
+ }
+};
/** Private key that includes an expiration date in case it never gets used. */
class CWalletKey
@@ -440,13 +546,14 @@ public:
//! todo: add something to note what created it (user, getnewaddress, change)
//! maybe should have a map<string, string> property map
- CWalletKey(int64_t nExpires=0);
+ explicit CWalletKey(int64_t nExpires=0);
ADD_SERIALIZE_METHODS;
template <typename Stream, typename Operation>
- inline void SerializationOp(Stream& s, Operation ser_action, int nType, int nVersion) {
- if (!(nType & SER_GETHASH))
+ inline void SerializationOp(Stream& s, Operation ser_action) {
+ int nVersion = s.GetVersion();
+ if (!(s.GetType() & SER_GETHASH))
READWRITE(nVersion);
READWRITE(vchPrivKey);
READWRITE(nTimeCreated);
@@ -456,7 +563,7 @@ public:
};
/**
- * Internal transfers.
+ * DEPRECATED Internal transfers.
* Database key is acentry<account><counter>.
*/
class CAccountingEntry
@@ -487,47 +594,49 @@ public:
nEntryNo = 0;
}
- ADD_SERIALIZE_METHODS;
-
- template <typename Stream, typename Operation>
- inline void SerializationOp(Stream& s, Operation ser_action, int nType, int nVersion) {
- if (!(nType & SER_GETHASH))
- READWRITE(nVersion);
+ template <typename Stream>
+ void Serialize(Stream& s) const {
+ int nVersion = s.GetVersion();
+ if (!(s.GetType() & SER_GETHASH)) {
+ s << nVersion;
+ }
//! Note: strAccount is serialized as part of the key, not here.
- READWRITE(nCreditDebit);
- READWRITE(nTime);
- READWRITE(LIMITED_STRING(strOtherAccount, 65536));
-
- if (!ser_action.ForRead())
- {
- WriteOrderPos(nOrderPos, mapValue);
-
- if (!(mapValue.empty() && _ssExtra.empty()))
- {
- CDataStream ss(nType, nVersion);
- ss.insert(ss.begin(), '\0');
- ss << mapValue;
- ss.insert(ss.end(), _ssExtra.begin(), _ssExtra.end());
- strComment.append(ss.str());
- }
+ s << nCreditDebit << nTime << strOtherAccount;
+
+ mapValue_t mapValueCopy = mapValue;
+ WriteOrderPos(nOrderPos, mapValueCopy);
+
+ std::string strCommentCopy = strComment;
+ if (!mapValueCopy.empty() || !_ssExtra.empty()) {
+ CDataStream ss(s.GetType(), s.GetVersion());
+ ss.insert(ss.begin(), '\0');
+ ss << mapValueCopy;
+ ss.insert(ss.end(), _ssExtra.begin(), _ssExtra.end());
+ strCommentCopy.append(ss.str());
}
+ s << strCommentCopy;
+ }
- READWRITE(LIMITED_STRING(strComment, 65536));
+ template <typename Stream>
+ void Unserialize(Stream& s) {
+ int nVersion = s.GetVersion();
+ if (!(s.GetType() & SER_GETHASH)) {
+ s >> nVersion;
+ }
+ //! Note: strAccount is serialized as part of the key, not here.
+ s >> nCreditDebit >> nTime >> LIMITED_STRING(strOtherAccount, 65536) >> LIMITED_STRING(strComment, 65536);
size_t nSepPos = strComment.find("\0", 0, 1);
- if (ser_action.ForRead())
- {
- mapValue.clear();
- if (std::string::npos != nSepPos)
- {
- CDataStream ss(std::vector<char>(strComment.begin() + nSepPos + 1, strComment.end()), nType, nVersion);
- ss >> mapValue;
- _ssExtra = std::vector<char>(ss.begin(), ss.end());
- }
- ReadOrderPos(nOrderPos, mapValue);
+ mapValue.clear();
+ if (std::string::npos != nSepPos) {
+ CDataStream ss(std::vector<char>(strComment.begin() + nSepPos + 1, strComment.end()), s.GetType(), s.GetVersion());
+ ss >> mapValue;
+ _ssExtra = std::vector<char>(ss.begin(), ss.end());
}
- if (std::string::npos != nSepPos)
+ ReadOrderPos(nOrderPos, mapValue);
+ if (std::string::npos != nSepPos) {
strComment.erase(nSepPos);
+ }
mapValue.erase("n");
}
@@ -536,32 +645,42 @@ private:
std::vector<char> _ssExtra;
};
+struct CoinSelectionParams
+{
+ bool use_bnb = true;
+ size_t change_output_size = 0;
+ size_t change_spend_size = 0;
+ CFeeRate effective_fee = CFeeRate(0);
+ size_t tx_noinputs_size = 0;
+
+ CoinSelectionParams(bool use_bnb, size_t change_output_size, size_t change_spend_size, CFeeRate effective_fee, size_t tx_noinputs_size) : use_bnb(use_bnb), change_output_size(change_output_size), change_spend_size(change_spend_size), effective_fee(effective_fee), tx_noinputs_size(tx_noinputs_size) {}
+ CoinSelectionParams() {}
+};
-/**
+class WalletRescanReserver; //forward declarations for ScanForWalletTransactions/RescanFromTime
+/**
* A CWallet is an extension of a keystore, which also maintains a set of transactions and balances,
* and provides the ability to create new transactions.
*/
-class CWallet : public CCryptoKeyStore, public CValidationInterface
+class CWallet final : public CCryptoKeyStore, public CValidationInterface
{
private:
- /**
- * Select a set of coins such that nValueRet >= nTargetValue and at least
- * all coins from coinControl are selected; Never select unconfirmed coins
- * if they are not ours
- */
- bool SelectCoins(const std::vector<COutput>& vAvailableCoins, const CAmount& nTargetValue, std::set<std::pair<const CWalletTx*,unsigned int> >& setCoinsRet, CAmount& nValueRet, const CCoinControl *coinControl = NULL) const;
+ std::atomic<bool> fAbortRescan{false};
+ std::atomic<bool> fScanningWallet{false}; // controlled by WalletRescanReserver
+ std::mutex mutexScanning;
+ friend class WalletRescanReserver;
- CWalletDB *pwalletdbEncryption;
+ WalletBatch *encrypted_batch = nullptr;
//! the current wallet version: clients below this version are not able to load the wallet
- int nWalletVersion;
+ int nWalletVersion = FEATURE_BASE;
//! the maximum wallet format version: memory-only variable that specifies to what version this wallet may be upgraded
- int nWalletMaxVersion;
+ int nWalletMaxVersion = FEATURE_BASE;
- int64_t nNextResend;
- int64_t nLastResend;
- bool fBroadcastTransactions;
+ int64_t nNextResend = 0;
+ int64_t nLastResend = 0;
+ bool fBroadcastTransactions = false;
/**
* Used to keep track of spent outpoints, and
@@ -573,78 +692,130 @@ private:
void AddToSpends(const COutPoint& outpoint, const uint256& wtxid);
void AddToSpends(const uint256& wtxid);
+ /**
+ * Add a transaction to the wallet, or update it. pIndex and posInBlock should
+ * be set when the transaction was known to be included in a block. When
+ * pIndex == nullptr, then wallet state is not updated in AddToWallet, but
+ * notifications happen and cached balances are marked dirty.
+ *
+ * If fUpdate is true, existing transactions will be updated.
+ * TODO: One exception to this is that the abandoned state is cleared under the
+ * assumption that any further notification of a transaction that was considered
+ * abandoned is an indication that it is not safe to be considered abandoned.
+ * Abandoned state should probably be more carefully tracked via different
+ * posInBlock signals or by checking mempool presence when necessary.
+ */
+ bool AddToWalletIfInvolvingMe(const CTransactionRef& tx, const CBlockIndex* pIndex, int posInBlock, bool fUpdate) EXCLUSIVE_LOCKS_REQUIRED(cs_wallet);
+
/* Mark a transaction (and its in-wallet descendants) as conflicting with a particular block. */
void MarkConflicted(const uint256& hashBlock, const uint256& hashTx);
+ /* Mark a transaction's inputs dirty, thus forcing the outputs to be recomputed */
+ void MarkInputsDirty(const CTransactionRef& tx);
+
void SyncMetaData(std::pair<TxSpends::iterator, TxSpends::iterator>);
+ /* Used by TransactionAddedToMemorypool/BlockConnected/Disconnected/ScanForWalletTransactions.
+ * Should be called with pindexBlock and posInBlock if this is for a transaction that is included in a block. */
+ void SyncTransaction(const CTransactionRef& tx, const CBlockIndex *pindex = nullptr, int posInBlock = 0, bool update_tx = true) EXCLUSIVE_LOCKS_REQUIRED(cs_wallet);
+
/* the HD chain data model (external chain counters) */
CHDChain hdChain;
- bool fFileBacked;
+ /* HD derive new child key (on internal or external chain) */
+ void DeriveNewChildKey(WalletBatch &batch, CKeyMetadata& metadata, CKey& secret, bool internal = false) EXCLUSIVE_LOCKS_REQUIRED(cs_wallet);
+
+ std::set<int64_t> setInternalKeyPool;
+ std::set<int64_t> setExternalKeyPool;
+ std::set<int64_t> set_pre_split_keypool;
+ int64_t m_max_keypool_index = 0;
+ std::map<CKeyID, int64_t> m_pool_key_to_index;
+ std::atomic<uint64_t> m_wallet_flags{0};
+
+ int64_t nTimeFirstKey = 0;
+
+ /**
+ * Private version of AddWatchOnly method which does not accept a
+ * timestamp, and which will reset the wallet's nTimeFirstKey value to 1 if
+ * the watch key did not previously have a timestamp associated with it.
+ * Because this is an inherited virtual method, it is accessible despite
+ * being marked private, but it is marked private anyway to encourage use
+ * of the other AddWatchOnly which accepts a timestamp and sets
+ * nTimeFirstKey more intelligently for more efficient rescans.
+ */
+ bool AddWatchOnly(const CScript& dest) override EXCLUSIVE_LOCKS_REQUIRED(cs_wallet);
+
+ /**
+ * Wallet filename from wallet=<path> command line or config option.
+ * Used in debug logs and to send RPCs to the right wallet instance when
+ * more than one wallet is loaded.
+ */
+ std::string m_name;
+
+ /** Internal database handle. */
+ std::unique_ptr<WalletDatabase> database;
+
+ /**
+ * The following is used to keep track of how far behind the wallet is
+ * from the chain sync, and to allow clients to block on us being caught up.
+ *
+ * Note that this is *not* how far we've processed, we may need some rescan
+ * to have seen all transactions in the chain, but is only used to track
+ * live BlockConnected callbacks.
+ *
+ * Protected by cs_main (see BlockUntilSyncedToCurrentChain)
+ */
+ const CBlockIndex* m_last_block_processed = nullptr;
- std::set<int64_t> setKeyPool;
public:
/*
* Main wallet lock.
- * This lock protects all the fields added by CWallet
- * except for:
- * fFileBacked (immutable after instantiation)
- * strWalletFile (immutable after instantiation)
+ * This lock protects all the fields added by CWallet.
*/
mutable CCriticalSection cs_wallet;
- std::string strWalletFile;
-
- void LoadKeyPool(int nIndex, const CKeyPool &keypool)
+ /** Get database handle used by this wallet. Ideally this function would
+ * not be necessary.
+ */
+ WalletDatabase& GetDBHandle()
{
- setKeyPool.insert(nIndex);
-
- // If no metadata exists yet, create a default with the pool key's
- // creation time. Note that this may be overwritten by actually
- // stored metadata for that key later, which is fine.
- CKeyID keyid = keypool.vchPubKey.GetID();
- if (mapKeyMetadata.count(keyid) == 0)
- mapKeyMetadata[keyid] = CKeyMetadata(keypool.nTime);
+ return *database;
}
+ /**
+ * Select a set of coins such that nValueRet >= nTargetValue and at least
+ * all coins from coinControl are selected; Never select unconfirmed coins
+ * if they are not ours
+ */
+ bool SelectCoins(const std::vector<COutput>& vAvailableCoins, const CAmount& nTargetValue, std::set<CInputCoin>& setCoinsRet, CAmount& nValueRet,
+ const CCoinControl& coin_control, CoinSelectionParams& coin_selection_params, bool& bnb_used) const;
+
+ /** Get a name for this wallet for logging/debugging purposes.
+ */
+ const std::string& GetName() const { return m_name; }
+
+ void LoadKeyPool(int64_t nIndex, const CKeyPool &keypool) EXCLUSIVE_LOCKS_REQUIRED(cs_wallet);
+ void MarkPreSplitKeys();
+
+ // Map from Key ID to key metadata.
std::map<CKeyID, CKeyMetadata> mapKeyMetadata;
+ // Map from Script ID to key metadata (for watch-only keys).
+ std::map<CScriptID, CKeyMetadata> m_script_metadata;
+
typedef std::map<unsigned int, CMasterKey> MasterKeyMap;
MasterKeyMap mapMasterKeys;
- unsigned int nMasterKeyMaxID;
+ unsigned int nMasterKeyMaxID = 0;
- CWallet()
+ /** Construct wallet with specified name and database implementation. */
+ CWallet(std::string name, std::unique_ptr<WalletDatabase> database) : m_name(std::move(name)), database(std::move(database))
{
- SetNull();
- }
-
- CWallet(const std::string& strWalletFileIn)
- {
- SetNull();
-
- strWalletFile = strWalletFileIn;
- fFileBacked = true;
}
~CWallet()
{
- delete pwalletdbEncryption;
- pwalletdbEncryption = NULL;
- }
-
- void SetNull()
- {
- nWalletVersion = FEATURE_BASE;
- nWalletMaxVersion = FEATURE_BASE;
- fFileBacked = false;
- nMasterKeyMaxID = 0;
- pwalletdbEncryption = NULL;
- nOrderPosNext = 0;
- nNextResend = 0;
- nLastResend = 0;
- nTimeFirstKey = 0;
- fBroadcastTransactions = false;
+ delete encrypted_batch;
+ encrypted_batch = nullptr;
}
std::map<uint256, CWalletTx> mapWallet;
@@ -654,26 +825,32 @@ public:
typedef std::multimap<int64_t, TxPair > TxItems;
TxItems wtxOrdered;
- int64_t nOrderPosNext;
- std::map<uint256, int> mapRequestCount;
+ int64_t nOrderPosNext = 0;
+ uint64_t nAccountingEntryNumber = 0;
std::map<CTxDestination, CAddressBookData> mapAddressBook;
- CPubKey vchDefaultKey;
-
std::set<COutPoint> setLockedCoins;
- int64_t nTimeFirstKey;
-
const CWalletTx* GetWalletTx(const uint256& hash) const;
//! check whether we are allowed to upgrade (or already support) to the named feature
- bool CanSupportFeature(enum WalletFeature wf) { AssertLockHeld(cs_wallet); return nWalletMaxVersion >= wf; }
+ bool CanSupportFeature(enum WalletFeature wf) const EXCLUSIVE_LOCKS_REQUIRED(cs_wallet) { AssertLockHeld(cs_wallet); return nWalletMaxVersion >= wf; }
/**
* populate vCoins with vector of available COutputs.
*/
- void AvailableCoins(std::vector<COutput>& vCoins, bool fOnlyConfirmed=true, const CCoinControl *coinControl = NULL, bool fIncludeZeroValue=false) const;
+ void AvailableCoins(std::vector<COutput>& vCoins, bool fOnlySafe=true, const CCoinControl *coinControl = nullptr, 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 EXCLUSIVE_LOCKS_REQUIRED(cs_wallet);
+
+ /**
+ * 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
@@ -681,35 +858,47 @@ public:
* completion the coin set and corresponding actual target value is
* assembled
*/
- bool SelectCoinsMinConf(const CAmount& nTargetValue, int nConfMine, int nConfTheirs, std::vector<COutput> vCoins, std::set<std::pair<const CWalletTx*,unsigned int> >& setCoinsRet, CAmount& nValueRet) const;
+ bool SelectCoinsMinConf(const CAmount& nTargetValue, const CoinEligibilityFilter& eligibility_filter, std::vector<OutputGroup> groups,
+ std::set<CInputCoin>& setCoinsRet, CAmount& nValueRet, const CoinSelectionParams& coin_selection_params, bool& bnb_used) const;
bool IsSpent(const uint256& hash, unsigned int n) const;
+ std::vector<OutputGroup> GroupOutputs(const std::vector<COutput>& outputs, bool single_coin) const;
- bool IsLockedCoin(uint256 hash, unsigned int n) const;
- void LockCoin(const COutPoint& output);
- void UnlockCoin(const COutPoint& output);
- void UnlockAllCoins();
- void ListLockedCoins(std::vector<COutPoint>& vOutpts);
+ bool IsLockedCoin(uint256 hash, unsigned int n) const EXCLUSIVE_LOCKS_REQUIRED(cs_wallet);
+ void LockCoin(const COutPoint& output) EXCLUSIVE_LOCKS_REQUIRED(cs_wallet);
+ void UnlockCoin(const COutPoint& output) EXCLUSIVE_LOCKS_REQUIRED(cs_wallet);
+ void UnlockAllCoins() EXCLUSIVE_LOCKS_REQUIRED(cs_wallet);
+ void ListLockedCoins(std::vector<COutPoint>& vOutpts) const EXCLUSIVE_LOCKS_REQUIRED(cs_wallet);
+
+ /*
+ * Rescan abort properties
+ */
+ void AbortRescan() { fAbortRescan = true; }
+ bool IsAbortingRescan() { return fAbortRescan; }
+ bool IsScanning() { return fScanningWallet; }
/**
* keystore implementation
* Generate a new key
*/
- CPubKey GenerateNewKey();
+ CPubKey GenerateNewKey(WalletBatch& batch, bool internal = false) EXCLUSIVE_LOCKS_REQUIRED(cs_wallet);
//! Adds a key to the store, and saves it to disk.
- bool AddKeyPubKey(const CKey& key, const CPubKey &pubkey);
+ bool AddKeyPubKey(const CKey& key, const CPubKey &pubkey) override EXCLUSIVE_LOCKS_REQUIRED(cs_wallet);
+ bool AddKeyPubKeyWithDB(WalletBatch &batch,const CKey& key, const CPubKey &pubkey) EXCLUSIVE_LOCKS_REQUIRED(cs_wallet);
//! Adds a key to the store, without saving it to disk (used by LoadWallet)
bool LoadKey(const CKey& key, const CPubKey &pubkey) { return CCryptoKeyStore::AddKeyPubKey(key, pubkey); }
//! Load metadata (used by LoadWallet)
- bool LoadKeyMetadata(const CPubKey &pubkey, const CKeyMetadata &metadata);
+ void LoadKeyMetadata(const CKeyID& keyID, const CKeyMetadata &metadata) EXCLUSIVE_LOCKS_REQUIRED(cs_wallet);
+ void LoadScriptMetadata(const CScriptID& script_id, const CKeyMetadata &metadata) EXCLUSIVE_LOCKS_REQUIRED(cs_wallet);
- bool LoadMinVersion(int nVersion) { AssertLockHeld(cs_wallet); nWalletVersion = nVersion; nWalletMaxVersion = std::max(nWalletMaxVersion, nVersion); return true; }
+ bool LoadMinVersion(int nVersion) EXCLUSIVE_LOCKS_REQUIRED(cs_wallet) { AssertLockHeld(cs_wallet); nWalletVersion = nVersion; nWalletMaxVersion = std::max(nWalletMaxVersion, nVersion); return true; }
+ void UpdateTimeFirstKey(int64_t nCreateTime) EXCLUSIVE_LOCKS_REQUIRED(cs_wallet);
//! Adds an encrypted key to the store, and saves it to disk.
- bool AddCryptedKey(const CPubKey &vchPubKey, const std::vector<unsigned char> &vchCryptedSecret);
+ bool AddCryptedKey(const CPubKey &vchPubKey, const std::vector<unsigned char> &vchCryptedSecret) override;
//! Adds an encrypted key to the store, without saving it to disk (used by LoadWallet)
bool LoadCryptedKey(const CPubKey &vchPubKey, const std::vector<unsigned char> &vchCryptedSecret);
- bool AddCScript(const CScript& redeemScript);
+ bool AddCScript(const CScript& redeemScript) override;
bool LoadCScript(const CScript& redeemScript);
//! Adds a destination data tuple to the store, and saves it to disk
@@ -717,93 +906,144 @@ public:
//! Erases a destination data tuple in the store and on disk
bool EraseDestData(const CTxDestination &dest, const std::string &key);
//! Adds a destination data tuple to the store, without saving it to disk
- bool LoadDestData(const CTxDestination &dest, const std::string &key, const std::string &value);
+ void 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);
- bool RemoveWatchOnly(const CScript &dest);
+ bool AddWatchOnly(const CScript& dest, int64_t nCreateTime) EXCLUSIVE_LOCKS_REQUIRED(cs_wallet);
+ bool RemoveWatchOnly(const CScript &dest) override EXCLUSIVE_LOCKS_REQUIRED(cs_wallet);
//! Adds a watch-only address to the store, without saving it to disk (used by LoadWallet)
bool LoadWatchOnly(const CScript &dest);
+ //! Holds a timestamp at which point the wallet is scheduled (externally) to be relocked. Caller must arrange for actual relocking to occur via Lock().
+ int64_t nRelockTime = 0;
+
bool Unlock(const SecureString& strWalletPassphrase);
bool ChangeWalletPassphrase(const SecureString& strOldWalletPassphrase, const SecureString& strNewWalletPassphrase);
bool EncryptWallet(const SecureString& strWalletPassphrase);
- void GetKeyBirthTimes(std::map<CKeyID, int64_t> &mapKeyBirth) const;
+ void GetKeyBirthTimes(std::map<CTxDestination, int64_t> &mapKeyBirth) const EXCLUSIVE_LOCKS_REQUIRED(cs_wallet);
+ unsigned int ComputeTimeSmart(const CWalletTx& wtx) const;
- /**
+ /**
* Increment the next transaction order id
* @return next transaction order id
*/
- int64_t IncOrderPosNext(CWalletDB *pwalletdb = NULL);
- bool AccountMove(std::string strFrom, std::string strTo, CAmount nAmount, std::string strComment = "");
- bool GetAccountPubkey(CPubKey &pubKey, std::string strAccount, bool bForceNew = false);
+ int64_t IncOrderPosNext(WalletBatch *batch = nullptr) EXCLUSIVE_LOCKS_REQUIRED(cs_wallet);
+ DBErrors ReorderTransactions();
+ bool AccountMove(std::string strFrom, std::string strTo, CAmount nAmount, std::string strComment = "") EXCLUSIVE_LOCKS_REQUIRED(cs_wallet);
+ bool GetLabelDestination(CTxDestination &dest, const std::string& label, bool bForceNew = false);
void MarkDirty();
bool AddToWallet(const CWalletTx& wtxIn, bool fFlushOnClose=true);
- bool LoadToWallet(const CWalletTx& wtxIn);
- void SyncTransaction(const CTransaction& tx, const CBlockIndex *pindex, int posInBlock);
- bool AddToWalletIfInvolvingMe(const CTransaction& tx, const CBlockIndex* pIndex, int posInBlock, bool fUpdate);
- int ScanForWalletTransactions(CBlockIndex* pindexStart, bool fUpdate = false);
+ void LoadToWallet(const CWalletTx& wtxIn);
+ void TransactionAddedToMempool(const CTransactionRef& tx) override;
+ void BlockConnected(const std::shared_ptr<const CBlock>& pblock, const CBlockIndex *pindex, const std::vector<CTransactionRef>& vtxConflicted) override;
+ void BlockDisconnected(const std::shared_ptr<const CBlock>& pblock) override;
+ int64_t RescanFromTime(int64_t startTime, const WalletRescanReserver& reserver, bool update);
+ CBlockIndex* ScanForWalletTransactions(CBlockIndex* pindexStart, CBlockIndex* pindexStop, const WalletRescanReserver& reserver, bool fUpdate = false);
+ void TransactionRemovedFromMempool(const CTransactionRef &ptx) override;
void ReacceptWalletTransactions();
- void ResendWalletTransactions(int64_t nBestBlockTime);
- std::vector<uint256> ResendWalletTransactionsBefore(int64_t nTime);
- CAmount GetBalance() const;
+ void ResendWalletTransactions(int64_t nBestBlockTime, CConnman* connman) override;
+ // ResendWalletTransactionsBefore may only be called if fBroadcastTransactions!
+ std::vector<uint256> ResendWalletTransactionsBefore(int64_t nTime, CConnman* connman);
+ CAmount GetBalance(const isminefilter& filter=ISMINE_SPENDABLE, const int min_depth=0) const;
CAmount GetUnconfirmedBalance() const;
CAmount GetImmatureBalance() const;
- CAmount GetWatchOnlyBalance() const;
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;
+
+ OutputType TransactionChangeType(OutputType change_type, const std::vector<CRecipient>& vecSend);
/**
* Insert additional inputs into the transaction by
* calling CreateTransaction();
*/
- bool FundTransaction(CMutableTransaction& tx, CAmount& nFeeRet, bool overrideEstimatedFeeRate, const CFeeRate& specificFeeRate, int& nChangePosInOut, std::string& strFailReason, bool includeWatching, bool lockUnspents, const CTxDestination& destChange = CNoDestination());
+ bool FundTransaction(CMutableTransaction& tx, CAmount& nFeeRet, int& nChangePosInOut, std::string& strFailReason, bool lockUnspents, const std::set<int>& setSubtractFeeFromOutputs, CCoinControl);
+ bool SignTransaction(CMutableTransaction& tx) EXCLUSIVE_LOCKS_REQUIRED(cs_wallet);
/**
* Create a new transaction paying the recipients with a set of coins
* selected by SelectCoins(); Also create the change output, when needed
* @note passing nChangePosInOut as -1 will result in setting a random position
*/
- bool CreateTransaction(const std::vector<CRecipient>& vecSend, CWalletTx& wtxNew, CReserveKey& reservekey, CAmount& nFeeRet, int& nChangePosInOut,
- std::string& strFailReason, const CCoinControl *coinControl = NULL, bool sign = true);
- bool CommitTransaction(CWalletTx& wtxNew, CReserveKey& reservekey);
-
- bool AddAccountingEntry(const CAccountingEntry&, CWalletDB & pwalletdb);
-
- static CFeeRate minTxFee;
- static CFeeRate fallbackFee;
- /**
- * Estimate the minimum fee considering user set parameters
- * and the required fee
- */
- static CAmount GetMinimumFee(unsigned int nTxBytes, unsigned int nConfirmTarget, const CTxMemPool& pool);
+ bool CreateTransaction(const std::vector<CRecipient>& vecSend, CTransactionRef& tx, CReserveKey& reservekey, CAmount& nFeeRet, int& nChangePosInOut,
+ std::string& strFailReason, const CCoinControl& coin_control, bool sign = true);
+ bool CommitTransaction(CTransactionRef tx, mapValue_t mapValue, std::vector<std::pair<std::string, std::string>> orderForm, std::string fromAccount, CReserveKey& reservekey, CConnman* connman, CValidationState& state);
+
+ void ListAccountCreditDebit(const std::string& strAccount, std::list<CAccountingEntry>& entries);
+ bool AddAccountingEntry(const CAccountingEntry&);
+ bool AddAccountingEntry(const CAccountingEntry&, WalletBatch *batch);
+ bool DummySignTx(CMutableTransaction &txNew, const std::set<CTxOut> &txouts, bool use_max_sig = false) const
+ {
+ std::vector<CTxOut> v_txouts(txouts.size());
+ std::copy(txouts.begin(), txouts.end(), v_txouts.begin());
+ return DummySignTx(txNew, v_txouts, use_max_sig);
+ }
+ bool DummySignTx(CMutableTransaction &txNew, const std::vector<CTxOut> &txouts, bool use_max_sig = false) const;
+ bool DummySignInput(CTxIn &tx_in, const CTxOut &txout, bool use_max_sig = false) const;
+
+ CFeeRate m_pay_tx_fee{DEFAULT_PAY_TX_FEE};
+ unsigned int m_confirm_target{DEFAULT_TX_CONFIRM_TARGET};
+ bool m_spend_zero_conf_change{DEFAULT_SPEND_ZEROCONF_CHANGE};
+ bool m_signal_rbf{DEFAULT_WALLET_RBF};
+ bool m_allow_fallback_fee{true}; //<! will be defined via chainparams
+ CFeeRate m_min_fee{DEFAULT_TRANSACTION_MINFEE}; //!< Override with -mintxfee
/**
- * Return the minimum required fee taking into account the
- * floating relay fee and user set minimum transaction fee
+ * If fee estimation does not have enough data to provide estimates, use this fee instead.
+ * Has no effect if not using fee estimation
+ * Override with -fallbackfee
*/
- static CAmount GetRequiredFee(unsigned int nTxBytes);
+ CFeeRate m_fallback_fee{DEFAULT_FALLBACK_FEE};
+ CFeeRate m_discard_rate{DEFAULT_DISCARD_FEE};
+ OutputType m_default_address_type{DEFAULT_ADDRESS_TYPE};
+ OutputType m_default_change_type{DEFAULT_CHANGE_TYPE};
bool NewKeyPool();
+ size_t KeypoolCountExternalKeys() EXCLUSIVE_LOCKS_REQUIRED(cs_wallet);
bool TopUpKeyPool(unsigned int kpSize = 0);
- void ReserveKeyFromKeyPool(int64_t& nIndex, CKeyPool& keypool);
+
+ /**
+ * Reserves a key from the keypool and sets nIndex to its index
+ *
+ * @param[out] nIndex the index of the key in keypool
+ * @param[out] keypool the keypool the key was drawn from, which could be the
+ * the pre-split pool if present, or the internal or external pool
+ * @param fRequestedInternal true if the caller would like the key drawn
+ * from the internal keypool, false if external is preferred
+ *
+ * @return true if succeeded, false if failed due to empty keypool
+ * @throws std::runtime_error if keypool read failed, key was invalid,
+ * was not found in the wallet, or was misclassified in the internal
+ * or external keypool
+ */
+ bool ReserveKeyFromKeyPool(int64_t& nIndex, CKeyPool& keypool, bool fRequestedInternal);
void KeepKey(int64_t nIndex);
- void ReturnKey(int64_t nIndex);
- bool GetKeyFromPool(CPubKey &key);
+ void ReturnKey(int64_t nIndex, bool fInternal, const CPubKey& pubkey);
+ bool GetKeyFromPool(CPubKey &key, bool internal = false);
int64_t GetOldestKeyPoolTime();
- void GetAllReserveKeys(std::set<CKeyID>& setAddress) const;
+ /**
+ * Marks all keys in the keypool up to and including reserve_key as used.
+ */
+ void MarkReserveKeysAsUsed(int64_t keypool_id) EXCLUSIVE_LOCKS_REQUIRED(cs_wallet);
+ const std::map<CKeyID, int64_t>& GetAllReserveKeys() const { return m_pool_key_to_index; }
- std::set< std::set<CTxDestination> > GetAddressGroupings();
+ std::set<std::set<CTxDestination>> GetAddressGroupings() EXCLUSIVE_LOCKS_REQUIRED(cs_wallet);
std::map<CTxDestination, CAmount> GetAddressBalances();
- CAmount GetAccountBalance(const std::string& strAccount, int nMinDepth, const isminefilter& filter);
- CAmount GetAccountBalance(CWalletDB& walletdb, const std::string& strAccount, int nMinDepth, const isminefilter& filter);
- std::set<CTxDestination> GetAccountAddresses(const std::string& strAccount) const;
+ std::set<CTxDestination> GetLabelAddresses(const std::string& label) const;
+ void DeleteLabel(const std::string& label);
isminetype IsMine(const CTxIn& txin) const;
+ /**
+ * Returns amount of debit if the input matches the
+ * filter, otherwise returns 0
+ */
CAmount GetDebit(const CTxIn& txin, const isminefilter& filter) const;
isminetype IsMine(const CTxOut& txout) const;
CAmount GetCredit(const CTxOut& txout, const isminefilter& filter) const;
@@ -813,47 +1053,32 @@ public:
/** should probably be renamed to IsRelevantToMe */
bool IsFromMe(const CTransaction& tx) const;
CAmount GetDebit(const CTransaction& tx, const isminefilter& filter) const;
+ /** Returns whether all of the inputs match the filter */
+ bool IsAllFromMe(const CTransaction& tx, const isminefilter& filter) const;
CAmount GetCredit(const CTransaction& tx, const isminefilter& filter) const;
CAmount GetChange(const CTransaction& tx) const;
- void SetBestChain(const CBlockLocator& loc);
+ void ChainStateFlushed(const CBlockLocator& loc) override;
DBErrors LoadWallet(bool& fFirstRunRet);
DBErrors ZapWalletTx(std::vector<CWalletTx>& vWtx);
- DBErrors ZapSelectTx(std::vector<uint256>& vHashIn, std::vector<uint256>& vHashOut);
+ DBErrors ZapSelectTx(std::vector<uint256>& vHashIn, std::vector<uint256>& vHashOut) EXCLUSIVE_LOCKS_REQUIRED(cs_wallet);
bool SetAddressBook(const CTxDestination& address, const std::string& strName, const std::string& purpose);
bool DelAddressBook(const CTxDestination& address);
- void UpdatedTransaction(const uint256 &hashTx);
+ const std::string& GetLabelName(const CScript& scriptPubKey) const;
- void Inventory(const uint256 &hash)
- {
- {
- LOCK(cs_wallet);
- std::map<uint256, int>::iterator mi = mapRequestCount.find(hash);
- if (mi != mapRequestCount.end())
- (*mi).second++;
- }
- }
+ void GetScriptForMining(std::shared_ptr<CReserveScript> &script);
- void GetScriptForMining(boost::shared_ptr<CReserveScript> &script);
- void ResetRequestCount(const uint256 &hash)
+ unsigned int GetKeyPoolSize() EXCLUSIVE_LOCKS_REQUIRED(cs_wallet)
{
- LOCK(cs_wallet);
- mapRequestCount[hash] = 0;
- };
-
- unsigned int GetKeyPoolSize()
- {
- AssertLockHeld(cs_wallet); // setKeyPool
- return setKeyPool.size();
+ AssertLockHeld(cs_wallet); // set{Ex,In}ternalKeyPool
+ return setInternalKeyPool.size() + setExternalKeyPool.size();
}
- bool SetDefaultKey(const CPubKey &vchPubKey);
-
//! signify that a particular wallet feature is now used. this may change nWalletVersion and nWalletMaxVersion if those are lower
- bool SetMinVersion(enum WalletFeature, CWalletDB* pwalletdbIn = NULL, bool fExplicit = false);
+ void SetMinVersion(enum WalletFeature, WalletBatch* batch_in = nullptr, bool fExplicit = false);
//! change which version we're allowed to upgrade to (note that this does not immediately imply upgrading to that format)
bool SetMaxVersion(int nVersion);
@@ -864,13 +1089,16 @@ public:
//! Get wallet transactions that conflict with given transaction (spend same outputs)
std::set<uint256> GetConflicts(const uint256& txid) const;
+ //! Check if a given transaction has any of its outputs spent by another transaction in the wallet
+ bool HasWalletSpend(const uint256& txid) const EXCLUSIVE_LOCKS_REQUIRED(cs_wallet);
+
//! Flush wallet (bitdb flush)
void Flush(bool shutdown=false);
- //! Verify the wallet database and perform salvage if required
- static bool Verify();
-
- /**
+ /** Wallet is about to be unloaded */
+ boost::signals2::signal<void ()> NotifyUnload;
+
+ /**
* Address book entry changed.
* @note called with lock cs_wallet held.
*/
@@ -879,7 +1107,7 @@ public:
const std::string &purpose,
ChangeType status)> NotifyAddressBookChanged;
- /**
+ /**
* Wallet transaction added, removed or updated.
* @note called with lock cs_wallet held.
*/
@@ -897,62 +1125,128 @@ 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);
- /* Returns the wallets help message */
- static std::string GetWalletHelpString(bool showDebug);
+ /** Mark a transaction as replaced by another transaction (e.g., BIP 125). */
+ bool MarkReplaced(const uint256& originalHash, const uint256& newHash);
+
+ //! Verify wallet naming and perform salvage on the wallet if required
+ static bool Verify(std::string wallet_file, bool salvage_wallet, std::string& error_string, std::string& warning_string);
/* Initializes the wallet, returns a new CWallet instance or a null pointer in case of an error */
- static bool InitLoadWallet();
+ static std::shared_ptr<CWallet> CreateWalletFromFile(const std::string& name, const fs::path& path, uint64_t wallet_creation_flags = 0);
- /* Wallets parameter interaction */
- static bool ParameterInteraction();
+ /**
+ * Wallet post-init setup
+ * Gives the wallet a chance to register repetitive tasks and complete post-init tasks
+ */
+ void postInitProcess();
bool BackupWallet(const std::string& strDest);
/* Set the HD chain model (chain child index counters) */
- bool SetHDChain(const CHDChain& chain, bool memonly);
- const CHDChain& GetHDChain() { return hdChain; }
+ void SetHDChain(const CHDChain& chain, bool memonly);
+ const CHDChain& GetHDChain() const { return hdChain; }
/* Returns true if HD is enabled */
- bool IsHDEnabled();
+ bool IsHDEnabled() const;
+
+ /* Generates a new HD seed (will not be activated) */
+ CPubKey GenerateNewSeed();
+
+ /* Derives a new HD seed (will not be activated) */
+ CPubKey DeriveNewSeed(const CKey& key);
+
+ /* Set the current HD seed (will reset the chain child index counters)
+ Sets the seed's version based on the current wallet version (so the
+ caller must ensure the current wallet version is correct before calling
+ this function). */
+ void SetHDSeed(const CPubKey& key);
+
+ /**
+ * Blocks until the wallet state is up-to-date to /at least/ the current
+ * chain at the time this function is entered
+ * Obviously holding cs_main/cs_wallet when going into this call may cause
+ * deadlock
+ */
+ void BlockUntilSyncedToCurrentChain() LOCKS_EXCLUDED(cs_wallet);
+
+ /**
+ * Explicitly make the wallet learn the related scripts for outputs to the
+ * given key. This is purely to make the wallet file compatible with older
+ * software, as CBasicKeyStore automatically does this implicitly for all
+ * keys now.
+ */
+ void LearnRelatedScripts(const CPubKey& key, OutputType);
+
+ /**
+ * Same as LearnRelatedScripts, but when the OutputType is not known (and could
+ * be anything).
+ */
+ void LearnAllRelatedScripts(const CPubKey& key);
+
+ /** set a single wallet flag */
+ void SetWalletFlag(uint64_t flags);
+
+ /** check if a certain wallet flag is set */
+ bool IsWalletFlagSet(uint64_t flag);
+
+ /** overwrite all flags by the given uint64_t
+ returns false if unknown, non-tolerable flags are present */
+ bool SetWalletFlags(uint64_t overwriteFlags, bool memOnly);
+
+ /** Returns a bracketed wallet name for displaying in logs, will return [default wallet] if the wallet has no name */
+ const std::string GetDisplayName() const {
+ std::string wallet_name = GetName().length() == 0 ? "default wallet" : GetName();
+ return strprintf("[%s]", wallet_name);
+ };
+
+ /** Prepends the wallet name in logging output to ease debugging in multi-wallet use cases */
+ template<typename... Params>
+ void WalletLogPrintf(std::string fmt, Params... parameters) const {
+ LogPrintf(("%s " + fmt).c_str(), GetDisplayName(), parameters...);
+ };
- /* Generates a new HD master key (will not be activated) */
- CPubKey GenerateNewHDMasterKey();
-
- /* Set the current HD master key (will reset the chain child index counters) */
- bool SetHDMasterKey(const CPubKey& key);
};
/** A key allocated from the key pool. */
-class CReserveKey : public CReserveScript
+class CReserveKey final : public CReserveScript
{
protected:
CWallet* pwallet;
int64_t nIndex;
CPubKey vchPubKey;
+ bool fInternal;
public:
- CReserveKey(CWallet* pwalletIn)
+ explicit CReserveKey(CWallet* pwalletIn)
{
nIndex = -1;
pwallet = pwalletIn;
+ fInternal = false;
}
+ CReserveKey() = default;
+ CReserveKey(const CReserveKey&) = delete;
+ CReserveKey& operator=(const CReserveKey&) = delete;
+
~CReserveKey()
{
ReturnKey();
}
void ReturnKey();
- bool GetReservedKey(CPubKey &pubkey);
+ bool GetReservedKey(CPubKey &pubkey, bool internal = false);
void KeepKey();
- void KeepScript() { KeepKey(); }
+ void KeepScript() override { KeepKey(); }
};
-/**
- * Account information.
+/**
+ * DEPRECATED Account information.
* Stored in wallet with key "acc"+string account name.
*/
class CAccount
@@ -973,11 +1267,53 @@ public:
ADD_SERIALIZE_METHODS;
template <typename Stream, typename Operation>
- inline void SerializationOp(Stream& s, Operation ser_action, int nType, int nVersion) {
- if (!(nType & SER_GETHASH))
+ inline void SerializationOp(Stream& s, Operation ser_action) {
+ int nVersion = s.GetVersion();
+ if (!(s.GetType() & SER_GETHASH))
READWRITE(nVersion);
READWRITE(vchPubKey);
}
};
+/** RAII object to check and reserve a wallet rescan */
+class WalletRescanReserver
+{
+private:
+ CWallet* m_wallet;
+ bool m_could_reserve;
+public:
+ explicit WalletRescanReserver(CWallet* w) : m_wallet(w), m_could_reserve(false) {}
+
+ bool reserve()
+ {
+ assert(!m_could_reserve);
+ std::lock_guard<std::mutex> lock(m_wallet->mutexScanning);
+ if (m_wallet->fScanningWallet) {
+ return false;
+ }
+ m_wallet->fScanningWallet = true;
+ m_could_reserve = true;
+ return true;
+ }
+
+ bool isReserved() const
+ {
+ return (m_could_reserve && m_wallet->fScanningWallet);
+ }
+
+ ~WalletRescanReserver()
+ {
+ std::lock_guard<std::mutex> lock(m_wallet->mutexScanning);
+ if (m_could_reserve) {
+ m_wallet->fScanningWallet = false;
+ }
+ }
+};
+
+// Calculate the size of the transaction assuming all signatures are max size
+// Use DummySignatureCreator, which inserts 71 byte signatures everywhere.
+// NOTE: this requires that all inputs must be in mapWallet (eg the tx should
+// be IsAllFromMe).
+int64_t CalculateMaximumSignedTxSize(const CTransaction &tx, const CWallet *wallet, bool use_max_sig = false);
+int64_t CalculateMaximumSignedTxSize(const CTransaction &tx, const CWallet *wallet, const std::vector<CTxOut>& txouts, bool use_max_sig = false);
#endif // BITCOIN_WALLET_WALLET_H
diff --git a/src/wallet/walletdb.cpp b/src/wallet/walletdb.cpp
index e6e9fc484b..1b787be198 100644
--- a/src/wallet/walletdb.cpp
+++ b/src/wallet/walletdb.cpp
@@ -1,79 +1,66 @@
// Copyright (c) 2009-2010 Satoshi Nakamoto
-// Copyright (c) 2009-2015 The Bitcoin Core developers
+// Copyright (c) 2009-2018 The Bitcoin Core developers
// Distributed under the MIT software license, see the accompanying
// file COPYING or http://www.opensource.org/licenses/mit-license.php.
-#include "wallet/walletdb.h"
-
-#include "base58.h"
-#include "consensus/validation.h"
-#include "main.h" // For CheckTransaction
-#include "protocol.h"
-#include "serialize.h"
-#include "sync.h"
-#include "util.h"
-#include "utiltime.h"
-#include "wallet/wallet.h"
-
-#include <boost/version.hpp>
-#include <boost/filesystem.hpp>
-#include <boost/foreach.hpp>
-#include <boost/scoped_ptr.hpp>
-#include <boost/thread.hpp>
+#include <wallet/walletdb.h>
+
+#include <consensus/tx_verify.h>
+#include <consensus/validation.h>
+#include <fs.h>
+#include <key_io.h>
+#include <protocol.h>
+#include <serialize.h>
+#include <sync.h>
+#include <util.h>
+#include <utiltime.h>
+#include <wallet/wallet.h>
-using namespace std;
+#include <atomic>
-static uint64_t nAccountingEntryNumber = 0;
+#include <boost/thread.hpp>
//
-// CWalletDB
+// WalletBatch
//
-bool CWalletDB::WriteName(const string& strAddress, const string& strName)
+bool WalletBatch::WriteName(const std::string& strAddress, const std::string& strName)
{
- nWalletDBUpdated++;
- return Write(make_pair(string("name"), strAddress), strName);
+ return WriteIC(std::make_pair(std::string("name"), strAddress), strName);
}
-bool CWalletDB::EraseName(const string& strAddress)
+bool WalletBatch::EraseName(const std::string& strAddress)
{
// This should only be used for sending addresses, never for receiving addresses,
// receiving addresses must always have an address book entry if they're not change return.
- nWalletDBUpdated++;
- return Erase(make_pair(string("name"), strAddress));
+ return EraseIC(std::make_pair(std::string("name"), strAddress));
}
-bool CWalletDB::WritePurpose(const string& strAddress, const string& strPurpose)
+bool WalletBatch::WritePurpose(const std::string& strAddress, const std::string& strPurpose)
{
- nWalletDBUpdated++;
- return Write(make_pair(string("purpose"), strAddress), strPurpose);
+ return WriteIC(std::make_pair(std::string("purpose"), strAddress), strPurpose);
}
-bool CWalletDB::ErasePurpose(const string& strPurpose)
+bool WalletBatch::ErasePurpose(const std::string& strAddress)
{
- nWalletDBUpdated++;
- return Erase(make_pair(string("purpose"), strPurpose));
+ return EraseIC(std::make_pair(std::string("purpose"), strAddress));
}
-bool CWalletDB::WriteTx(const CWalletTx& wtx)
+bool WalletBatch::WriteTx(const CWalletTx& wtx)
{
- nWalletDBUpdated++;
- return Write(std::make_pair(std::string("tx"), wtx.GetHash()), wtx);
+ return WriteIC(std::make_pair(std::string("tx"), wtx.GetHash()), wtx);
}
-bool CWalletDB::EraseTx(uint256 hash)
+bool WalletBatch::EraseTx(uint256 hash)
{
- nWalletDBUpdated++;
- return Erase(std::make_pair(std::string("tx"), hash));
+ return EraseIC(std::make_pair(std::string("tx"), hash));
}
-bool CWalletDB::WriteKey(const CPubKey& vchPubKey, const CPrivKey& vchPrivKey, const CKeyMetadata& keyMeta)
+bool WalletBatch::WriteKey(const CPubKey& vchPubKey, const CPrivKey& vchPrivKey, const CKeyMetadata& keyMeta)
{
- nWalletDBUpdated++;
-
- if (!Write(std::make_pair(std::string("keymeta"), vchPubKey),
- keyMeta, false))
+ if (!WriteIC(std::make_pair(std::string("keymeta"), vchPubKey), keyMeta, false)) {
return false;
+ }
// hash pubkey/privkey to accelerate wallet load
std::vector<unsigned char> vchKey;
@@ -81,161 +68,148 @@ bool CWalletDB::WriteKey(const CPubKey& vchPubKey, const CPrivKey& vchPrivKey, c
vchKey.insert(vchKey.end(), vchPubKey.begin(), vchPubKey.end());
vchKey.insert(vchKey.end(), vchPrivKey.begin(), vchPrivKey.end());
- return Write(std::make_pair(std::string("key"), vchPubKey), std::make_pair(vchPrivKey, Hash(vchKey.begin(), vchKey.end())), false);
+ return WriteIC(std::make_pair(std::string("key"), vchPubKey), std::make_pair(vchPrivKey, Hash(vchKey.begin(), vchKey.end())), false);
}
-bool CWalletDB::WriteCryptedKey(const CPubKey& vchPubKey,
+bool WalletBatch::WriteCryptedKey(const CPubKey& vchPubKey,
const std::vector<unsigned char>& vchCryptedSecret,
const CKeyMetadata &keyMeta)
{
- const bool fEraseUnencryptedKey = true;
- nWalletDBUpdated++;
-
- if (!Write(std::make_pair(std::string("keymeta"), vchPubKey),
- keyMeta))
+ if (!WriteIC(std::make_pair(std::string("keymeta"), vchPubKey), keyMeta)) {
return false;
+ }
- if (!Write(std::make_pair(std::string("ckey"), vchPubKey), vchCryptedSecret, false))
+ if (!WriteIC(std::make_pair(std::string("ckey"), vchPubKey), vchCryptedSecret, false)) {
return false;
- if (fEraseUnencryptedKey)
- {
- Erase(std::make_pair(std::string("key"), vchPubKey));
- Erase(std::make_pair(std::string("wkey"), vchPubKey));
}
+ EraseIC(std::make_pair(std::string("key"), vchPubKey));
+ EraseIC(std::make_pair(std::string("wkey"), vchPubKey));
return true;
}
-bool CWalletDB::WriteMasterKey(unsigned int nID, const CMasterKey& kMasterKey)
+bool WalletBatch::WriteMasterKey(unsigned int nID, const CMasterKey& kMasterKey)
{
- nWalletDBUpdated++;
- return Write(std::make_pair(std::string("mkey"), nID), kMasterKey, true);
+ return WriteIC(std::make_pair(std::string("mkey"), nID), kMasterKey, true);
}
-bool CWalletDB::WriteCScript(const uint160& hash, const CScript& redeemScript)
+bool WalletBatch::WriteCScript(const uint160& hash, const CScript& redeemScript)
{
- nWalletDBUpdated++;
- return Write(std::make_pair(std::string("cscript"), hash), *(const CScriptBase*)(&redeemScript), false);
+ return WriteIC(std::make_pair(std::string("cscript"), hash), redeemScript, false);
}
-bool CWalletDB::WriteWatchOnly(const CScript &dest)
+bool WalletBatch::WriteWatchOnly(const CScript &dest, const CKeyMetadata& keyMeta)
{
- nWalletDBUpdated++;
- return Write(std::make_pair(std::string("watchs"), *(const CScriptBase*)(&dest)), '1');
-}
-
-bool CWalletDB::EraseWatchOnly(const CScript &dest)
-{
- nWalletDBUpdated++;
- return Erase(std::make_pair(std::string("watchs"), *(const CScriptBase*)(&dest)));
+ if (!WriteIC(std::make_pair(std::string("watchmeta"), dest), keyMeta)) {
+ return false;
+ }
+ return WriteIC(std::make_pair(std::string("watchs"), dest), '1');
}
-bool CWalletDB::WriteBestBlock(const CBlockLocator& locator)
+bool WalletBatch::EraseWatchOnly(const CScript &dest)
{
- nWalletDBUpdated++;
- Write(std::string("bestblock"), CBlockLocator()); // Write empty block locator so versions that require a merkle branch automatically rescan
- return Write(std::string("bestblock_nomerkle"), locator);
+ if (!EraseIC(std::make_pair(std::string("watchmeta"), dest))) {
+ return false;
+ }
+ return EraseIC(std::make_pair(std::string("watchs"), dest));
}
-bool CWalletDB::ReadBestBlock(CBlockLocator& locator)
+bool WalletBatch::WriteBestBlock(const CBlockLocator& locator)
{
- if (Read(std::string("bestblock"), locator) && !locator.vHave.empty()) return true;
- return Read(std::string("bestblock_nomerkle"), locator);
+ WriteIC(std::string("bestblock"), CBlockLocator()); // Write empty block locator so versions that require a merkle branch automatically rescan
+ return WriteIC(std::string("bestblock_nomerkle"), locator);
}
-bool CWalletDB::WriteOrderPosNext(int64_t nOrderPosNext)
+bool WalletBatch::ReadBestBlock(CBlockLocator& locator)
{
- nWalletDBUpdated++;
- return Write(std::string("orderposnext"), nOrderPosNext);
+ if (m_batch.Read(std::string("bestblock"), locator) && !locator.vHave.empty()) return true;
+ return m_batch.Read(std::string("bestblock_nomerkle"), locator);
}
-bool CWalletDB::WriteDefaultKey(const CPubKey& vchPubKey)
+bool WalletBatch::WriteOrderPosNext(int64_t nOrderPosNext)
{
- nWalletDBUpdated++;
- return Write(std::string("defaultkey"), vchPubKey);
+ return WriteIC(std::string("orderposnext"), nOrderPosNext);
}
-bool CWalletDB::ReadPool(int64_t nPool, CKeyPool& keypool)
+bool WalletBatch::ReadPool(int64_t nPool, CKeyPool& keypool)
{
- return Read(std::make_pair(std::string("pool"), nPool), keypool);
+ return m_batch.Read(std::make_pair(std::string("pool"), nPool), keypool);
}
-bool CWalletDB::WritePool(int64_t nPool, const CKeyPool& keypool)
+bool WalletBatch::WritePool(int64_t nPool, const CKeyPool& keypool)
{
- nWalletDBUpdated++;
- return Write(std::make_pair(std::string("pool"), nPool), keypool);
+ return WriteIC(std::make_pair(std::string("pool"), nPool), keypool);
}
-bool CWalletDB::ErasePool(int64_t nPool)
+bool WalletBatch::ErasePool(int64_t nPool)
{
- nWalletDBUpdated++;
- return Erase(std::make_pair(std::string("pool"), nPool));
+ return EraseIC(std::make_pair(std::string("pool"), nPool));
}
-bool CWalletDB::WriteMinVersion(int nVersion)
+bool WalletBatch::WriteMinVersion(int nVersion)
{
- return Write(std::string("minversion"), nVersion);
+ return WriteIC(std::string("minversion"), nVersion);
}
-bool CWalletDB::ReadAccount(const string& strAccount, CAccount& account)
+bool WalletBatch::ReadAccount(const std::string& strAccount, CAccount& account)
{
account.SetNull();
- return Read(make_pair(string("acc"), strAccount), account);
+ return m_batch.Read(std::make_pair(std::string("acc"), strAccount), account);
}
-bool CWalletDB::WriteAccount(const string& strAccount, const CAccount& account)
+bool WalletBatch::WriteAccount(const std::string& strAccount, const CAccount& account)
{
- return Write(make_pair(string("acc"), strAccount), account);
+ return WriteIC(std::make_pair(std::string("acc"), strAccount), account);
}
-bool CWalletDB::WriteAccountingEntry(const uint64_t nAccEntryNum, const CAccountingEntry& acentry)
+bool WalletBatch::EraseAccount(const std::string& strAccount)
{
- return Write(std::make_pair(std::string("acentry"), std::make_pair(acentry.strAccount, nAccEntryNum)), acentry);
+ return EraseIC(std::make_pair(std::string("acc"), strAccount));
}
-bool CWalletDB::WriteAccountingEntry_Backend(const CAccountingEntry& acentry)
+bool WalletBatch::WriteAccountingEntry(const uint64_t nAccEntryNum, const CAccountingEntry& acentry)
{
- return WriteAccountingEntry(++nAccountingEntryNumber, acentry);
+ return WriteIC(std::make_pair(std::string("acentry"), std::make_pair(acentry.strAccount, nAccEntryNum)), acentry);
}
-CAmount CWalletDB::GetAccountCreditDebit(const string& strAccount)
+CAmount WalletBatch::GetAccountCreditDebit(const std::string& strAccount)
{
- list<CAccountingEntry> entries;
+ std::list<CAccountingEntry> entries;
ListAccountCreditDebit(strAccount, entries);
CAmount nCreditDebit = 0;
- BOOST_FOREACH (const CAccountingEntry& entry, entries)
+ for (const CAccountingEntry& entry : entries)
nCreditDebit += entry.nCreditDebit;
return nCreditDebit;
}
-void CWalletDB::ListAccountCreditDebit(const string& strAccount, list<CAccountingEntry>& entries)
+void WalletBatch::ListAccountCreditDebit(const std::string& strAccount, std::list<CAccountingEntry>& entries)
{
bool fAllAccounts = (strAccount == "*");
- Dbc* pcursor = GetCursor();
+ Dbc* pcursor = m_batch.GetCursor();
if (!pcursor)
- throw runtime_error(std::string(__func__) + ": cannot create DB cursor");
- unsigned int fFlags = DB_SET_RANGE;
+ throw std::runtime_error(std::string(__func__) + ": cannot create DB cursor");
+ bool setRange = true;
while (true)
{
// Read next record
CDataStream ssKey(SER_DISK, CLIENT_VERSION);
- if (fFlags == DB_SET_RANGE)
- ssKey << std::make_pair(std::string("acentry"), std::make_pair((fAllAccounts ? string("") : strAccount), uint64_t(0)));
+ if (setRange)
+ ssKey << std::make_pair(std::string("acentry"), std::make_pair((fAllAccounts ? std::string("") : strAccount), uint64_t(0)));
CDataStream ssValue(SER_DISK, CLIENT_VERSION);
- int ret = ReadAtCursor(pcursor, ssKey, ssValue, fFlags);
- fFlags = DB_NEXT;
+ int ret = m_batch.ReadAtCursor(pcursor, ssKey, ssValue, setRange);
+ setRange = false;
if (ret == DB_NOTFOUND)
break;
else if (ret != 0)
{
pcursor->close();
- throw runtime_error(std::string(__func__) + ": error scanning DB");
+ throw std::runtime_error(std::string(__func__) + ": error scanning DB");
}
// Unserialize
- string strType;
+ std::string strType;
ssKey >> strType;
if (strType != "acentry")
break;
@@ -252,103 +226,29 @@ void CWalletDB::ListAccountCreditDebit(const string& strAccount, list<CAccountin
pcursor->close();
}
-DBErrors CWalletDB::ReorderTransactions(CWallet* pwallet)
-{
- LOCK(pwallet->cs_wallet);
- // Old wallets didn't have any defined order for transactions
- // Probably a bad idea to change the output of this
-
- // First: get all CWalletTx and CAccountingEntry into a sorted-by-time multimap.
- typedef pair<CWalletTx*, CAccountingEntry*> TxPair;
- typedef multimap<int64_t, TxPair > TxItems;
- TxItems txByTime;
-
- for (map<uint256, CWalletTx>::iterator it = pwallet->mapWallet.begin(); it != pwallet->mapWallet.end(); ++it)
- {
- CWalletTx* wtx = &((*it).second);
- txByTime.insert(make_pair(wtx->nTimeReceived, TxPair(wtx, (CAccountingEntry*)0)));
- }
- list<CAccountingEntry> acentries;
- ListAccountCreditDebit("", acentries);
- BOOST_FOREACH(CAccountingEntry& entry, acentries)
- {
- txByTime.insert(make_pair(entry.nTime, TxPair((CWalletTx*)0, &entry)));
- }
-
- int64_t& nOrderPosNext = pwallet->nOrderPosNext;
- nOrderPosNext = 0;
- std::vector<int64_t> nOrderPosOffsets;
- for (TxItems::iterator it = txByTime.begin(); it != txByTime.end(); ++it)
- {
- CWalletTx *const pwtx = (*it).second.first;
- CAccountingEntry *const pacentry = (*it).second.second;
- int64_t& nOrderPos = (pwtx != 0) ? pwtx->nOrderPos : pacentry->nOrderPos;
-
- if (nOrderPos == -1)
- {
- nOrderPos = nOrderPosNext++;
- nOrderPosOffsets.push_back(nOrderPos);
-
- if (pwtx)
- {
- if (!WriteTx(*pwtx))
- return DB_LOAD_FAIL;
- }
- else
- if (!WriteAccountingEntry(pacentry->nEntryNo, *pacentry))
- return DB_LOAD_FAIL;
- }
- else
- {
- int64_t nOrderPosOff = 0;
- BOOST_FOREACH(const int64_t& nOffsetStart, nOrderPosOffsets)
- {
- if (nOrderPos >= nOffsetStart)
- ++nOrderPosOff;
- }
- nOrderPos += nOrderPosOff;
- nOrderPosNext = std::max(nOrderPosNext, nOrderPos + 1);
-
- if (!nOrderPosOff)
- continue;
-
- // Since we're changing the order, write it back
- if (pwtx)
- {
- if (!WriteTx(*pwtx))
- return DB_LOAD_FAIL;
- }
- else
- if (!WriteAccountingEntry(pacentry->nEntryNo, *pacentry))
- return DB_LOAD_FAIL;
- }
- }
- WriteOrderPosNext(nOrderPosNext);
-
- return DB_LOAD_OK;
-}
-
class CWalletScanState {
public:
unsigned int nKeys;
unsigned int nCKeys;
+ unsigned int nWatchKeys;
unsigned int nKeyMeta;
+ unsigned int m_unknown_records;
bool fIsEncrypted;
bool fAnyUnordered;
int nFileVersion;
- vector<uint256> vWalletUpgrade;
+ std::vector<uint256> vWalletUpgrade;
CWalletScanState() {
- nKeys = nCKeys = nKeyMeta = 0;
+ nKeys = nCKeys = nWatchKeys = nKeyMeta = m_unknown_records = 0;
fIsEncrypted = false;
fAnyUnordered = false;
nFileVersion = 0;
}
};
-bool
+static bool
ReadKeyValue(CWallet* pwallet, CDataStream& ssKey, CDataStream& ssValue,
- CWalletScanState &wss, string& strType, string& strErr)
+ CWalletScanState &wss, std::string& strType, std::string& strErr) EXCLUSIVE_LOCKS_REQUIRED(pwallet->cs_wallet)
{
try {
// Unserialize
@@ -357,24 +257,24 @@ ReadKeyValue(CWallet* pwallet, CDataStream& ssKey, CDataStream& ssValue,
ssKey >> strType;
if (strType == "name")
{
- string strAddress;
+ std::string strAddress;
ssKey >> strAddress;
- ssValue >> pwallet->mapAddressBook[CBitcoinAddress(strAddress).Get()].name;
+ ssValue >> pwallet->mapAddressBook[DecodeDestination(strAddress)].name;
}
else if (strType == "purpose")
{
- string strAddress;
+ std::string strAddress;
ssKey >> strAddress;
- ssValue >> pwallet->mapAddressBook[CBitcoinAddress(strAddress).Get()].purpose;
+ ssValue >> pwallet->mapAddressBook[DecodeDestination(strAddress)].purpose;
}
else if (strType == "tx")
{
uint256 hash;
ssKey >> hash;
- CWalletTx wtx;
+ CWalletTx wtx(nullptr /* pwallet */, MakeTransactionRef());
ssValue >> wtx;
CValidationState state;
- if (!(CheckTransaction(wtx, state) && (wtx.GetHash() == hash) && state.IsValid()))
+ if (!(CheckTransaction(*wtx.tx, state) && (wtx.GetHash() == hash) && state.IsValid()))
return false;
// Undo serialize changes in 31600
@@ -404,12 +304,13 @@ ReadKeyValue(CWallet* pwallet, CDataStream& ssKey, CDataStream& ssValue,
}
else if (strType == "acentry")
{
- string strAccount;
+ std::string strAccount;
ssKey >> strAccount;
uint64_t nNumber;
ssKey >> nNumber;
- if (nNumber > nAccountingEntryNumber)
- nAccountingEntryNumber = nNumber;
+ if (nNumber > pwallet->nAccountingEntryNumber) {
+ pwallet->nAccountingEntryNumber = nNumber;
+ }
if (!wss.fAnyUnordered)
{
@@ -421,16 +322,13 @@ ReadKeyValue(CWallet* pwallet, CDataStream& ssKey, CDataStream& ssValue,
}
else if (strType == "watchs")
{
+ wss.nWatchKeys++;
CScript script;
- ssKey >> *(CScriptBase*)(&script);
+ ssKey >> script;
char fYes;
ssValue >> fYes;
if (fYes == '1')
pwallet->LoadWatchOnly(script);
-
- // Watch-only addresses have no birthday information for now,
- // so set the wallet birthday to the beginning of time.
- pwallet->nTimeFirstKey = 1;
}
else if (strType == "key" || strType == "wkey")
{
@@ -520,7 +418,7 @@ ReadKeyValue(CWallet* pwallet, CDataStream& ssKey, CDataStream& ssValue,
strErr = "Error reading wallet database: CPubKey corrupt";
return false;
}
- vector<unsigned char> vchPrivKey;
+ std::vector<unsigned char> vchPrivKey;
ssValue >> vchPrivKey;
wss.nCKeys++;
@@ -538,17 +436,27 @@ ReadKeyValue(CWallet* pwallet, CDataStream& ssKey, CDataStream& ssValue,
CKeyMetadata keyMeta;
ssValue >> keyMeta;
wss.nKeyMeta++;
-
- pwallet->LoadKeyMetadata(vchPubKey, keyMeta);
-
- // find earliest key creation time, as wallet birthday
- if (!pwallet->nTimeFirstKey ||
- (keyMeta.nCreateTime < pwallet->nTimeFirstKey))
- pwallet->nTimeFirstKey = keyMeta.nCreateTime;
+ pwallet->LoadKeyMetadata(vchPubKey.GetID(), keyMeta);
+ }
+ else if (strType == "watchmeta")
+ {
+ CScript script;
+ ssKey >> script;
+ CKeyMetadata keyMeta;
+ ssValue >> keyMeta;
+ wss.nKeyMeta++;
+ pwallet->LoadScriptMetadata(CScriptID(script), keyMeta);
}
else if (strType == "defaultkey")
{
- ssValue >> pwallet->vchDefaultKey;
+ // We don't want or need the default key, but if there is one set,
+ // we want to make sure that it is valid so that we can detect corruption
+ CPubKey vchPubKey;
+ ssValue >> vchPubKey;
+ if (!vchPubKey.IsValid()) {
+ strErr = "Error reading wallet database: Default Key corrupt";
+ return false;
+ }
}
else if (strType == "pool")
{
@@ -570,7 +478,7 @@ ReadKeyValue(CWallet* pwallet, CDataStream& ssKey, CDataStream& ssValue,
uint160 hash;
ssKey >> hash;
CScript script;
- ssValue >> *(CScriptBase*)(&script);
+ ssValue >> script;
if (!pwallet->LoadCScript(script))
{
strErr = "Error reading wallet database: LoadCScript failed";
@@ -587,21 +495,23 @@ ReadKeyValue(CWallet* pwallet, CDataStream& ssKey, CDataStream& ssValue,
ssKey >> strAddress;
ssKey >> strKey;
ssValue >> strValue;
- if (!pwallet->LoadDestData(CBitcoinAddress(strAddress).Get(), strKey, strValue))
- {
- strErr = "Error reading wallet database: LoadDestData failed";
- return false;
- }
+ pwallet->LoadDestData(DecodeDestination(strAddress), strKey, strValue);
}
else if (strType == "hdchain")
{
CHDChain chain;
ssValue >> chain;
- if (!pwallet->SetHDChain(chain, true))
- {
- strErr = "Error reading wallet database: SetHDChain failed";
+ pwallet->SetHDChain(chain, true);
+ } else if (strType == "flags") {
+ uint64_t flags;
+ ssValue >> flags;
+ if (!pwallet->SetWalletFlags(flags, true)) {
+ strErr = "Error reading wallet database: Unknown non-tolerable wallet flags found";
return false;
}
+ } else if (strType != "bestblock" && strType != "bestblock_nomerkle" &&
+ strType != "minversion") {
+ wss.m_unknown_records++;
}
} catch (...)
{
@@ -610,35 +520,34 @@ ReadKeyValue(CWallet* pwallet, CDataStream& ssKey, CDataStream& ssValue,
return true;
}
-static bool IsKeyType(string strType)
+bool WalletBatch::IsKeyType(const std::string& strType)
{
return (strType== "key" || strType == "wkey" ||
strType == "mkey" || strType == "ckey");
}
-DBErrors CWalletDB::LoadWallet(CWallet* pwallet)
+DBErrors WalletBatch::LoadWallet(CWallet* pwallet)
{
- pwallet->vchDefaultKey = CPubKey();
CWalletScanState wss;
bool fNoncriticalErrors = false;
- DBErrors result = DB_LOAD_OK;
+ DBErrors result = DBErrors::LOAD_OK;
+ LOCK(pwallet->cs_wallet);
try {
- LOCK(pwallet->cs_wallet);
int nMinVersion = 0;
- if (Read((string)"minversion", nMinVersion))
+ if (m_batch.Read((std::string)"minversion", nMinVersion))
{
- if (nMinVersion > CLIENT_VERSION)
- return DB_TOO_NEW;
+ if (nMinVersion > FEATURE_LATEST)
+ return DBErrors::TOO_NEW;
pwallet->LoadMinVersion(nMinVersion);
}
// Get cursor
- Dbc* pcursor = GetCursor();
+ Dbc* pcursor = m_batch.GetCursor();
if (!pcursor)
{
- LogPrintf("Error getting wallet database cursor\n");
- return DB_CORRUPT;
+ pwallet->WalletLogPrintf("Error getting wallet database cursor\n");
+ return DBErrors::CORRUPT;
}
while (true)
@@ -646,34 +555,36 @@ DBErrors CWalletDB::LoadWallet(CWallet* pwallet)
// Read next record
CDataStream ssKey(SER_DISK, CLIENT_VERSION);
CDataStream ssValue(SER_DISK, CLIENT_VERSION);
- int ret = ReadAtCursor(pcursor, ssKey, ssValue);
+ int ret = m_batch.ReadAtCursor(pcursor, ssKey, ssValue);
if (ret == DB_NOTFOUND)
break;
else if (ret != 0)
{
- LogPrintf("Error reading next record from wallet database\n");
- return DB_CORRUPT;
+ pwallet->WalletLogPrintf("Error reading next record from wallet database\n");
+ return DBErrors::CORRUPT;
}
// Try to be tolerant of single corrupt records:
- string strType, strErr;
+ std::string strType, strErr;
if (!ReadKeyValue(pwallet, ssKey, ssValue, wss, strType, strErr))
{
// losing keys is considered a catastrophic error, anything else
// we assume the user can live with:
- if (IsKeyType(strType))
- result = DB_CORRUPT;
- else
- {
+ if (IsKeyType(strType) || strType == "defaultkey") {
+ result = DBErrors::CORRUPT;
+ } else if(strType == "flags") {
+ // reading the wallet flags can only fail if unknown flags are present
+ result = DBErrors::TOO_NEW;
+ } else {
// Leave other errors alone, if we try to fix them we might make things worse.
fNoncriticalErrors = true; // ... but do warn the user there is something wrong.
if (strType == "tx")
// Rescan if there is a bad transaction record:
- SoftSetBoolArg("-rescan", true);
+ gArgs.SoftSetBoolArg("-rescan", true);
}
}
if (!strErr.empty())
- LogPrintf("%s\n", strErr);
+ pwallet->WalletLogPrintf("%s\n", strErr);
}
pcursor->close();
}
@@ -681,70 +592,66 @@ DBErrors CWalletDB::LoadWallet(CWallet* pwallet)
throw;
}
catch (...) {
- result = DB_CORRUPT;
+ result = DBErrors::CORRUPT;
}
- if (fNoncriticalErrors && result == DB_LOAD_OK)
- result = DB_NONCRITICAL_ERROR;
+ if (fNoncriticalErrors && result == DBErrors::LOAD_OK)
+ result = DBErrors::NONCRITICAL_ERROR;
// Any wallet corruption at all: skip any rewriting or
// upgrading, we don't want to make it worse.
- if (result != DB_LOAD_OK)
+ if (result != DBErrors::LOAD_OK)
return result;
- LogPrintf("nFileVersion = %d\n", wss.nFileVersion);
+ pwallet->WalletLogPrintf("nFileVersion = %d\n", wss.nFileVersion);
- LogPrintf("Keys: %u plaintext, %u encrypted, %u w/ metadata, %u total\n",
- wss.nKeys, wss.nCKeys, wss.nKeyMeta, wss.nKeys + wss.nCKeys);
+ pwallet->WalletLogPrintf("Keys: %u plaintext, %u encrypted, %u w/ metadata, %u total. Unknown wallet records: %u\n",
+ wss.nKeys, wss.nCKeys, wss.nKeyMeta, wss.nKeys + wss.nCKeys, wss.m_unknown_records);
// nTimeFirstKey is only reliable if all keys have metadata
- if ((wss.nKeys + wss.nCKeys) != wss.nKeyMeta)
- pwallet->nTimeFirstKey = 1; // 0 would be considered 'no value'
+ if ((wss.nKeys + wss.nCKeys + wss.nWatchKeys) != wss.nKeyMeta)
+ pwallet->UpdateTimeFirstKey(1);
- BOOST_FOREACH(uint256 hash, wss.vWalletUpgrade)
- WriteTx(pwallet->mapWallet[hash]);
+ for (uint256 hash : wss.vWalletUpgrade)
+ WriteTx(pwallet->mapWallet.at(hash));
// Rewrite encrypted wallets of versions 0.4.0 and 0.5.0rc:
if (wss.fIsEncrypted && (wss.nFileVersion == 40000 || wss.nFileVersion == 50000))
- return DB_NEED_REWRITE;
+ return DBErrors::NEED_REWRITE;
if (wss.nFileVersion < CLIENT_VERSION) // Update
WriteVersion(CLIENT_VERSION);
if (wss.fAnyUnordered)
- result = ReorderTransactions(pwallet);
+ result = pwallet->ReorderTransactions();
pwallet->laccentries.clear();
ListAccountCreditDebit("*", pwallet->laccentries);
- BOOST_FOREACH(CAccountingEntry& entry, pwallet->laccentries) {
- pwallet->wtxOrdered.insert(make_pair(entry.nOrderPos, CWallet::TxPair((CWalletTx*)0, &entry)));
+ for (CAccountingEntry& entry : pwallet->laccentries) {
+ pwallet->wtxOrdered.insert(make_pair(entry.nOrderPos, CWallet::TxPair(nullptr, &entry)));
}
return result;
}
-DBErrors CWalletDB::FindWalletTx(CWallet* pwallet, vector<uint256>& vTxHash, vector<CWalletTx>& vWtx)
+DBErrors WalletBatch::FindWalletTx(std::vector<uint256>& vTxHash, std::vector<CWalletTx>& vWtx)
{
- pwallet->vchDefaultKey = CPubKey();
- bool fNoncriticalErrors = false;
- DBErrors result = DB_LOAD_OK;
+ DBErrors result = DBErrors::LOAD_OK;
try {
- LOCK(pwallet->cs_wallet);
int nMinVersion = 0;
- if (Read((string)"minversion", nMinVersion))
+ if (m_batch.Read((std::string)"minversion", nMinVersion))
{
- if (nMinVersion > CLIENT_VERSION)
- return DB_TOO_NEW;
- pwallet->LoadMinVersion(nMinVersion);
+ if (nMinVersion > FEATURE_LATEST)
+ return DBErrors::TOO_NEW;
}
// Get cursor
- Dbc* pcursor = GetCursor();
+ Dbc* pcursor = m_batch.GetCursor();
if (!pcursor)
{
LogPrintf("Error getting wallet database cursor\n");
- return DB_CORRUPT;
+ return DBErrors::CORRUPT;
}
while (true)
@@ -752,22 +659,22 @@ DBErrors CWalletDB::FindWalletTx(CWallet* pwallet, vector<uint256>& vTxHash, vec
// Read next record
CDataStream ssKey(SER_DISK, CLIENT_VERSION);
CDataStream ssValue(SER_DISK, CLIENT_VERSION);
- int ret = ReadAtCursor(pcursor, ssKey, ssValue);
+ int ret = m_batch.ReadAtCursor(pcursor, ssKey, ssValue);
if (ret == DB_NOTFOUND)
break;
else if (ret != 0)
{
LogPrintf("Error reading next record from wallet database\n");
- return DB_CORRUPT;
+ return DBErrors::CORRUPT;
}
- string strType;
+ std::string strType;
ssKey >> strType;
if (strType == "tx") {
uint256 hash;
ssKey >> hash;
- CWalletTx wtx;
+ CWalletTx wtx(nullptr /* pwallet */, MakeTransactionRef());
ssValue >> wtx;
vTxHash.push_back(hash);
@@ -780,22 +687,19 @@ DBErrors CWalletDB::FindWalletTx(CWallet* pwallet, vector<uint256>& vTxHash, vec
throw;
}
catch (...) {
- result = DB_CORRUPT;
+ result = DBErrors::CORRUPT;
}
- if (fNoncriticalErrors && result == DB_LOAD_OK)
- result = DB_NONCRITICAL_ERROR;
-
return result;
}
-DBErrors CWalletDB::ZapSelectTx(CWallet* pwallet, vector<uint256>& vTxHashIn, vector<uint256>& vTxHashOut)
+DBErrors WalletBatch::ZapSelectTx(std::vector<uint256>& vTxHashIn, std::vector<uint256>& vTxHashOut)
{
// build list of wallet TXs and hashes
- vector<uint256> vTxHash;
- vector<CWalletTx> vWtx;
- DBErrors err = FindWalletTx(pwallet, vTxHash, vWtx);
- if (err != DB_LOAD_OK) {
+ std::vector<uint256> vTxHash;
+ std::vector<CWalletTx> vWtx;
+ DBErrors err = FindWalletTx(vTxHash, vWtx);
+ if (err != DBErrors::LOAD_OK) {
return err;
}
@@ -804,8 +708,8 @@ DBErrors CWalletDB::ZapSelectTx(CWallet* pwallet, vector<uint256>& vTxHashIn, ve
// erase each matching wallet TX
bool delerror = false;
- vector<uint256>::iterator it = vTxHashIn.begin();
- BOOST_FOREACH (uint256 hash, vTxHash) {
+ std::vector<uint256>::iterator it = vTxHashIn.begin();
+ for (uint256 hash : vTxHash) {
while (it < vTxHashIn.end() && (*it) < hash) {
it++;
}
@@ -813,9 +717,8 @@ DBErrors CWalletDB::ZapSelectTx(CWallet* pwallet, vector<uint256>& vTxHashIn, ve
break;
}
else if ((*it) == hash) {
- pwallet->mapWallet.erase(hash);
if(!EraseTx(hash)) {
- LogPrint("db", "Transaction was found for deletion but returned database error: %s\n", hash.GetHex());
+ LogPrint(BCLog::DB, "Transaction was found for deletion but returned database error: %s\n", hash.GetHex());
delerror = true;
}
vTxHashOut.push_back(hash);
@@ -823,194 +726,148 @@ DBErrors CWalletDB::ZapSelectTx(CWallet* pwallet, vector<uint256>& vTxHashIn, ve
}
if (delerror) {
- return DB_CORRUPT;
+ return DBErrors::CORRUPT;
}
- return DB_LOAD_OK;
+ return DBErrors::LOAD_OK;
}
-DBErrors CWalletDB::ZapWalletTx(CWallet* pwallet, vector<CWalletTx>& vWtx)
+DBErrors WalletBatch::ZapWalletTx(std::vector<CWalletTx>& vWtx)
{
// build list of wallet TXs
- vector<uint256> vTxHash;
- DBErrors err = FindWalletTx(pwallet, vTxHash, vWtx);
- if (err != DB_LOAD_OK)
+ std::vector<uint256> vTxHash;
+ DBErrors err = FindWalletTx(vTxHash, vWtx);
+ if (err != DBErrors::LOAD_OK)
return err;
// erase each wallet TX
- BOOST_FOREACH (uint256& hash, vTxHash) {
+ for (uint256& hash : vTxHash) {
if (!EraseTx(hash))
- return DB_CORRUPT;
+ return DBErrors::CORRUPT;
}
- return DB_LOAD_OK;
+ return DBErrors::LOAD_OK;
}
-void ThreadFlushWalletDB(const string& strFile)
+void MaybeCompactWalletDB()
{
- // Make this thread recognisable as the wallet flushing thread
- RenameThread("bitcoin-wallet");
-
- static bool fOneThread;
- if (fOneThread)
+ static std::atomic<bool> fOneThread(false);
+ if (fOneThread.exchange(true)) {
return;
- fOneThread = true;
- if (!GetBoolArg("-flushwallet", DEFAULT_FLUSHWALLET))
+ }
+ if (!gArgs.GetBoolArg("-flushwallet", DEFAULT_FLUSHWALLET)) {
return;
+ }
- unsigned int nLastSeen = nWalletDBUpdated;
- unsigned int nLastFlushed = nWalletDBUpdated;
- int64_t nLastWalletUpdate = GetTime();
- while (true)
- {
- MilliSleep(500);
+ for (const std::shared_ptr<CWallet>& pwallet : GetWallets()) {
+ WalletDatabase& dbh = pwallet->GetDBHandle();
- if (nLastSeen != nWalletDBUpdated)
- {
- nLastSeen = nWalletDBUpdated;
- nLastWalletUpdate = GetTime();
- }
+ unsigned int nUpdateCounter = dbh.nUpdateCounter;
- if (nLastFlushed != nWalletDBUpdated && GetTime() - nLastWalletUpdate >= 2)
- {
- TRY_LOCK(bitdb.cs_db,lockDb);
- if (lockDb)
- {
- // Don't do this if any databases are in use
- int nRefCount = 0;
- map<string, int>::iterator mi = bitdb.mapFileUseCount.begin();
- while (mi != bitdb.mapFileUseCount.end())
- {
- nRefCount += (*mi).second;
- mi++;
- }
+ if (dbh.nLastSeen != nUpdateCounter) {
+ dbh.nLastSeen = nUpdateCounter;
+ dbh.nLastWalletUpdate = GetTime();
+ }
- if (nRefCount == 0)
- {
- boost::this_thread::interruption_point();
- map<string, int>::iterator mi = bitdb.mapFileUseCount.find(strFile);
- if (mi != bitdb.mapFileUseCount.end())
- {
- LogPrint("db", "Flushing %s\n", strFile);
- nLastFlushed = nWalletDBUpdated;
- int64_t nStart = GetTimeMillis();
-
- // Flush wallet file so it's self contained
- bitdb.CloseDb(strFile);
- bitdb.CheckpointLSN(strFile);
-
- bitdb.mapFileUseCount.erase(mi++);
- LogPrint("db", "Flushed %s %dms\n", strFile, GetTimeMillis() - nStart);
- }
- }
+ if (dbh.nLastFlushed != nUpdateCounter && GetTime() - dbh.nLastWalletUpdate >= 2) {
+ if (BerkeleyBatch::PeriodicFlush(dbh)) {
+ dbh.nLastFlushed = nUpdateCounter;
}
}
}
+
+ fOneThread = false;
}
//
// Try to (very carefully!) recover wallet file if there is a problem.
//
-bool CWalletDB::Recover(CDBEnv& dbenv, const std::string& filename, bool fOnlyKeys)
-{
- // Recovery procedure:
- // move wallet file to wallet.timestamp.bak
- // Call Salvage with fAggressive=true to
- // get as much data as possible.
- // Rewrite salvaged data to fresh wallet file
- // Set -rescan so any missing transactions will be
- // found.
- int64_t now = GetTime();
- std::string newFilename = strprintf("wallet.%d.bak", now);
-
- int result = dbenv.dbenv->dbrename(NULL, filename.c_str(), NULL,
- newFilename.c_str(), DB_AUTO_COMMIT);
- if (result == 0)
- LogPrintf("Renamed %s to %s\n", filename, newFilename);
- else
- {
- LogPrintf("Failed to rename %s to %s\n", filename, newFilename);
- return false;
- }
+bool WalletBatch::Recover(const fs::path& wallet_path, void *callbackDataIn, bool (*recoverKVcallback)(void* callbackData, CDataStream ssKey, CDataStream ssValue), std::string& out_backup_filename)
+{
+ return BerkeleyBatch::Recover(wallet_path, callbackDataIn, recoverKVcallback, out_backup_filename);
+}
- std::vector<CDBEnv::KeyValPair> salvagedData;
- bool fSuccess = dbenv.Salvage(newFilename, true, salvagedData);
- if (salvagedData.empty())
+bool WalletBatch::Recover(const fs::path& wallet_path, std::string& out_backup_filename)
+{
+ // recover without a key filter callback
+ // results in recovering all record types
+ return WalletBatch::Recover(wallet_path, nullptr, nullptr, out_backup_filename);
+}
+
+bool WalletBatch::RecoverKeysOnlyFilter(void *callbackData, CDataStream ssKey, CDataStream ssValue)
+{
+ CWallet *dummyWallet = reinterpret_cast<CWallet*>(callbackData);
+ CWalletScanState dummyWss;
+ std::string strType, strErr;
+ bool fReadOK;
{
- LogPrintf("Salvage(aggressive) found no records in %s.\n", newFilename);
- return false;
+ // Required in LoadKeyMetadata():
+ LOCK(dummyWallet->cs_wallet);
+ fReadOK = ReadKeyValue(dummyWallet, ssKey, ssValue,
+ dummyWss, strType, strErr);
}
- LogPrintf("Salvage(aggressive) found %u records\n", salvagedData.size());
-
- boost::scoped_ptr<Db> pdbCopy(new Db(dbenv.dbenv, 0));
- int ret = pdbCopy->open(NULL, // Txn pointer
- filename.c_str(), // Filename
- "main", // Logical db name
- DB_BTREE, // Database type
- DB_CREATE, // Flags
- 0);
- if (ret > 0)
+ if (!IsKeyType(strType) && strType != "hdchain")
+ return false;
+ if (!fReadOK)
{
- LogPrintf("Cannot create database file %s\n", filename);
+ LogPrintf("WARNING: WalletBatch::Recover skipping %s: %s\n", strType, strErr);
return false;
}
- CWallet dummyWallet;
- CWalletScanState wss;
- DbTxn* ptxn = dbenv.TxnBegin();
- BOOST_FOREACH(CDBEnv::KeyValPair& row, salvagedData)
- {
- if (fOnlyKeys)
- {
- CDataStream ssKey(row.first, SER_DISK, CLIENT_VERSION);
- CDataStream ssValue(row.second, SER_DISK, CLIENT_VERSION);
- string strType, strErr;
- bool fReadOK;
- {
- // Required in LoadKeyMetadata():
- LOCK(dummyWallet.cs_wallet);
- fReadOK = ReadKeyValue(&dummyWallet, ssKey, ssValue,
- wss, strType, strErr);
- }
- if (!IsKeyType(strType) && strType != "hdchain")
- continue;
- if (!fReadOK)
- {
- LogPrintf("WARNING: CWalletDB::Recover skipping %s: %s\n", strType, strErr);
- continue;
- }
- }
- Dbt datKey(&row.first[0], row.first.size());
- Dbt datValue(&row.second[0], row.second.size());
- int ret2 = pdbCopy->put(ptxn, &datKey, &datValue, DB_NOOVERWRITE);
- if (ret2 > 0)
- fSuccess = false;
- }
- ptxn->commit(0);
- pdbCopy->close(0);
+ return true;
+}
- return fSuccess;
+bool WalletBatch::VerifyEnvironment(const fs::path& wallet_path, std::string& errorStr)
+{
+ return BerkeleyBatch::VerifyEnvironment(wallet_path, errorStr);
}
-bool CWalletDB::Recover(CDBEnv& dbenv, const std::string& filename)
+bool WalletBatch::VerifyDatabaseFile(const fs::path& wallet_path, std::string& warningStr, std::string& errorStr)
{
- return CWalletDB::Recover(dbenv, filename, false);
+ return BerkeleyBatch::VerifyDatabaseFile(wallet_path, warningStr, errorStr, WalletBatch::Recover);
}
-bool CWalletDB::WriteDestData(const std::string &address, const std::string &key, const std::string &value)
+bool WalletBatch::WriteDestData(const std::string &address, const std::string &key, const std::string &value)
{
- nWalletDBUpdated++;
- return Write(std::make_pair(std::string("destdata"), std::make_pair(address, key)), value);
+ return WriteIC(std::make_pair(std::string("destdata"), std::make_pair(address, key)), value);
}
-bool CWalletDB::EraseDestData(const std::string &address, const std::string &key)
+bool WalletBatch::EraseDestData(const std::string &address, const std::string &key)
{
- nWalletDBUpdated++;
- return Erase(std::make_pair(std::string("destdata"), std::make_pair(address, key)));
+ return EraseIC(std::make_pair(std::string("destdata"), std::make_pair(address, key)));
}
-bool CWalletDB::WriteHDChain(const CHDChain& chain)
+bool WalletBatch::WriteHDChain(const CHDChain& chain)
+{
+ return WriteIC(std::string("hdchain"), chain);
+}
+
+bool WalletBatch::WriteWalletFlags(const uint64_t flags)
+{
+ return WriteIC(std::string("flags"), flags);
+}
+
+bool WalletBatch::TxnBegin()
+{
+ return m_batch.TxnBegin();
+}
+
+bool WalletBatch::TxnCommit()
+{
+ return m_batch.TxnCommit();
+}
+
+bool WalletBatch::TxnAbort()
+{
+ return m_batch.TxnAbort();
+}
+
+bool WalletBatch::ReadVersion(int& nVersion)
+{
+ return m_batch.ReadVersion(nVersion);
+}
+
+bool WalletBatch::WriteVersion(int nVersion)
{
- nWalletDBUpdated++;
- return Write(std::string("hdchain"), chain);
+ return m_batch.WriteVersion(nVersion);
}
diff --git a/src/wallet/walletdb.h b/src/wallet/walletdb.h
index 5addd5c5c0..0e6cb1bba4 100644
--- a/src/wallet/walletdb.h
+++ b/src/wallet/walletdb.h
@@ -1,15 +1,15 @@
// Copyright (c) 2009-2010 Satoshi Nakamoto
-// Copyright (c) 2009-2015 The Bitcoin Core developers
+// Copyright (c) 2009-2018 The Bitcoin Core developers
// Distributed under the MIT software license, see the accompanying
// file COPYING or http://www.opensource.org/licenses/mit-license.php.
#ifndef BITCOIN_WALLET_WALLETDB_H
#define BITCOIN_WALLET_WALLETDB_H
-#include "amount.h"
-#include "primitives/transaction.h"
-#include "wallet/db.h"
-#include "key.h"
+#include <amount.h>
+#include <primitives/transaction.h>
+#include <wallet/db.h>
+#include <key.h>
#include <list>
#include <stdint.h>
@@ -17,6 +17,18 @@
#include <utility>
#include <vector>
+/**
+ * Overview of wallet database classes:
+ *
+ * - WalletBatch is an abstract modifier object for the wallet database, and encapsulates a database
+ * batch update as well as methods to act on the database. It should be agnostic to the database implementation.
+ *
+ * The following classes are implementation specific:
+ * - BerkeleyEnvironment is an environment in which the database exists.
+ * - BerkeleyDatabase represents a wallet database.
+ * - BerkeleyBatch is a low-level database batch update.
+ */
+
static const bool DEFAULT_FLUSHWALLET = true;
class CAccount;
@@ -30,15 +42,18 @@ class CWalletTx;
class uint160;
class uint256;
+/** Backend-agnostic database type. */
+using WalletDatabase = BerkeleyDatabase;
+
/** Error statuses for the wallet database */
-enum DBErrors
+enum class DBErrors
{
- DB_LOAD_OK,
- DB_CORRUPT,
- DB_NONCRITICAL_ERROR,
- DB_TOO_NEW,
- DB_LOAD_FAIL,
- DB_NEED_REWRITE
+ LOAD_OK,
+ CORRUPT,
+ NONCRITICAL_ERROR,
+ TOO_NEW,
+ LOAD_FAIL,
+ NEED_REWRITE
};
/* simple HD chain data model */
@@ -46,27 +61,32 @@ class CHDChain
{
public:
uint32_t nExternalChainCounter;
- CKeyID masterKeyID; //!< master key hash160
+ uint32_t nInternalChainCounter;
+ CKeyID seed_id; //!< seed hash160
- static const int CURRENT_VERSION = 1;
+ static const int VERSION_HD_BASE = 1;
+ static const int VERSION_HD_CHAIN_SPLIT = 2;
+ static const int CURRENT_VERSION = VERSION_HD_CHAIN_SPLIT;
int nVersion;
CHDChain() { SetNull(); }
ADD_SERIALIZE_METHODS;
template <typename Stream, typename Operation>
- inline void SerializationOp(Stream& s, Operation ser_action, int nType, int nVersion)
+ inline void SerializationOp(Stream& s, Operation ser_action)
{
READWRITE(this->nVersion);
- nVersion = this->nVersion;
READWRITE(nExternalChainCounter);
- READWRITE(masterKeyID);
+ READWRITE(seed_id);
+ if (this->nVersion >= VERSION_HD_CHAIN_SPLIT)
+ READWRITE(nInternalChainCounter);
}
void SetNull()
{
nVersion = CHDChain::CURRENT_VERSION;
nExternalChainCounter = 0;
- masterKeyID.SetNull();
+ nInternalChainCounter = 0;
+ seed_id.SetNull();
}
};
@@ -79,13 +99,13 @@ public:
int nVersion;
int64_t nCreateTime; // 0 means unknown
std::string hdKeypath; //optional HD/bip32 keypath
- CKeyID hdMasterKeyID; //id of the HD masterkey used to derive this key
+ CKeyID hd_seed_id; //id of the HD seed used to derive this key
CKeyMetadata()
{
SetNull();
}
- CKeyMetadata(int64_t nCreateTime_)
+ explicit CKeyMetadata(int64_t nCreateTime_)
{
SetNull();
nCreateTime = nCreateTime_;
@@ -94,14 +114,13 @@ public:
ADD_SERIALIZE_METHODS;
template <typename Stream, typename Operation>
- inline void SerializationOp(Stream& s, Operation ser_action, int nType, int nVersion) {
+ inline void SerializationOp(Stream& s, Operation ser_action) {
READWRITE(this->nVersion);
- nVersion = this->nVersion;
READWRITE(nCreateTime);
if (this->nVersion >= VERSION_WITH_HDDATA)
{
READWRITE(hdKeypath);
- READWRITE(hdMasterKeyID);
+ READWRITE(hd_seed_id);
}
}
@@ -110,17 +129,46 @@ public:
nVersion = CKeyMetadata::CURRENT_VERSION;
nCreateTime = 0;
hdKeypath.clear();
- hdMasterKeyID.SetNull();
+ hd_seed_id.SetNull();
}
};
-/** Access to the wallet database */
-class CWalletDB : public CDB
+/** Access to the wallet database.
+ * This represents a single transaction at the
+ * database. It will be committed when the object goes out of scope.
+ * Optionally (on by default) it will flush to disk as well.
+ */
+class WalletBatch
{
+private:
+ template <typename K, typename T>
+ bool WriteIC(const K& key, const T& value, bool fOverwrite = true)
+ {
+ if (!m_batch.Write(key, value, fOverwrite)) {
+ return false;
+ }
+ m_database.IncrementUpdateCounter();
+ return true;
+ }
+
+ template <typename K>
+ bool EraseIC(const K& key)
+ {
+ if (!m_batch.Erase(key)) {
+ return false;
+ }
+ m_database.IncrementUpdateCounter();
+ return true;
+ }
+
public:
- CWalletDB(const std::string& strFilename, const char* pszMode = "r+", bool fFlushOnClose = true) : CDB(strFilename, pszMode, fFlushOnClose)
+ explicit WalletBatch(WalletDatabase& database, const char* pszMode = "r+", bool _fFlushOnClose = true) :
+ m_batch(database, pszMode, _fFlushOnClose),
+ m_database(database)
{
}
+ WalletBatch(const WalletBatch&) = delete;
+ WalletBatch& operator=(const WalletBatch&) = delete;
bool WriteName(const std::string& strAddress, const std::string& strName);
bool EraseName(const std::string& strAddress);
@@ -137,7 +185,7 @@ public:
bool WriteCScript(const uint160& hash, const CScript& redeemScript);
- bool WriteWatchOnly(const CScript &script);
+ bool WriteWatchOnly(const CScript &script, const CKeyMetadata &keymeta);
bool EraseWatchOnly(const CScript &script);
bool WriteBestBlock(const CBlockLocator& locator);
@@ -145,8 +193,6 @@ public:
bool WriteOrderPosNext(int64_t nOrderPosNext);
- bool WriteDefaultKey(const CPubKey& vchPubKey);
-
bool ReadPool(int64_t nPool, CKeyPool& keypool);
bool WritePool(int64_t nPool, const CKeyPool& keypool);
bool ErasePool(int64_t nPool);
@@ -155,9 +201,10 @@ public:
/// This writes directly to the database, and will not update the CWallet's cached accounting entries!
/// Use wallet.AddAccountingEntry instead, to write *and* update its caches.
- bool WriteAccountingEntry_Backend(const CAccountingEntry& acentry);
+ bool WriteAccountingEntry(const uint64_t nAccEntryNum, const CAccountingEntry& acentry);
bool ReadAccount(const std::string& strAccount, CAccount& account);
bool WriteAccount(const std::string& strAccount, const CAccount& account);
+ bool EraseAccount(const std::string& strAccount);
/// Write destination data key,value tuple to database
bool WriteDestData(const std::string &address, const std::string &key, const std::string &value);
@@ -167,24 +214,43 @@ public:
CAmount GetAccountCreditDebit(const std::string& strAccount);
void ListAccountCreditDebit(const std::string& strAccount, std::list<CAccountingEntry>& acentries);
- DBErrors ReorderTransactions(CWallet* pwallet);
DBErrors LoadWallet(CWallet* pwallet);
- DBErrors FindWalletTx(CWallet* pwallet, std::vector<uint256>& vTxHash, std::vector<CWalletTx>& vWtx);
- DBErrors ZapWalletTx(CWallet* pwallet, std::vector<CWalletTx>& vWtx);
- DBErrors ZapSelectTx(CWallet* pwallet, std::vector<uint256>& vHashIn, std::vector<uint256>& vHashOut);
- static bool Recover(CDBEnv& dbenv, const std::string& filename, bool fOnlyKeys);
- static bool Recover(CDBEnv& dbenv, const std::string& filename);
+ DBErrors FindWalletTx(std::vector<uint256>& vTxHash, std::vector<CWalletTx>& vWtx);
+ DBErrors ZapWalletTx(std::vector<CWalletTx>& vWtx);
+ DBErrors ZapSelectTx(std::vector<uint256>& vHashIn, std::vector<uint256>& vHashOut);
+ /* Try to (very carefully!) recover wallet database (with a possible key type filter) */
+ static bool Recover(const fs::path& wallet_path, void *callbackDataIn, bool (*recoverKVcallback)(void* callbackData, CDataStream ssKey, CDataStream ssValue), std::string& out_backup_filename);
+ /* Recover convenience-function to bypass the key filter callback, called when verify fails, recovers everything */
+ static bool Recover(const fs::path& wallet_path, std::string& out_backup_filename);
+ /* Recover filter (used as callback), will only let keys (cryptographical keys) as KV/key-type pass through */
+ static bool RecoverKeysOnlyFilter(void *callbackData, CDataStream ssKey, CDataStream ssValue);
+ /* Function to determine if a certain KV/key-type is a key (cryptographical key) type */
+ static bool IsKeyType(const std::string& strType);
+ /* verifies the database environment */
+ static bool VerifyEnvironment(const fs::path& wallet_path, std::string& errorStr);
+ /* verifies the database file */
+ static bool VerifyDatabaseFile(const fs::path& wallet_path, std::string& warningStr, std::string& errorStr);
//! write the hdchain model (external chain child index counter)
bool WriteHDChain(const CHDChain& chain);
+ bool WriteWalletFlags(const uint64_t flags);
+ //! Begin a new transaction
+ bool TxnBegin();
+ //! Commit current transaction
+ bool TxnCommit();
+ //! Abort current transaction
+ bool TxnAbort();
+ //! Read wallet version
+ bool ReadVersion(int& nVersion);
+ //! Write wallet version
+ bool WriteVersion(int nVersion);
private:
- CWalletDB(const CWalletDB&);
- void operator=(const CWalletDB&);
-
- bool WriteAccountingEntry(const uint64_t nAccEntryNum, const CAccountingEntry& acentry);
+ BerkeleyBatch m_batch;
+ WalletDatabase& m_database;
};
-void ThreadFlushWalletDB(const std::string& strFile);
+//! Compacts BDB state so that wallet.dat is self-contained (if there are changes)
+void MaybeCompactWalletDB();
#endif // BITCOIN_WALLET_WALLETDB_H
diff --git a/src/wallet/walletutil.cpp b/src/wallet/walletutil.cpp
new file mode 100644
index 0000000000..34c76ec4c4
--- /dev/null
+++ b/src/wallet/walletutil.cpp
@@ -0,0 +1,27 @@
+// Copyright (c) 2017-2018 The Bitcoin Core developers
+// Distributed under the MIT software license, see the accompanying
+// file COPYING or http://www.opensource.org/licenses/mit-license.php.
+
+#include <wallet/walletutil.h>
+
+fs::path GetWalletDir()
+{
+ fs::path path;
+
+ if (gArgs.IsArgSet("-walletdir")) {
+ path = gArgs.GetArg("-walletdir", "");
+ if (!fs::is_directory(path)) {
+ // If the path specified doesn't exist, we return the deliberately
+ // invalid empty string.
+ path = "";
+ }
+ } else {
+ path = GetDataDir();
+ // If a wallets directory exists, use that, otherwise default to GetDataDir
+ if (fs::is_directory(path / "wallets")) {
+ path /= "wallets";
+ }
+ }
+
+ return path;
+}
diff --git a/src/wallet/walletutil.h b/src/wallet/walletutil.h
new file mode 100644
index 0000000000..7c42954616
--- /dev/null
+++ b/src/wallet/walletutil.h
@@ -0,0 +1,14 @@
+// Copyright (c) 2017-2018 The Bitcoin Core developers
+// Distributed under the MIT software license, see the accompanying
+// file COPYING or http://www.opensource.org/licenses/mit-license.php.
+
+#ifndef BITCOIN_WALLET_WALLETUTIL_H
+#define BITCOIN_WALLET_WALLETUTIL_H
+
+#include <chainparamsbase.h>
+#include <util.h>
+
+//! Get the path of the wallet directory.
+fs::path GetWalletDir();
+
+#endif // BITCOIN_WALLET_WALLETUTIL_H