[wallet] Refactor to use new MIN_CHANGE
* Introduce new constant MIN_CHANGE and use it instead of the hardcoded "CENT" * Add test case for MIN_CHANGE * Introduce new constant for -mintxfee default: DEFAULT_TRANSACTION_MINFEE = 1000
This commit is contained in:
parent
0143a1f228
commit
6b0e622c25
4 changed files with 84 additions and 55 deletions
|
@ -567,7 +567,7 @@ void CoinControlDialog::updateLabels(WalletModel *model, QDialog* dialog)
|
|||
nChange -= nPayFee;
|
||||
|
||||
// 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));
|
||||
if (txout.IsDust(::minRelayTxFee))
|
||||
|
|
|
@ -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
|
||||
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)
|
||||
|
||||
// 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(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();
|
||||
add_coin(0.1*CENT);
|
||||
add_coin(0.2*CENT);
|
||||
add_coin(0.3*CENT);
|
||||
add_coin(0.4*CENT);
|
||||
add_coin(0.5*CENT);
|
||||
add_coin(0.1*MIN_CHANGE);
|
||||
add_coin(0.2*MIN_CHANGE);
|
||||
add_coin(0.3*MIN_CHANGE);
|
||||
add_coin(0.4*MIN_CHANGE);
|
||||
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);
|
||||
// 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, making it possible to avoid sub-cent change, things change:
|
||||
add_coin(1111*CENT);
|
||||
// but if we add a bigger coin, small change is avoided
|
||||
add_coin(1111*MIN_CHANGE);
|
||||
|
||||
// try making 1 cent from 0.1 + 0.2 + 0.3 + 0.4 + 0.5 + 1111 = 1112.5 cents
|
||||
BOOST_CHECK( wallet.SelectCoinsMinConf(1 * CENT, 1, 1, vCoins, setCoinsRet, nValueRet));
|
||||
BOOST_CHECK_EQUAL(nValueRet, 1 * CENT); // we should get the exact amount
|
||||
// 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 sub-cent coins:
|
||||
add_coin(0.6*CENT);
|
||||
add_coin(0.7*CENT);
|
||||
// if we add more small coins:
|
||||
add_coin(0.6*MIN_CHANGE);
|
||||
add_coin(0.7*MIN_CHANGE);
|
||||
|
||||
// and try again to make 1.0 cents, we can still make 1.0 cents
|
||||
BOOST_CHECK( wallet.SelectCoinsMinConf(1 * CENT, 1, 1, vCoins, setCoinsRet, nValueRet));
|
||||
BOOST_CHECK_EQUAL(nValueRet, 1 * CENT); // we should get the exact amount
|
||||
// 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
|
||||
|
@ -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(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
|
||||
|
||||
// sometimes it will fail, and so we use the next biggest coin:
|
||||
empty_wallet();
|
||||
add_coin(0.5 * CENT);
|
||||
add_coin(0.6 * CENT);
|
||||
add_coin(0.7 * CENT);
|
||||
add_coin(1111 * CENT);
|
||||
BOOST_CHECK( wallet.SelectCoinsMinConf(1 * CENT, 1, 1, vCoins, setCoinsRet, nValueRet));
|
||||
BOOST_CHECK_EQUAL(nValueRet, 1111 * CENT); // we get the bigger coin
|
||||
add_coin(0.5 * MIN_CHANGE);
|
||||
add_coin(0.6 * MIN_CHANGE);
|
||||
add_coin(0.7 * MIN_CHANGE);
|
||||
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(0.4 * CENT);
|
||||
add_coin(0.6 * CENT);
|
||||
add_coin(0.8 * CENT);
|
||||
add_coin(1111 * CENT);
|
||||
BOOST_CHECK( wallet.SelectCoinsMinConf(1 * CENT, 1, 1, vCoins, setCoinsRet, nValueRet));
|
||||
BOOST_CHECK_EQUAL(nValueRet, 1 * CENT); // we should get the exact amount
|
||||
add_coin(0.4 * MIN_CHANGE);
|
||||
add_coin(0.6 * MIN_CHANGE);
|
||||
add_coin(0.8 * MIN_CHANGE);
|
||||
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 sub-cent change
|
||||
// test avoiding small change
|
||||
empty_wallet();
|
||||
add_coin(0.0005 * COIN);
|
||||
add_coin(0.01 * COIN);
|
||||
add_coin(1 * COIN);
|
||||
add_coin(0.05 * MIN_CHANGE);
|
||||
add_coin(1 * MIN_CHANGE);
|
||||
add_coin(100 * MIN_CHANGE);
|
||||
|
||||
// trying to make 1.0001 from these three coins
|
||||
BOOST_CHECK( wallet.SelectCoinsMinConf(1.0001 * COIN, 1, 1, vCoins, setCoinsRet, nValueRet));
|
||||
BOOST_CHECK_EQUAL(nValueRet, 1.0105 * COIN); // we should get all coins
|
||||
// trying to make 100.01 from these three coins
|
||||
BOOST_CHECK( wallet.SelectCoinsMinConf(100.01 * MIN_CHANGE, 1, 1, vCoins, setCoinsRet, nValueRet));
|
||||
BOOST_CHECK_EQUAL(nValueRet, 101.05 * MIN_CHANGE); // we should get all coins
|
||||
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
|
||||
BOOST_CHECK( wallet.SelectCoinsMinConf(0.999 * COIN, 1, 1, vCoins, setCoinsRet, nValueRet));
|
||||
BOOST_CHECK_EQUAL(nValueRet, 1.01 * COIN); // we should get 1 + 0.01
|
||||
// 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(99.9 * MIN_CHANGE, 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);
|
||||
}
|
||||
}
|
||||
|
||||
// test randomness
|
||||
{
|
||||
empty_wallet();
|
||||
|
|
|
@ -47,7 +47,7 @@ bool fPayAtLeastCustomFee = true;
|
|||
* Fees smaller than this (in satoshi) are considered zero fee (for transaction creation)
|
||||
* Override with -mintxfee
|
||||
*/
|
||||
CFeeRate CWallet::minTxFee = CFeeRate(1000);
|
||||
CFeeRate CWallet::minTxFee = CFeeRate(DEFAULT_TRANSACTION_MINFEE);
|
||||
|
||||
/** @defgroup mapWallet
|
||||
*
|
||||
|
@ -1498,9 +1498,6 @@ CAmount CWallet::GetImmatureWatchOnlyBalance() const
|
|||
return nTotal;
|
||||
}
|
||||
|
||||
/**
|
||||
* populate vCoins with vector of available COutputs.
|
||||
*/
|
||||
void CWallet::AvailableCoins(vector<COutput>& vCoins, bool fOnlyConfirmed, const CCoinControl *coinControl, bool fIncludeZeroValue) const
|
||||
{
|
||||
vCoins.clear();
|
||||
|
@ -1620,7 +1617,7 @@ bool CWallet::SelectCoinsMinConf(const CAmount& nTargetValue, int nConfMine, int
|
|||
nValueRet += coin.first;
|
||||
return true;
|
||||
}
|
||||
else if (n < nTargetValue + CENT)
|
||||
else if (n < nTargetValue + MIN_CHANGE)
|
||||
{
|
||||
vValue.push_back(coin);
|
||||
nTotalLower += n;
|
||||
|
@ -1655,14 +1652,14 @@ bool CWallet::SelectCoinsMinConf(const CAmount& nTargetValue, int nConfMine, int
|
|||
vector<char> vfBest;
|
||||
CAmount nBest;
|
||||
|
||||
ApproximateBestSubset(vValue, nTotalLower, nTargetValue, vfBest, nBest, 1000);
|
||||
if (nBest != nTargetValue && nTotalLower >= nTargetValue + CENT)
|
||||
ApproximateBestSubset(vValue, nTotalLower, nTargetValue + CENT, vfBest, nBest, 1000);
|
||||
ApproximateBestSubset(vValue, nTotalLower, nTargetValue, vfBest, nBest);
|
||||
if (nBest != nTargetValue && nTotalLower >= nTargetValue + MIN_CHANGE)
|
||||
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,
|
||||
// or the next bigger coin is closer), return the bigger coin
|
||||
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);
|
||||
nValueRet += coinLowestLarger.first;
|
||||
|
|
|
@ -41,8 +41,12 @@ extern bool fPayAtLeastCustomFee;
|
|||
static const CAmount DEFAULT_TRANSACTION_FEE = 0;
|
||||
//! -paytxfee will warn if called with a higher fee than this amount (in satoshis) per KB
|
||||
static const CAmount nHighTransactionFeeWarning = 0.01 * COIN;
|
||||
//! -mintxfee default
|
||||
static const CAmount DEFAULT_TRANSACTION_MINFEE = 1000;
|
||||
//! -maxtxfee default
|
||||
static const CAmount DEFAULT_TRANSACTION_MAXFEE = 0.1 * COIN;
|
||||
//! minimum change amount
|
||||
static const CAmount MIN_CHANGE = CENT;
|
||||
//! -txconfirmtarget default
|
||||
static const unsigned int DEFAULT_TX_CONFIRM_TARGET = 2;
|
||||
//! -maxtxfee will warn if called with a higher fee than this amount (in satoshis)
|
||||
|
@ -544,7 +548,14 @@ public:
|
|||
//! 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; }
|
||||
|
||||
/**
|
||||
* populate vCoins with vector of available COutputs.
|
||||
*/
|
||||
void AvailableCoins(std::vector<COutput>& vCoins, bool fOnlyConfirmed=true, const CCoinControl *coinControl = NULL, bool fIncludeZeroValue=false) const;
|
||||
/**
|
||||
* Select coins until nTargetValue is reached. Return the actual value
|
||||
* and the corresponding coin set.
|
||||
*/
|
||||
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;
|
||||
|
|
Loading…
Reference in a new issue