Skip to content

Commit fd6afa6

Browse files
authored
Merge pull request #2029 from jamescowens/fee_fix
wallet: Fix bug in CreateTransaction causing insufficient fees
2 parents 9a023cf + 97bdc01 commit fd6afa6

File tree

2 files changed

+132
-22
lines changed

2 files changed

+132
-22
lines changed

src/main.cpp

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -992,9 +992,9 @@ bool AcceptToMemoryPool(CTxMemPool& pool, CTransaction &tx, bool* pfMissingInput
992992
// Don't accept it if it can't get into a block
993993
int64_t txMinFee = tx.GetMinFee(1000, GMF_RELAY, nSize);
994994
if (nFees < txMinFee)
995-
return error("AcceptToMemoryPool : not enough fees %s, %" PRId64 " < %" PRId64,
995+
return error("AcceptToMemoryPool : not enough fees %s, %" PRId64 " < %" PRId64 ", nSize %" PRId64,
996996
hash.ToString().c_str(),
997-
nFees, txMinFee);
997+
nFees, txMinFee, nSize);
998998

999999
// Continuously rate-limit free transactions
10001000
// This mitigates 'penny-flooding' -- sending thousands of free transactions just to

src/wallet/wallet.cpp

Lines changed: 130 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -1673,6 +1673,8 @@ bool CWallet::SelectCoins(int64_t nTargetValue, unsigned int nSpendTime, set<pai
16731673
}
16741674

16751675
if (contract) {
1676+
LogPrint(BCLog::LogFlags::ESTIMATEFEE, "INFO %s: Contract is included so SelectSmallestCoins will be used.", __func__);
1677+
16761678
return (SelectSmallestCoins(nTargetValue, nSpendTime, 1, 10, vCoins, setCoinsRet, nValueRet) ||
16771679
SelectSmallestCoins(nTargetValue, nSpendTime, 1, 1, vCoins, setCoinsRet, nValueRet) ||
16781680
SelectSmallestCoins(nTargetValue, nSpendTime, 0, 1, vCoins, setCoinsRet, nValueRet));
@@ -1795,12 +1797,15 @@ bool CWallet::SelectCoinsForStaking(unsigned int nSpendTime, std::vector<pair<co
17951797
return true;
17961798
}
17971799

1798-
bool CWallet::CreateTransaction(const vector<pair<CScript, int64_t> >& vecSend, set<pair<const CWalletTx*,unsigned int>>& setCoins,
1800+
bool CWallet::CreateTransaction(const vector<pair<CScript, int64_t> >& vecSend, set<pair<const CWalletTx*,unsigned int>>& setCoins_in,
17991801
CWalletTx& wtxNew, CReserveKey& reservekey, int64_t& nFeeRet, const CCoinControl* coinControl)
18001802
{
18011803

18021804
int64_t nValueOut = 0;
18031805
int64_t message_fee = 0;
1806+
set<pair<const CWalletTx*,unsigned int>> setCoins_out;
1807+
1808+
bool provided_coin_set = !setCoins_in.empty();
18041809

18051810
for (auto const& s : vecSend)
18061811
{
@@ -1833,10 +1838,13 @@ bool CWallet::CreateTransaction(const vector<pair<CScript, int64_t> >& vecSend,
18331838
{
18341839
wtxNew.vin.clear();
18351840
wtxNew.vout.clear();
1841+
setCoins_out.clear();
18361842
wtxNew.fFromMe = true;
18371843

18381844
int64_t nTotalValue = nValueOut + nFeeRet;
1839-
double dPriority = 0;
1845+
// dPriority is not currently used. Commented out.
1846+
//double dPriority = 0;
1847+
18401848
// vouts to the payees
18411849
for (auto const& s : vecSend)
18421850
wtxNew.vout.emplace_back(s.second, s.first);
@@ -1850,7 +1858,7 @@ bool CWallet::CreateTransaction(const vector<pair<CScript, int64_t> >& vecSend,
18501858
int64_t nValueIn = 0;
18511859

18521860
// If provided coin set is empty, choose coins to use.
1853-
if (!setCoins.size())
1861+
if (!provided_coin_set)
18541862
{
18551863
// If the transaction contains a contract, we want to select the
18561864
// smallest UTXOs available:
@@ -1862,26 +1870,78 @@ bool CWallet::CreateTransaction(const vector<pair<CScript, int64_t> >& vecSend,
18621870
&& !wtxNew.vContracts.empty()
18631871
&& wtxNew.vContracts[0].m_type != GRC::ContractType::MESSAGE;
18641872

1865-
if (!SelectCoins(nTotalValue, wtxNew.nTime, setCoins, nValueIn, coinControl, contract)) {
1873+
// Notice that setCoins_out is that set PRODUCED by SelectCoins. Tying this to the input
1874+
// parameter of CreateTransaction was a major bug here before. It is now separated.
1875+
if (!SelectCoins(nTotalValue, wtxNew.nTime, setCoins_out, nValueIn, coinControl, contract)) {
18661876
return error("%s: Failed to select coins", __func__);
18671877
}
1878+
1879+
if (LogInstance().WillLogCategory(BCLog::LogFlags::ESTIMATEFEE))
1880+
{
1881+
CAmount setcoins_total = 0;
1882+
1883+
for (const auto& output: setCoins_out)
1884+
{
1885+
setcoins_total += output.first->vout[output.second].nValue;
1886+
}
1887+
1888+
LogPrintf("INFO %s: Just after SelectCoins: "
1889+
"nTotalValue = %s, nValueIn = %s, nValueOut = %s, setCoins total = %s.",
1890+
__func__,
1891+
FormatMoney(nTotalValue),
1892+
FormatMoney(nValueIn),
1893+
FormatMoney(nValueOut),
1894+
FormatMoney(setcoins_total));
1895+
}
1896+
1897+
// Compute dPriority on automatically selected coins.
1898+
/*
1899+
for (auto const& input : setCoins_out)
1900+
{
1901+
int64_t nCredit = input.first->vout[input.second].nValue;
1902+
dPriority += (double) nCredit * input.first->GetDepthInMainChain();
1903+
}
1904+
*/
18681905
}
18691906
else
18701907
{
1871-
// Add up input value for the provided set of coins.
1872-
for (auto const& input : setCoins)
1908+
// Add up input value for the provided set of coins, and also compute dPriority. (dPriority is
1909+
// commented out because it is not actually used right now.
1910+
for (auto const& input : setCoins_in)
18731911
{
1874-
nValueIn += input.first->vout[input.second].nValue;
1912+
int64_t nCredit = input.first->vout[input.second].nValue;
1913+
//dPriority += (double) nCredit * input.first->GetDepthInMainChain();
1914+
nValueIn += nCredit;
18751915
}
18761916
}
18771917

1878-
for (auto const& pcoin : setCoins)
1918+
int64_t nChange = nValueIn - nValueOut - nFeeRet;
1919+
1920+
// Note: In the case where CreateTransaction is called with a provided input set of coins,
1921+
// if the nValueIn of those coins is sufficient to cover the minimum nTransactionFee that starts
1922+
// the while loop, it will pass the first iteration. If the size of the transaction causes the nFeeRet
1923+
// to elevate and a second pass shows that the nValueOut + required fee is greater than that available
1924+
// i.e. negative change, then the loop is exited with an error. The reasoning for this is that
1925+
// in the case of no provided coin set, SelectTransaction above will be given the chance to modify its
1926+
// selection to cover the increased fees, hopefully converging on an appropriate solution. In the case
1927+
// of a provided set of inputs, that set is immutable for this transaction, so no point in continuing.
1928+
if (provided_coin_set && nChange < 0)
18791929
{
1880-
int64_t nCredit = pcoin.first->vout[pcoin.second].nValue;
1881-
dPriority += (double)nCredit * pcoin.first->GetDepthInMainChain();
1930+
return error("%s: Total value of inputs, %s, cannot cover the transaction fees of %s. "
1931+
"CreateTransaction aborted.",
1932+
__func__,
1933+
FormatMoney(nValueIn),
1934+
FormatMoney(nFeeRet));
18821935
}
18831936

1884-
int64_t nChange = nValueIn - nValueOut - nFeeRet;
1937+
LogPrint(BCLog::LogFlags::ESTIMATEFEE, "INFO %s: Before CENT test: nValueIn = %s, nValueOut = %s, "
1938+
"nChange = %s, nFeeRet = %s.",
1939+
__func__,
1940+
FormatMoney(nValueIn),
1941+
FormatMoney(nValueOut),
1942+
FormatMoney(nChange),
1943+
FormatMoney(nFeeRet));
1944+
18851945
// if sub-cent change is required, the fee must be raised to at least GetBaseFee
18861946
// or until nChange becomes zero
18871947
// NOTE: this depends on the exact behaviour of GetMinFee
@@ -1890,6 +1950,12 @@ bool CWallet::CreateTransaction(const vector<pair<CScript, int64_t> >& vecSend,
18901950
int64_t nMoveToFee = min(nChange, wtxNew.GetBaseFee() - nFeeRet);
18911951
nChange -= nMoveToFee;
18921952
nFeeRet += nMoveToFee;
1953+
1954+
LogPrint(BCLog::LogFlags::ESTIMATEFEE, "INFO %s: After CENT limit adjustment: nChange = %s, "
1955+
"nFeeRet = %s",
1956+
__func__,
1957+
FormatMoney(nChange),
1958+
FormatMoney(nFeeRet));
18931959
}
18941960

18951961
if (nChange > 0)
@@ -1925,37 +1991,81 @@ bool CWallet::CreateTransaction(const vector<pair<CScript, int64_t> >& vecSend,
19251991
wtxNew.vout.insert(position, CTxOut(nChange, scriptChange));
19261992
}
19271993
else
1994+
{
19281995
reservekey.ReturnKey();
1996+
}
19291997

1930-
// Fill vin
1931-
for (auto const& coin : setCoins)
1932-
wtxNew.vin.push_back(CTxIn(coin.first->GetHash(),coin.second));
1998+
if (setCoins_in.size())
1999+
{
2000+
// Fill vin from provided inputs
2001+
for (auto const& coin : setCoins_in)
2002+
{
2003+
wtxNew.vin.push_back(CTxIn(coin.first->GetHash(),coin.second));
2004+
}
19332005

1934-
// Sign
1935-
int nIn = 0;
1936-
for (auto const& coin : setCoins)
1937-
if (!SignSignature(*this, *coin.first, wtxNew, nIn++)) {
1938-
return error("%s: Failed to sign tx", __func__);
2006+
// Sign
2007+
int nIn = 0;
2008+
for (auto const& coin : setCoins_in)
2009+
if (!SignSignature(*this, *coin.first, wtxNew, nIn++)) {
2010+
return error("%s: Failed to sign tx", __func__);
2011+
}
2012+
}
2013+
else // use setCoins_out from SelectCoins as the inputs
2014+
{
2015+
// Fill vin from provided inputs
2016+
for (auto const& coin : setCoins_out)
2017+
{
2018+
wtxNew.vin.push_back(CTxIn(coin.first->GetHash(),coin.second));
19392019
}
19402020

2021+
// Sign
2022+
int nIn = 0;
2023+
for (auto const& coin : setCoins_out)
2024+
if (!SignSignature(*this, *coin.first, wtxNew, nIn++))
2025+
{
2026+
return error("%s: Failed to sign tx", __func__);
2027+
}
2028+
}
2029+
19412030
// Limit size
19422031
unsigned int nBytes = ::GetSerializeSize(*(CTransaction*)&wtxNew, SER_NETWORK, PROTOCOL_VERSION);
19432032
if (nBytes >= MAX_STANDARD_TX_SIZE) {
19442033
return error("%s: tx size %d greater than standard %d", __func__, nBytes, MAX_STANDARD_TX_SIZE);
19452034
}
19462035

1947-
dPriority /= nBytes;
2036+
// dPriority is not currently used.
2037+
//dPriority /= nBytes;
19482038

19492039
// Check that enough fee is included
19502040
int64_t nPayFee = nTransactionFee * (1 + (int64_t)nBytes / 1000);
19512041
int64_t nMinFee = wtxNew.GetMinFee(1000, GMF_SEND, nBytes);
19522042

2043+
LogPrint(BCLog::LogFlags::ESTIMATEFEE, "INFO %s: nTransactionFee = %s, nBytes = %" PRId64 ", nPayFee = %s"
2044+
", nMinFee = %s, nFeeRet = %s.",
2045+
__func__,
2046+
FormatMoney(nTransactionFee),
2047+
nBytes,
2048+
FormatMoney(nPayFee),
2049+
FormatMoney(nMinFee),
2050+
FormatMoney(nFeeRet));
2051+
19532052
if (nFeeRet < max(nPayFee, nMinFee))
19542053
{
19552054
nFeeRet = max(nPayFee, nMinFee);
19562055
continue;
19572056
}
19582057

2058+
LogPrint(BCLog::LogFlags::ESTIMATEFEE, "INFO %s: FINAL nValueIn = %s, nChange = %s, nTransactionFee = %s,"
2059+
" nBytes = %" PRId64 ", nPayFee = %s, nMinFee = %s, nFeeRet = %s.",
2060+
__func__,
2061+
FormatMoney(nValueIn),
2062+
FormatMoney(nChange),
2063+
FormatMoney(nTransactionFee),
2064+
nBytes,
2065+
FormatMoney(nPayFee),
2066+
FormatMoney(nMinFee),
2067+
FormatMoney(nFeeRet));
2068+
19592069
// Fill vtxPrev by copying from previous transactions vtxPrev
19602070
wtxNew.AddSupportingTransactions(txdb);
19612071
wtxNew.fTimeReceivedIsTxTime = true;

0 commit comments

Comments
 (0)