Browse Source

Merge pull request #6669

6342a48 Init: Use DEFAULT_TRANSACTION_MINFEE in help message (MarcoFalke)
a9c73a1 [wallet] Add comments for doxygen (MarcoFalke)
6b0e622 [wallet] Refactor to use new MIN_CHANGE (MarcoFalke)
0.13
Wladimir J. van der Laan 9 years ago
parent
commit
8a95a18562
No known key found for this signature in database
GPG Key ID: 74810B012346C9A6
  1. 2
      src/init.cpp
  2. 2
      src/qt/coincontroldialog.cpp
  3. 113
      src/wallet/test/wallet_tests.cpp
  4. 16
      src/wallet/wallet.cpp
  5. 29
      src/wallet/wallet.h

2
src/init.cpp

@ -376,7 +376,7 @@ std::string HelpMessage(HelpMessageMode mode)
strUsage += HelpMessageOpt("-keypool=<n>", strprintf(_("Set key pool size to <n> (default: %u)"), 100)); strUsage += HelpMessageOpt("-keypool=<n>", strprintf(_("Set key pool size to <n> (default: %u)"), 100));
if (showDebug) if (showDebug)
strUsage += HelpMessageOpt("-mintxfee=<amt>", strprintf("Fees (in %s/kB) smaller than this are considered zero fee for transaction creation (default: %s)", strUsage += HelpMessageOpt("-mintxfee=<amt>", strprintf("Fees (in %s/kB) smaller than this are considered zero fee for transaction creation (default: %s)",
CURRENCY_UNIT, FormatMoney(CWallet::minTxFee.GetFeePerK()))); CURRENCY_UNIT, FormatMoney(DEFAULT_TRANSACTION_MINFEE)));
strUsage += HelpMessageOpt("-paytxfee=<amt>", strprintf(_("Fee (in %s/kB) to add to transactions you send (default: %s)"), strUsage += HelpMessageOpt("-paytxfee=<amt>", strprintf(_("Fee (in %s/kB) to add to transactions you send (default: %s)"),
CURRENCY_UNIT, FormatMoney(payTxFee.GetFeePerK()))); CURRENCY_UNIT, FormatMoney(payTxFee.GetFeePerK())));
strUsage += HelpMessageOpt("-rescan", _("Rescan the block chain for missing wallet transactions") + " " + _("on startup")); strUsage += HelpMessageOpt("-rescan", _("Rescan the block chain for missing wallet transactions") + " " + _("on startup"));

2
src/qt/coincontroldialog.cpp

