Consensus: Minimal way to move dust out of consensus
This commit is contained in:
parent
35da2aeed7
commit
330bb5a456
8 changed files with 54 additions and 50 deletions
|
@ -15,6 +15,43 @@
|
||||||
|
|
||||||
#include <boost/foreach.hpp>
|
#include <boost/foreach.hpp>
|
||||||
|
|
||||||
|
CAmount GetDustThreshold(const CTxOut& txout, const CFeeRate& dustRelayFee)
|
||||||
|
{
|
||||||
|
// "Dust" is defined in terms of dustRelayFee,
|
||||||
|
// which has units satoshis-per-kilobyte.
|
||||||
|
// If you'd pay more than 1/3 in fees
|
||||||
|
// to spend something, then we consider it dust.
|
||||||
|
// A typical spendable non-segwit txout is 34 bytes big, and will
|
||||||
|
// need a CTxIn of at least 148 bytes to spend:
|
||||||
|
// so dust is a spendable txout less than
|
||||||
|
// 546*dustRelayFee/1000 (in satoshis).
|
||||||
|
// A typical spendable segwit txout is 31 bytes big, and will
|
||||||
|
// need a CTxIn of at least 67 bytes to spend:
|
||||||
|
// so dust is a spendable txout less than
|
||||||
|
// 294*dustRelayFee/1000 (in satoshis).
|
||||||
|
if (txout.scriptPubKey.IsUnspendable())
|
||||||
|
return 0;
|
||||||
|
|
||||||
|
size_t nSize = GetSerializeSize(txout, SER_DISK, 0);
|
||||||
|
int witnessversion = 0;
|
||||||
|
std::vector<unsigned char> witnessprogram;
|
||||||
|
|
||||||
|
if (txout.scriptPubKey.IsWitnessProgram(witnessversion, witnessprogram)) {
|
||||||
|
// sum the sizes of the parts of a transaction input
|
||||||
|
// with 75% segwit discount applied to the script size.
|
||||||
|
nSize += (32 + 4 + 1 + (107 / WITNESS_SCALE_FACTOR) + 4);
|
||||||
|
} else {
|
||||||
|
nSize += (32 + 4 + 1 + 107 + 4); // the 148 mentioned above
|
||||||
|
}
|
||||||
|
|
||||||
|
return 3 * dustRelayFee.GetFee(nSize);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool IsDust(const CTxOut& txout, const CFeeRate& dustRelayFee)
|
||||||
|
{
|
||||||
|
return (txout.nValue < GetDustThreshold(txout, dustRelayFee));
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Check transaction inputs to mitigate two
|
* Check transaction inputs to mitigate two
|
||||||
* potential denial-of-service attacks:
|
* potential denial-of-service attacks:
|
||||||
|
@ -106,7 +143,7 @@ bool IsStandardTx(const CTransaction& tx, std::string& reason, const bool witnes
|
||||||
else if ((whichType == TX_MULTISIG) && (!fIsBareMultisigStd)) {
|
else if ((whichType == TX_MULTISIG) && (!fIsBareMultisigStd)) {
|
||||||
reason = "bare-multisig";
|
reason = "bare-multisig";
|
||||||
return false;
|
return false;
|
||||||
} else if (txout.IsDust(dustRelayFee)) {
|
} else if (IsDust(txout, ::dustRelayFee)) {
|
||||||
reason = "dust";
|
reason = "dust";
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
|
@ -13,6 +13,7 @@
|
||||||
#include <string>
|
#include <string>
|
||||||
|
|
||||||
class CCoinsViewCache;
|
class CCoinsViewCache;
|
||||||
|
class CTxOut;
|
||||||
|
|
||||||
/** Default for -blockmaxsize, which controls the maximum size of block the mining code will create **/
|
/** Default for -blockmaxsize, which controls the maximum size of block the mining code will create **/
|
||||||
static const unsigned int DEFAULT_BLOCK_MAX_SIZE = 750000;
|
static const unsigned int DEFAULT_BLOCK_MAX_SIZE = 750000;
|
||||||
|
@ -72,6 +73,10 @@ static const unsigned int STANDARD_NOT_MANDATORY_VERIFY_FLAGS = STANDARD_SCRIPT_
|
||||||
static const unsigned int STANDARD_LOCKTIME_VERIFY_FLAGS = LOCKTIME_VERIFY_SEQUENCE |
|
static const unsigned int STANDARD_LOCKTIME_VERIFY_FLAGS = LOCKTIME_VERIFY_SEQUENCE |
|
||||||
LOCKTIME_MEDIAN_TIME_PAST;
|
LOCKTIME_MEDIAN_TIME_PAST;
|
||||||
|
|
||||||
|
CAmount GetDustThreshold(const CTxOut& txout, const CFeeRate& dustRelayFee);
|
||||||
|
|
||||||
|
bool IsDust(const CTxOut& txout, const CFeeRate& dustRelayFee);
|
||||||
|
|
||||||
bool IsStandard(const CScript& scriptPubKey, txnouttype& whichType, const bool witnessEnabled = false);
|
bool IsStandard(const CScript& scriptPubKey, txnouttype& whichType, const bool witnessEnabled = false);
|
||||||
/**
|
/**
|
||||||
* Check for standard transaction types
|
* Check for standard transaction types
|
||||||
|
|
|
@ -161,43 +161,6 @@ public:
|
||||||
return (nValue == -1);
|
return (nValue == -1);
|
||||||
}
|
}
|
||||||
|
|
||||||
CAmount GetDustThreshold(const CFeeRate &minRelayTxFee) const
|
|
||||||
{
|
|
||||||
// "Dust" is defined in terms of CTransaction::minRelayTxFee,
|
|
||||||
// which has units satoshis-per-kilobyte.
|
|
||||||
// If you'd pay more than 1/3 in fees
|
|
||||||
// to spend something, then we consider it dust.
|
|
||||||
// A typical spendable non-segwit txout is 34 bytes big, and will
|
|
||||||
// need a CTxIn of at least 148 bytes to spend:
|
|
||||||
// so dust is a spendable txout less than
|
|
||||||
// 546*minRelayTxFee/1000 (in satoshis).
|
|
||||||
// A typical spendable segwit txout is 31 bytes big, and will
|
|
||||||
// need a CTxIn of at least 67 bytes to spend:
|
|
||||||
// so dust is a spendable txout less than
|
|
||||||
// 294*minRelayTxFee/1000 (in satoshis).
|
|
||||||
if (scriptPubKey.IsUnspendable())
|
|
||||||
return 0;
|
|
||||||
|
|
||||||
size_t nSize = GetSerializeSize(*this, SER_DISK, 0);
|
|
||||||
int witnessversion = 0;
|
|
||||||
std::vector<unsigned char> witnessprogram;
|
|
||||||
|
|
||||||
if (scriptPubKey.IsWitnessProgram(witnessversion, witnessprogram)) {
|
|
||||||
// sum the sizes of the parts of a transaction input
|
|
||||||
// with 75% segwit discount applied to the script size.
|
|
||||||
nSize += (32 + 4 + 1 + (107 / WITNESS_SCALE_FACTOR) + 4);
|
|
||||||
} else {
|
|
||||||
nSize += (32 + 4 + 1 + 107 + 4); // the 148 mentioned above
|
|
||||||
}
|
|
||||||
|
|
||||||
return 3 * minRelayTxFee.GetFee(nSize);
|
|
||||||
}
|
|
||||||
|
|
||||||
bool IsDust(const CFeeRate &minRelayTxFee) const
|
|
||||||
{
|
|
||||||
return (nValue < GetDustThreshold(minRelayTxFee));
|
|
||||||
}
|
|
||||||
|
|
||||||
friend bool operator==(const CTxOut& a, const CTxOut& b)
|
friend bool operator==(const CTxOut& a, const CTxOut& b)
|
||||||
{
|
{
|
||||||
return (a.nValue == b.nValue &&
|
return (a.nValue == b.nValue &&
|
||||||
|
|
|
@ -434,8 +434,7 @@ void CoinControlDialog::updateLabels(WalletModel *model, QDialog* dialog)
|
||||||
{
|
{
|
||||||
CTxOut txout(amount, (CScript)std::vector<unsigned char>(24, 0));
|
CTxOut txout(amount, (CScript)std::vector<unsigned char>(24, 0));
|
||||||
txDummy.vout.push_back(txout);
|
txDummy.vout.push_back(txout);
|
||||||
if (txout.IsDust(dustRelayFee))
|
fDust |= IsDust(txout, ::dustRelayFee);
|
||||||
fDust = true;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -527,10 +526,10 @@ void CoinControlDialog::updateLabels(WalletModel *model, QDialog* dialog)
|
||||||
if (nChange > 0 && nChange < MIN_CHANGE)
|
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(dustRelayFee))
|
if (IsDust(txout, ::dustRelayFee))
|
||||||
{
|
{
|
||||||
if (CoinControlDialog::fSubtractFeeFromAmount) // dust-change will be raised until no dust
|
if (CoinControlDialog::fSubtractFeeFromAmount) // dust-change will be raised until no dust
|
||||||
nChange = txout.GetDustThreshold(dustRelayFee);
|
nChange = GetDustThreshold(txout, ::dustRelayFee);
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
nPayFee += nChange;
|
nPayFee += nChange;
|
||||||
|
|
|
@ -251,7 +251,7 @@ bool isDust(const QString& address, const CAmount& amount)
|
||||||
CTxDestination dest = CBitcoinAddress(address.toStdString()).Get();
|
CTxDestination dest = CBitcoinAddress(address.toStdString()).Get();
|
||||||
CScript script = GetScriptForDestination(dest);
|
CScript script = GetScriptForDestination(dest);
|
||||||
CTxOut txOut(amount, script);
|
CTxOut txOut(amount, script);
|
||||||
return txOut.IsDust(dustRelayFee);
|
return IsDust(txOut, ::dustRelayFee);
|
||||||
}
|
}
|
||||||
|
|
||||||
QString HtmlEscape(const QString& str, bool fMultiLine)
|
QString HtmlEscape(const QString& str, bool fMultiLine)
|
||||||
|
|
|
@ -580,7 +580,7 @@ bool PaymentServer::processPaymentRequest(const PaymentRequestPlus& request, Sen
|
||||||
|
|
||||||
// Extract and check amounts
|
// Extract and check amounts
|
||||||
CTxOut txOut(sendingTo.second, sendingTo.first);
|
CTxOut txOut(sendingTo.second, sendingTo.first);
|
||||||
if (txOut.IsDust(dustRelayFee)) {
|
if (IsDust(txOut, ::dustRelayFee)) {
|
||||||
Q_EMIT message(tr("Payment request error"), tr("Requested payment amount of %1 is too small (considered dust).")
|
Q_EMIT message(tr("Payment request error"), tr("Requested payment amount of %1 is too small (considered dust).")
|
||||||
.arg(BitcoinUnits::formatWithUnit(optionsModel->getDisplayUnit(), sendingTo.second)),
|
.arg(BitcoinUnits::formatWithUnit(optionsModel->getDisplayUnit(), sendingTo.second)),
|
||||||
CClientUIInterface::MSG_ERROR);
|
CClientUIInterface::MSG_ERROR);
|
||||||
|
|
|
@ -214,7 +214,7 @@ CFeeBumper::CFeeBumper(const CWallet *pWallet, const uint256 txidIn, int newConf
|
||||||
|
|
||||||
// If the output would become dust, discard it (converting the dust to fee)
|
// If the output would become dust, discard it (converting the dust to fee)
|
||||||
poutput->nValue -= nDelta;
|
poutput->nValue -= nDelta;
|
||||||
if (poutput->nValue <= poutput->GetDustThreshold(::dustRelayFee)) {
|
if (poutput->nValue <= GetDustThreshold(*poutput, ::dustRelayFee)) {
|
||||||
LogPrint(BCLog::RPC, "Bumping fee and discarding dust output\n");
|
LogPrint(BCLog::RPC, "Bumping fee and discarding dust output\n");
|
||||||
nNewFee += poutput->nValue;
|
nNewFee += poutput->nValue;
|
||||||
mtx.vout.erase(mtx.vout.begin() + nOutput);
|
mtx.vout.erase(mtx.vout.begin() + nOutput);
|
||||||
|
|
|
@ -2449,7 +2449,7 @@ bool CWallet::CreateTransaction(const std::vector<CRecipient>& vecSend, CWalletT
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (txout.IsDust(dustRelayFee))
|
if (IsDust(txout, ::dustRelayFee))
|
||||||
{
|
{
|
||||||
if (recipient.fSubtractFeeFromAmount && nFeeRet > 0)
|
if (recipient.fSubtractFeeFromAmount && nFeeRet > 0)
|
||||||
{
|
{
|
||||||
|
@ -2514,16 +2514,16 @@ bool CWallet::CreateTransaction(const std::vector<CRecipient>& vecSend, CWalletT
|
||||||
// We do not move dust-change to fees, because the sender would end up paying more than requested.
|
// 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.
|
// This would be against the purpose of the all-inclusive feature.
|
||||||
// So instead we raise the change and deduct from the recipient.
|
// So instead we raise the change and deduct from the recipient.
|
||||||
if (nSubtractFeeFromAmount > 0 && newTxOut.IsDust(dustRelayFee))
|
if (nSubtractFeeFromAmount > 0 && IsDust(newTxOut, ::dustRelayFee))
|
||||||
{
|
{
|
||||||
CAmount nDust = newTxOut.GetDustThreshold(dustRelayFee) - newTxOut.nValue;
|
CAmount nDust = GetDustThreshold(newTxOut, ::dustRelayFee) - newTxOut.nValue;
|
||||||
newTxOut.nValue += nDust; // raise change until no more dust
|
newTxOut.nValue += nDust; // raise change until no more dust
|
||||||
for (unsigned int i = 0; i < vecSend.size(); i++) // subtract from first recipient
|
for (unsigned int i = 0; i < vecSend.size(); i++) // subtract from first recipient
|
||||||
{
|
{
|
||||||
if (vecSend[i].fSubtractFeeFromAmount)
|
if (vecSend[i].fSubtractFeeFromAmount)
|
||||||
{
|
{
|
||||||
txNew.vout[i].nValue -= nDust;
|
txNew.vout[i].nValue -= nDust;
|
||||||
if (txNew.vout[i].IsDust(dustRelayFee))
|
if (IsDust(txNew.vout[i], ::dustRelayFee))
|
||||||
{
|
{
|
||||||
strFailReason = _("The transaction amount is too small to send after the fee has been deducted");
|
strFailReason = _("The transaction amount is too small to send after the fee has been deducted");
|
||||||
return false;
|
return false;
|
||||||
|
@ -2535,7 +2535,7 @@ bool CWallet::CreateTransaction(const std::vector<CRecipient>& vecSend, CWalletT
|
||||||
|
|
||||||
// Never create dust outputs; if we would, just
|
// Never create dust outputs; if we would, just
|
||||||
// add the dust to the fee.
|
// add the dust to the fee.
|
||||||
if (newTxOut.IsDust(dustRelayFee))
|
if (IsDust(newTxOut, ::dustRelayFee))
|
||||||
{
|
{
|
||||||
nChangePosInOut = -1;
|
nChangePosInOut = -1;
|
||||||
nFeeRet += nChange;
|
nFeeRet += nChange;
|
||||||
|
|
Loading…
Reference in a new issue