From a2d67b52d688d3044927a3b534d0450b6559f5cd Mon Sep 17 00:00:00 2001 From: CryptoManiac Date: Thu, 10 Jul 2014 21:21:11 +0400 Subject: [PATCH] New transaction fees scheme * Enable free user transactions since 1 July 2014; * Force CENT as nMinTxFee and nMinRelayTxFee until 1 July 2014; * minimum fees adjustments. --- novacoin-qt.pro | 1 + src/main.cpp | 73 ++++++++++++++++++++++++++++++---------- src/main.h | 17 +++------ src/miner.cpp | 2 +- src/qt/coincontroldialog.cpp | 75 ++++++++++++++++++++++++------------------ src/timestamps.h | 13 +++++++ src/wallet.cpp | 37 +++++++++++++------- 7 files changed, 142 insertions(+), 76 deletions(-) create mode 100644 src/timestamps.h diff --git a/novacoin-qt.pro b/novacoin-qt.pro index a59787a..1f0acc3 100644 --- a/novacoin-qt.pro +++ b/novacoin-qt.pro @@ -162,6 +162,7 @@ HEADERS += src/qt/bitcoingui.h \ src/coincontrol.h \ src/sync.h \ src/util.h \ + src/timestamps.h \ src/uint256.h \ src/kernel.h \ src/scrypt.h \ diff --git a/src/main.cpp b/src/main.cpp index fa8e764..e14b73e 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -482,10 +482,6 @@ bool CTransaction::CheckTransaction() const if (txout.IsEmpty() && !IsCoinBase() && !IsCoinStake()) return DoS(100, error("CTransaction::CheckTransaction() : txout empty for user transaction")); - // NovaCoin: enforce minimum output amount for user transactions until 1 May 2014 04:00:00 GMT - if (!fTestNet && !IsCoinBase() && !txout.IsEmpty() && nTime < OUTPUT_SWITCH_TIME && txout.nValue < MIN_TXOUT_AMOUNT) - return DoS(100, error("CTransaction::CheckTransaction() : txout.nValue below minimum")); - if (txout.nValue < 0) return DoS(100, error("CTransaction::CheckTransaction() : txout.nValue is negative")); if (txout.nValue > MAX_MONEY) @@ -519,18 +515,58 @@ bool CTransaction::CheckTransaction() const return true; } -int64 CTransaction::GetMinFee(unsigned int nBlockSize, bool fAllowFree, - enum GetMinFee_mode mode, unsigned int nBytes) const +int64 CTransaction::GetMinFee(unsigned int nBlockSize, bool fAllowFree, enum GetMinFee_mode mode, unsigned int nBytes) const { - // Base fee is either MIN_TX_FEE or MIN_RELAY_TX_FEE - int64 nBaseFee = (mode == GMF_RELAY) ? MIN_RELAY_TX_FEE : MIN_TX_FEE; + // Use new fees approach if we are on test network or + // switch date has been reached + bool fNewApproach = fTestNet || nTime > FEE_SWITCH_TIME; + + int64 nMinTxFee = MIN_TX_FEE, nMinRelayTxFee = MIN_RELAY_TX_FEE; + + if(!fNewApproach || IsCoinStake()) + { + // Enforce 0.01 as minimum fee for old approach or coinstake + nMinTxFee = CENT; + nMinRelayTxFee = CENT; + } + + // Base fee is either nMinTxFee or nMinRelayTxFee + int64 nBaseFee = (mode == GMF_RELAY) ? nMinRelayTxFee : nMinTxFee; unsigned int nNewBlockSize = nBlockSize + nBytes; int64 nMinFee = (1 + (int64)nBytes / 1000) * nBaseFee; - // To limit dust spam, require MIN_TX_FEE/MIN_RELAY_TX_FEE if any output is less than 0.01 - if (nMinFee < nBaseFee) + if (fNewApproach) { + if (fAllowFree) + { + if (nBlockSize == 1) + { + // Transactions under 1K are free + if (nBytes < 1000) + nMinFee = 0; + } + else + { + // Free transaction area + if (nNewBlockSize < 27000) + nMinFee = 0; + } + } + + // To limit dust spam, require additional MIN_TX_FEE/MIN_RELAY_TX_FEE for + // each non empty output which is less than 0.01 + // + // It's safe to ignore empty outputs here, because these inputs are allowed + // only for coinbase and coinstake transactions. + BOOST_FOREACH(const CTxOut& txout, vout) + if (txout.nValue < CENT && !txout.IsEmpty()) + nMinFee += nBaseFee; + } + else if (nMinFee < nBaseFee) + { + // To limit dust spam, require MIN_TX_FEE/MIN_RELAY_TX_FEE if + // any output is less than 0.01 BOOST_FOREACH(const CTxOut& txout, vout) if (txout.nValue < CENT) nMinFee = nBaseFee; @@ -546,6 +582,7 @@ int64 CTransaction::GetMinFee(unsigned int nBlockSize, bool fAllowFree, if (!MoneyRange(nMinFee)) nMinFee = MAX_MONEY; + return nMinFee; } @@ -640,7 +677,7 @@ bool CTxMemPool::accept(CTxDB& txdb, CTransaction &tx, bool fCheckInputs, unsigned int nSize = ::GetSerializeSize(tx, SER_NETWORK, PROTOCOL_VERSION); // Don't accept it if it can't get into a block - int64 txMinFee = tx.GetMinFee(1000, false, GMF_RELAY, nSize); + int64 txMinFee = tx.GetMinFee(1000, true, GMF_RELAY, nSize); if (nFees < txMinFee) return error("CTxMemPool::accept() : not enough fees %s, %"PRI64d" < %"PRI64d, hash.ToString().c_str(), @@ -1494,11 +1531,13 @@ bool CTransaction::ConnectInputs(CTxDB& txdb, MapPrevTx inputs, mapnBits, nTime) - GetMinFee() + MIN_TX_FEE; + unsigned int nTxSize = (nTime > VALIDATION_SWITCH_TIME || fTestNet) ? GetSerializeSize(SER_NETWORK, PROTOCOL_VERSION) : 0; - if (nStakeReward > nCalculatedStakeReward) - return DoS(100, error("ConnectInputs() : coinstake pays too much(actual=%"PRI64d" vs calculated=%"PRI64d")", nStakeReward, nCalculatedStakeReward)); + int64 nReward = GetValueOut() - nValueIn; + int64 nCalculatedReward = GetProofOfStakeReward(nCoinAge, pindexBlock->nBits, nTime) - GetMinFee(1, false, GMF_BLOCK, nTxSize) + CENT; + + if (nReward > nCalculatedReward) + return DoS(100, error("CheckInputs() : coinstake pays too much(actual=%"PRI64d" vs calculated=%"PRI64d")", nReward, nCalculatedReward)); } else { @@ -1510,10 +1549,6 @@ bool CTransaction::ConnectInputs(CTxDB& txdb, MapPrevTx inputs, map= 0 && nValue <= MAX_MONEY); } // Threshold for nLockTime: below this value it is interpreted as block number, otherwise as UNIX timestamp. diff --git a/src/miner.cpp b/src/miner.cpp index d478f71..0038474 100644 --- a/src/miner.cpp +++ b/src/miner.cpp @@ -279,7 +279,7 @@ CBlock* CreateNewBlock(CWallet* pwallet, bool fProofOfStake) continue; // Simplify transaction fee - allow free = false - int64 nMinFee = tx.GetMinFee(nBlockSize, false, GMF_BLOCK); + int64 nMinFee = tx.GetMinFee(nBlockSize, true, GMF_BLOCK, nTxSize); // Skip free transactions if we're past the minimum block size: if (fSortedByFee && (dFeePerKb < nMinTxFee) && (nBlockSize + nTxSize >= nBlockMinSize)) diff --git a/src/qt/coincontroldialog.cpp b/src/qt/coincontroldialog.cpp index 2df3c64..0fd690c 100644 --- a/src/qt/coincontroldialog.cpp +++ b/src/qt/coincontroldialog.cpp @@ -443,7 +443,7 @@ void CoinControlDialog::updateLabels(WalletModel *model, QDialog* dialog) double dPriority = 0; double dPriorityInputs = 0; unsigned int nQuantity = 0; - + vector vCoinControl; vector vOutputs; coinControl->ListSelected(vCoinControl); @@ -453,13 +453,13 @@ void CoinControlDialog::updateLabels(WalletModel *model, QDialog* dialog) { // Quantity nQuantity++; - + // Amount nAmount += out.tx->vout[out.i].nValue; - + // Priority dPriorityInputs += (double)out.tx->vout[out.i].nValue * (out.nDepth+1); - + // Bytes CTxDestination address; if(ExtractDestination(out.tx->vout[out.i].scriptPubKey, address)) @@ -473,59 +473,70 @@ void CoinControlDialog::updateLabels(WalletModel *model, QDialog* dialog) } else nBytesInputs += 148; } - + // calculation if (nQuantity > 0) { // Bytes nBytes = nBytesInputs + ((CoinControlDialog::payAmounts.size() > 0 ? CoinControlDialog::payAmounts.size() + 1 : 2) * 34) + 10; // always assume +1 output for change here - + // Priority dPriority = dPriorityInputs / nBytes; sPriorityLabel = CoinControlDialog::getPriorityLabel(dPriority); - + // Fee int64 nFee = nTransactionFee * (1 + (int64)nBytes / 1000); - + // Min Fee - int64 nMinFee = txDummy.GetMinFee(1, false, GMF_SEND, nBytes); - + bool fAllowFree = CTransaction::AllowFree(dPriority); + + // Disable free transactions until 1 July 2014 + if (!fTestNet && txDummy.nTime < FEE_SWITCH_TIME) + { + fAllowFree = false; + } + + int64 nMinFee = txDummy.GetMinFee(1, fAllowFree, GMF_SEND, nBytes); + nPayFee = max(nFee, nMinFee); - + if (nPayAmount > 0) { nChange = nAmount - nPayFee - nPayAmount; - + // if sub-cent change is required, the fee must be raised to at least CTransaction::nMinTxFee - if (nPayFee < CENT && nChange > 0 && nChange < CENT) + if (txDummy.nTime < FEE_SWITCH_TIME && !fTestNet) { - if (nChange < CENT) // change < 0.01 => simply move all change to fees + if (nPayFee < CENT && nChange > 0 && nChange < CENT) { - nPayFee = nChange; - nChange = 0; + if (nChange < CENT) // change < 0.01 => simply move all change to fees + { + nPayFee = nChange; + nChange = 0; + } + else + { + nChange = nChange + nPayFee - CENT; + nPayFee = CENT; + } } - else - { - nChange = nChange + nPayFee - CENT; - nPayFee = CENT; - } } - + if (nChange == 0) nBytes -= 34; } - + // after fee nAfterFee = nAmount - nPayFee; if (nAfterFee < 0) nAfterFee = 0; } - + // actually update labels int nDisplayUnit = BitcoinUnits::BTC; if (model && model->getOptionsModel()) nDisplayUnit = model->getOptionsModel()->getDisplayUnit(); - + QLabel *l1 = dialog->findChild("labelCoinControlQuantity"); QLabel *l2 = dialog->findChild("labelCoinControlAmount"); QLabel *l3 = dialog->findChild("labelCoinControlFee"); @@ -534,13 +545,13 @@ void CoinControlDialog::updateLabels(WalletModel *model, QDialog* dialog) QLabel *l6 = dialog->findChild("labelCoinControlPriority"); QLabel *l7 = dialog->findChild("labelCoinControlLowOutput"); QLabel *l8 = dialog->findChild("labelCoinControlChange"); - + // enable/disable "low output" and "change" dialog->findChild("labelCoinControlLowOutputText")->setEnabled(nPayAmount > 0); dialog->findChild("labelCoinControlLowOutput") ->setEnabled(nPayAmount > 0); dialog->findChild("labelCoinControlChangeText") ->setEnabled(nPayAmount > 0); dialog->findChild("labelCoinControlChange") ->setEnabled(nPayAmount > 0); - + // stats l1->setText(QString::number(nQuantity)); // Quantity l2->setText(BitcoinUnits::formatWithUnit(nDisplayUnit, nAmount)); // Amount @@ -550,15 +561,15 @@ void CoinControlDialog::updateLabels(WalletModel *model, QDialog* dialog) l6->setText(sPriorityLabel); // Priority l7->setText((fLowOutput ? (fDust ? tr("DUST") : tr("yes")) : tr("no"))); // Low Output / Dust l8->setText(BitcoinUnits::formatWithUnit(nDisplayUnit, nChange)); // Change - + // turn labels "red" - l5->setStyleSheet((nBytes >= 10000) ? "color:red;" : ""); // Bytes >= 10000 + l5->setStyleSheet((nBytes >= 5000) ? "color:red;" : ""); // Bytes >= 5000 l6->setStyleSheet((dPriority <= 576000) ? "color:red;" : ""); // Priority < "medium" l7->setStyleSheet((fLowOutput) ? "color:red;" : ""); // Low Output = "yes" l8->setStyleSheet((nChange > 0 && nChange < CENT) ? "color:red;" : ""); // Change < 0.01BTC - + // tool tips - l5->setToolTip(tr("This label turns red, if the transaction size is bigger than 10000 bytes.\n\n This means a fee of at least %1 per kb is required.\n\n Can vary +/- 1 Byte per input.").arg(BitcoinUnits::formatWithUnit(nDisplayUnit, CENT))); + l5->setToolTip(tr("This label turns red, if the transaction size is bigger than 5000 bytes.\n\n This means a fee of at least %1 per kb is required.\n\n Can vary +/- 1 Byte per input.").arg(BitcoinUnits::formatWithUnit(nDisplayUnit, CENT))); l6->setToolTip(tr("Transactions with higher priority get more likely into a block.\n\nThis label turns red, if the priority is smaller than \"medium\".\n\n This means a fee of at least %1 per kb is required.").arg(BitcoinUnits::formatWithUnit(nDisplayUnit, CENT))); l7->setToolTip(tr("This label turns red, if any recipient receives an amount smaller than %1.\n\n This means a fee of at least %2 is required. \n\n Amounts below 0.546 times the minimum relay fee are shown as DUST.").arg(BitcoinUnits::formatWithUnit(nDisplayUnit, CENT)).arg(BitcoinUnits::formatWithUnit(nDisplayUnit, CENT))); l8->setToolTip(tr("This label turns red, if the change is smaller than %1.\n\n This means a fee of at least %2 is required.").arg(BitcoinUnits::formatWithUnit(nDisplayUnit, CENT)).arg(BitcoinUnits::formatWithUnit(nDisplayUnit, CENT))); @@ -566,7 +577,7 @@ void CoinControlDialog::updateLabels(WalletModel *model, QDialog* dialog) dialog->findChild("labelCoinControlPriorityText") ->setToolTip(l6->toolTip()); dialog->findChild("labelCoinControlLowOutputText")->setToolTip(l7->toolTip()); dialog->findChild("labelCoinControlChangeText") ->setToolTip(l8->toolTip()); - + // Insufficient funds QLabel *label = dialog->findChild("labelCoinControlInsuffFunds"); if (label) diff --git a/src/timestamps.h b/src/timestamps.h new file mode 100644 index 0000000..de8a6a6 --- /dev/null +++ b/src/timestamps.h @@ -0,0 +1,13 @@ +#ifndef BITCOIN_TIMESTAMPS_H +#define BITCOIN_TIMESTAMPS_H + +static const unsigned int ENTROPY_SWITCH_TIME = 1362791041; // Sat, 09 Mar 2013 01:04:01 GMT +static const unsigned int STAKE_SWITCH_TIME = 1371686400; // Thu, 20 Jun 2013 00:00:00 GMT +static const unsigned int TARGETS_SWITCH_TIME = 1374278400; // Sat, 20 Jul 2013 00:00:00 GMT +static const unsigned int CHAINCHECKS_SWITCH_TIME = 1379635200; // Fri, 20 Sep 2013 00:00:00 GMT +static const unsigned int STAKECURVE_SWITCH_TIME = 1382227200; // Sun, 20 Oct 2013 00:00:00 GMT +static const unsigned int FEE_SWITCH_TIME = 1405814400; // Sun, 20 Jul 2014 00:00:00 GMT + +static const unsigned int VALIDATION_SWITCH_TIME = 1408492800; // Wed, 20 Aug 2014 00:00:00 GMT + +#endif diff --git a/src/wallet.cpp b/src/wallet.cpp index c78a153..0994a2d 100644 --- a/src/wallet.cpp +++ b/src/wallet.cpp @@ -1394,18 +1394,21 @@ bool CWallet::CreateTransaction(const vector >& vecSend, CW // if sub-cent change is required, the fee must be raised to at least MIN_TX_FEE // or until nChange becomes zero // NOTE: this depends on the exact behaviour of GetMinFee - if (nFeeRet < MIN_TX_FEE && nChange > 0 && nChange < CENT) + if (wtxNew.nTime < FEE_SWITCH_TIME && !fTestNet) { - int64 nMoveToFee = min(nChange, MIN_TX_FEE - nFeeRet); - nChange -= nMoveToFee; - nFeeRet += nMoveToFee; - } + if (nFeeRet < MIN_TX_FEE && nChange > 0 && nChange < CENT) + { + int64 nMoveToFee = min(nChange, MIN_TX_FEE - nFeeRet); + nChange -= nMoveToFee; + nFeeRet += nMoveToFee; + } - // ppcoin: sub-cent change is moved to fee - if (nChange > 0 && nChange < MIN_TXOUT_AMOUNT) - { - nFeeRet += nChange; - nChange = 0; + // sub-cent change is moved to fee + if (nChange > 0 && nChange < CENT) + { + nFeeRet += nChange; + nChange = 0; + } } if (nChange > 0) @@ -1460,7 +1463,15 @@ bool CWallet::CreateTransaction(const vector >& vecSend, CW // Check that enough fee is included int64 nPayFee = nTransactionFee * (1 + (int64)nBytes / 1000); - int64 nMinFee = wtxNew.GetMinFee(1, false, GMF_SEND, nBytes); + bool fAllowFree = CTransaction::AllowFree(dPriority); + + // Disable free transactions until 1 July 2014 + if (!fTestNet && wtxNew.nTime < FEE_SWITCH_TIME) + { + fAllowFree = false; + } + + int64 nMinFee = wtxNew.GetMinFee(1, fAllowFree, GMF_SEND, nBytes); if (nFeeRet < max(nPayFee, nMinFee)) { @@ -1800,9 +1811,9 @@ bool CWallet::CreateCoinStake(const CKeyStore& keystore, unsigned int nBits, int return error("CreateCoinStake : exceeded coinstake size limit"); // Check enough fee is paid - if (nMinFee < txNew.GetMinFee() - MIN_TX_FEE) + if (nMinFee < txNew.GetMinFee(1, false, GMF_BLOCK, nBytes) - CENT) { - nMinFee = txNew.GetMinFee() - MIN_TX_FEE; + nMinFee = txNew.GetMinFee(1, false, GMF_BLOCK, nBytes) - CENT; continue; // try signing again } else -- 1.7.1