@ -567,7 +567,7 @@ void CoinControlDialog::updateLabels(WalletModel *model, QDialog* dialog)
nChange -= nPayFee; nChange -= nPayFee;
// Never create dust outputs; if we would, just add the dust to the fee. // Never create dust outputs; if we would, just add the dust to the fee.
if (nChange > 0 && nChange < CENT) if (nChange > 0 && nChange < MIN_CHANGE)
{ {
CTxOut txout(nChange, (CScript)std::vector<unsigned char>(24, 0)); CTxOut txout(nChange, (CScript)std::vector<unsigned char>(24, 0));
if (txout.IsDust(::minRelayTxFee)) if (txout.IsDust(::minRelayTxFee))

113
src/wallet/test/wallet_tests.cpp

@ -117,7 +117,7 @@ BOOST_AUTO_TEST_CASE(coin_selection_tests)
// try making 34 cents from 1,2,5,10,20 - we can't do it exactly // 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( wallet.SelectCoinsMinConf(34 * CENT, 1, 1, vCoins, setCoinsRet, nValueRet));
BOOST_CHECK_GT(nValueRet, 34 * CENT); // but should get more than 34 cents 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) 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 // when we try making 7 cents, the smaller coins (1,2,5) are enough. We should see just 2+5
@ -185,33 +185,34 @@ BOOST_AUTO_TEST_CASE(coin_selection_tests)
BOOST_CHECK_EQUAL(nValueRet, 2 * COIN); // we should get 2 BTC in 1 coin BOOST_CHECK_EQUAL(nValueRet, 2 * COIN); // we should get 2 BTC in 1 coin
BOOST_CHECK_EQUAL(setCoinsRet.size(), 1U); BOOST_CHECK_EQUAL(setCoinsRet.size(), 1U);
// empty the wallet and start again, now with fractions of a cent, to test sub-cent change avoidance // empty the wallet and start again, now with fractions of a cent, to test small change avoidance
empty_wallet(); empty_wallet();
add_coin(0.1*CENT); add_coin(0.1*MIN_CHANGE);
add_coin(0.2*CENT); add_coin(0.2*MIN_CHANGE);
add_coin(0.3*CENT); add_coin(0.3*MIN_CHANGE);
add_coin(0.4*CENT); add_coin(0.4*MIN_CHANGE);
add_coin(0.5*CENT); add_coin(0.5*MIN_CHANGE);
// try making 1 cent from 0.1 + 0.2 + 0.3 + 0.4 + 0.5 = 1.5 cents
// we'll get sub-cent change whatever happens, so can expect 1.0 exactly
BOOST_CHECK( wallet.SelectCoinsMinConf(1 * CENT, 1, 1, vCoins, setCoinsRet, nValueRet));
BOOST_CHECK_EQUAL(nValueRet, 1 * CENT);
// but if we add a bigger coin, making it possible to avoid sub-cent change, things change: // try making 1 * MIN_CHANGE from the 1.5 * MIN_CHANGE
add_coin(1111*CENT); // 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);
// try making 1 cent from 0.1 + 0.2 + 0.3 + 0.4 + 0.5 + 1111 = 1112.5 cents // but if we add a bigger coin, small change is avoided
BOOST_CHECK( wallet.SelectCoinsMinConf(1 * CENT, 1, 1, vCoins, setCoinsRet, nValueRet)); add_coin(1111*MIN_CHANGE);
BOOST_CHECK_EQUAL(nValueRet, 1 * CENT); // we should get the exact amount
// if we add more sub-cent coins: // try making 1 from 0.1 + 0.2 + 0.3 + 0.4 + 0.5 + 1111 = 1112.5
add_coin(0.6*CENT); BOOST_CHECK( wallet.SelectCoinsMinConf(1 * MIN_CHANGE, 1, 1, vCoins, setCoinsRet, nValueRet));
add_coin(0.7*CENT); BOOST_CHECK_EQUAL(nValueRet, 1 * MIN_CHANGE); // we should get the exact amount
// and try again to make 1.0 cents, we can still make 1.0 cents // if we add more small coins:
BOOST_CHECK( wallet.SelectCoinsMinConf(1 * CENT, 1, 1, vCoins, setCoinsRet, nValueRet)); add_coin(0.6*MIN_CHANGE);
BOOST_CHECK_EQUAL(nValueRet, 1 * CENT); // we should get the exact amount add_coin(0.7*MIN_CHANGE);
// 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) // 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 // they tried to consolidate 10 50k coins into one 500k coin, and ended up with 50k in change
@ -223,45 +224,65 @@ BOOST_AUTO_TEST_CASE(coin_selection_tests)
BOOST_CHECK_EQUAL(nValueRet, 500000 * COIN); // we should get the exact amount BOOST_CHECK_EQUAL(nValueRet, 500000 * COIN); // we should get the exact amount
BOOST_CHECK_EQUAL(setCoinsRet.size(), 10U); // in ten coins BOOST_CHECK_EQUAL(setCoinsRet.size(), 10U); // in ten coins
// if there's not enough in the smaller coins to make at least 1 cent change (0.5+0.6+0.7 < 1.0+1.0), // 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 // we need to try finding an exact subset anyway
// sometimes it will fail, and so we use the next biggest coin: // sometimes it will fail, and so we use the next biggest coin:
empty_wallet(); empty_wallet();
add_coin(0.5 * CENT); add_coin(0.5 * MIN_CHANGE);
add_coin(0.6 * CENT); add_coin(0.6 * MIN_CHANGE);
add_coin(0.7 * CENT); add_coin(0.7 * MIN_CHANGE);
add_coin(1111 * CENT); add_coin(1111 * MIN_CHANGE);
BOOST_CHECK( wallet.SelectCoinsMinConf(1 * CENT, 1, 1, vCoins, setCoinsRet, nValueRet)); BOOST_CHECK( wallet.SelectCoinsMinConf(1 * MIN_CHANGE, 1, 1, vCoins, setCoinsRet, nValueRet));
BOOST_CHECK_EQUAL(nValueRet, 1111 * CENT); // we get the bigger coin BOOST_CHECK_EQUAL(nValueRet, 1111 * MIN_CHANGE); // we get the bigger coin
BOOST_CHECK_EQUAL(setCoinsRet.size(), 1U); BOOST_CHECK_EQUAL(setCoinsRet.size(), 1U);
// but sometimes it's possible, and we use an exact subset (0.4 + 0.6 = 1.0) // but sometimes it's possible, and we use an exact subset (0.4 + 0.6 = 1.0)
empty_wallet(); empty_wallet();
add_coin(0.4 * CENT); add_coin(0.4 * MIN_CHANGE);
add_coin(0.6 * CENT); add_coin(0.6 * MIN_CHANGE);
add_coin(0.8 * CENT); add_coin(0.8 * MIN_CHANGE);
add_coin(1111 * CENT); add_coin(1111 * MIN_CHANGE);
BOOST_CHECK( wallet.SelectCoinsMinConf(1 * CENT, 1, 1, vCoins, setCoinsRet, nValueRet)); BOOST_CHECK( wallet.SelectCoinsMinConf(MIN_CHANGE, 1, 1, vCoins, setCoinsRet, nValueRet));
BOOST_CHECK_EQUAL(nValueRet, 1 * CENT); // we should get the exact amount 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 BOOST_CHECK_EQUAL(setCoinsRet.size(), 2U); // in two coins 0.4+0.6
// test avoiding sub-cent change // test avoiding small change
empty_wallet(); empty_wallet();
add_coin(0.0005 * COIN); add_coin(0.05 * MIN_CHANGE);
add_coin(0.01 * COIN); add_coin(1 * MIN_CHANGE);
add_coin(1 * COIN); add_coin(100 * MIN_CHANGE);
// trying to make 1.0001 from these three coins // trying to make 100.01 from these three coins
BOOST_CHECK( wallet.SelectCoinsMinConf(1.0001 * COIN, 1, 1, vCoins, setCoinsRet, nValueRet)); BOOST_CHECK( wallet.SelectCoinsMinConf(100.01 * MIN_CHANGE, 1, 1, vCoins, setCoinsRet, nValueRet));
BOOST_CHECK_EQUAL(nValueRet, 1.0105 * COIN); // we should get all coins BOOST_CHECK_EQUAL(nValueRet, 101.05 * MIN_CHANGE); // we should get all coins
BOOST_CHECK_EQUAL(setCoinsRet.size(), 3U); BOOST_CHECK_EQUAL(setCoinsRet.size(), 3U);
// but if we try to make 0.999, we should take the bigger of the two small coins to avoid sub-cent change // 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(0.999 * COIN, 1, 1, vCoins, setCoinsRet, nValueRet)); BOOST_CHECK( wallet.SelectCoinsMinConf(99.9 * MIN_CHANGE, 1, 1, vCoins, setCoinsRet, nValueRet));
BOOST_CHECK_EQUAL(nValueRet, 1.01 * COIN); // we should get 1 + 0.01 BOOST_CHECK_EQUAL(nValueRet, 101 * MIN_CHANGE);
BOOST_CHECK_EQUAL(setCoinsRet.size(), 2U); 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);
}
}
// test randomness // test randomness
{ {
empty_wallet(); empty_wallet();

16
src/wallet/wallet.cpp

@ -47,7 +47,7 @@ bool fPayAtLeastCustomFee = true;
* Fees smaller than this (in satoshi) are considered zero fee (for transaction creation) * Fees smaller than this (in satoshi) are considered zero fee (for transaction creation)
* Override with -mintxfee * Override with -mintxfee
*/ */
CFeeRate CWallet::minTxFee = CFeeRate(1000); CFeeRate CWallet::minTxFee = CFeeRate(DEFAULT_TRANSACTION_MINFEE);
/** @defgroup mapWallet /** @defgroup mapWallet
* *
@ -1497,9 +1497,6 @@ CAmount CWallet::GetImmatureWatchOnlyBalance() const
return nTotal; return nTotal;
} }
/**
* populate vCoins with vector of available COutputs.
*/
void CWallet::AvailableCoins(vector<COutput>& vCoins, bool fOnlyConfirmed, const CCoinControl *coinControl, bool fIncludeZeroValue) const void CWallet::AvailableCoins(vector<COutput>& vCoins, bool fOnlyConfirmed, const CCoinControl *coinControl, bool fIncludeZeroValue) const
{ {
vCoins.clear(); vCoins.clear();
@ -1619,7 +1616,7 @@ bool CWallet::SelectCoinsMinConf(const CAmount& nTargetValue, int nConfMine, int
nValueRet += coin.first; nValueRet += coin.first;
return true; return true;
} }
else if (n < nTargetValue + CENT) else if (n < nTargetValue + MIN_CHANGE)
{ {
vValue.push_back(coin); vValue.push_back(coin);
nTotalLower += n; nTotalLower += n;
@ -1654,14 +1651,14 @@ bool CWallet::SelectCoinsMinConf(const CAmount& nTargetValue, int nConfMine, int
vector<char> vfBest; vector<char> vfBest;
CAmount nBest; CAmount nBest;
ApproximateBestSubset(vValue, nTotalLower, nTargetValue, vfBest, nBest, 1000); ApproximateBestSubset(vValue, nTotalLower, nTargetValue, vfBest, nBest);
if (nBest != nTargetValue && nTotalLower >= nTargetValue + CENT) if (nBest != nTargetValue && nTotalLower >= nTargetValue + MIN_CHANGE)
ApproximateBestSubset(vValue, nTotalLower, nTargetValue + CENT, vfBest, nBest, 1000); ApproximateBestSubset(vValue, nTotalLower, nTargetValue + MIN_CHANGE, vfBest, nBest);
// If we have a bigger coin and (either the stochastic approximation didn't find a good solution, // 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 // or the next bigger coin is closer), return the bigger coin
if (coinLowestLarger.second.first && if (coinLowestLarger.second.first &&
((nBest != nTargetValue && nBest < nTargetValue + CENT) || coinLowestLarger.first <= nBest)) ((nBest != nTargetValue && nBest < nTargetValue + MIN_CHANGE) || coinLowestLarger.first <= nBest))
{ {
setCoinsRet.insert(coinLowestLarger.second); setCoinsRet.insert(coinLowestLarger.second);
nValueRet += coinLowestLarger.first; nValueRet += coinLowestLarger.first;
@ -1844,6 +1841,7 @@ bool CWallet::CreateTransaction(const vector<CRecipient>& vecSend, CWalletTx& wt
LOCK2(cs_main, cs_wallet); LOCK2(cs_main, cs_wallet);
{ {
nFeeRet = 0; nFeeRet = 0;
// Start with no fee and loop until there is enough fee
while (true) while (true)
{ {
txNew.vin.clear(); txNew.vin.clear();

29
src/wallet/wallet.h

@ -41,8 +41,12 @@ extern bool fPayAtLeastCustomFee;
static const CAmount DEFAULT_TRANSACTION_FEE = 0; static const CAmount DEFAULT_TRANSACTION_FEE = 0;
//! -paytxfee will warn if called with a higher fee than this amount (in satoshis) per KB //! -paytxfee will warn if called with a higher fee than this amount (in satoshis) per KB
static const CAmount nHighTransactionFeeWarning = 0.01 * COIN; static const CAmount nHighTransactionFeeWarning = 0.01 * COIN;
//! -mintxfee default
static const CAmount DEFAULT_TRANSACTION_MINFEE = 1000;
//! -maxtxfee default //! -maxtxfee default
static const CAmount DEFAULT_TRANSACTION_MAXFEE = 0.1 * COIN; static const CAmount DEFAULT_TRANSACTION_MAXFEE = 0.1 * COIN;
//! minimum change amount
static const CAmount MIN_CHANGE = CENT;
//! -txconfirmtarget default //! -txconfirmtarget default
static const unsigned int DEFAULT_TX_CONFIRM_TARGET = 2; static const unsigned int DEFAULT_TX_CONFIRM_TARGET = 2;
//! -maxtxfee will warn if called with a higher fee than this amount (in satoshis) //! -maxtxfee will warn if called with a higher fee than this amount (in satoshis)
@ -442,6 +446,11 @@ public:
class CWallet : public CCryptoKeyStore, public CValidationInterface class CWallet : public CCryptoKeyStore, public CValidationInterface
{ {
private: 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 CAmount& nTargetValue, std::set<std::pair<const CWalletTx*,unsigned int> >& setCoinsRet, CAmount& nValueRet, const CCoinControl *coinControl = NULL) const; bool SelectCoins(const CAmount& nTargetValue, std::set<std::pair<const CWalletTx*,unsigned int> >& setCoinsRet, CAmount& nValueRet, const CCoinControl *coinControl = NULL) const;
CWalletDB *pwalletdbEncryption; CWalletDB *pwalletdbEncryption;
@ -539,7 +548,17 @@ public:
//! check whether we are allowed to upgrade (or already support) to the named feature //! 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) { 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 fOnlyConfirmed=true, const CCoinControl *coinControl = NULL, bool fIncludeZeroValue=false) const;
/**
* Shuffle and select coins until nTargetValue is reached while avoiding
* small change; This method is stochastic for some inputs and upon
* completion the coin set and corresponding actual target value is
* 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, int nConfMine, int nConfTheirs, std::vector<COutput> vCoins, std::set<std::pair<const CWalletTx*,unsigned int> >& setCoinsRet, CAmount& nValueRet) const;
bool IsSpent(const uint256& hash, unsigned int n) const; bool IsSpent(const uint256& hash, unsigned int n) const;
@ -622,7 +641,17 @@ public:
CAmount GetWatchOnlyBalance() const; CAmount GetWatchOnlyBalance() const;
CAmount GetUnconfirmedWatchOnlyBalance() const; CAmount GetUnconfirmedWatchOnlyBalance() const;
CAmount GetImmatureWatchOnlyBalance() const; CAmount GetImmatureWatchOnlyBalance() const;
/**
* Insert additional inputs into the transaction by
* calling CreateTransaction();
*/
bool FundTransaction(CMutableTransaction& tx, CAmount& nFeeRet, int& nChangePosRet, std::string& strFailReason, bool includeWatching); bool FundTransaction(CMutableTransaction& tx, CAmount& nFeeRet, int& nChangePosRet, std::string& strFailReason, bool includeWatching);
/**
* Create a new transaction paying the recipients with a set of coins
* selected by SelectCoins(); Also create the change output, when needed
*/
bool CreateTransaction(const std::vector<CRecipient>& vecSend, CWalletTx& wtxNew, CReserveKey& reservekey, CAmount& nFeeRet, int& nChangePosRet, bool CreateTransaction(const std::vector<CRecipient>& vecSend, CWalletTx& wtxNew, CReserveKey& reservekey, CAmount& nFeeRet, int& nChangePosRet,
std::string& strFailReason, const CCoinControl *coinControl = NULL, bool sign = true); std::string& strFailReason, const CCoinControl *coinControl = NULL, bool sign = true);
bool CommitTransaction(CWalletTx& wtxNew, CReserveKey& reservekey); bool CommitTransaction(CWalletTx& wtxNew, CReserveKey& reservekey);

Loading…
Cancel
Save