diff --git a/doc/release-notes.md b/doc/release-notes.md
--- a/doc/release-notes.md
+++ b/doc/release-notes.md
@@ -3,3 +3,6 @@
This release includes the following features and fixes:
+ - Update fee calculation to add 179 effective bytes per transaction output in excess of inputs.
+ Refund 179 bytes worth of minimum fee per input in excess of outputs to a minimum of
+ 10 + 34 * (number of utxos)
diff --git a/src/primitives/transaction.h b/src/primitives/transaction.h
--- a/src/primitives/transaction.h
+++ b/src/primitives/transaction.h
@@ -312,6 +312,10 @@
// size)
unsigned int CalculateModifiedSize(unsigned int nTxSize = 0) const;
+ // Computes an adjusted tx size so that the UTXIs are billed partially
+ // upfront.
+ size_t GetBillableSize() const;
+
/**
* Get the total transaction size in bytes.
* @return Total transaction size in bytes
diff --git a/src/primitives/transaction.cpp b/src/primitives/transaction.cpp
--- a/src/primitives/transaction.cpp
+++ b/src/primitives/transaction.cpp
@@ -109,6 +109,24 @@
return nTxSize;
}
+size_t CTransaction::GetBillableSize() const {
+ size_t nTxSize = GetTotalSize(), inputs = vin.size(), outputs = vout.size();
+
+ // 179 bytes is the minimum size it would take to spend any outputs which
+ // are created. We want to change in advance of spending them to
+ // incentivize keeping your UTXO set reasonbly sized.
+ int64_t modSize =
+ int64_t(nTxSize) + (int64_t(outputs) - int64_t(inputs)) * 179;
+
+ // Note: It is impossible to generate a negative number above in any real
+ // world situation. This is because the inputs have a least 179 byte
+ // each. However, it is possible to have shorter scriptSigs than 179
+ // bytes. Therefore, we include a minimum of 10 bytes + 34 * vouts.
+ nTxSize = std::max(int64_t(outputs * 34 + 10), modSize);
+
+ return nTxSize;
+}
+
unsigned int CTransaction::GetTotalSize() const {
return ::GetSerializeSize(*this, SER_NETWORK, PROTOCOL_VERSION);
}
diff --git a/src/test/transaction_tests.cpp b/src/test/transaction_tests.cpp
--- a/src/test/transaction_tests.cpp
+++ b/src/test/transaction_tests.cpp
@@ -757,4 +757,24 @@
BOOST_CHECK(!IsStandardTx(CTransaction(t), reason));
}
+BOOST_AUTO_TEST_CASE(tx_transaction_fee) {
+ std::vector sizes = {1, 2, 4, 8, 16, 32, 64, 128, 256, 512};
+ for (size_t inputs : sizes) {
+ for (size_t outputs : sizes) {
+ CMutableTransaction mtx;
+ mtx.vin.resize(inputs);
+ mtx.vout.resize(outputs);
+ CTransaction tx(mtx);
+ auto bs = tx.GetBillableSize();
+ auto s = tx.GetTotalSize();
+ BOOST_CHECK(bs > 0);
+ if (inputs > outputs) {
+ BOOST_CHECK(bs < s);
+ } else {
+ BOOST_CHECK(bs >= s);
+ }
+ }
+ }
+}
+
BOOST_AUTO_TEST_SUITE_END()
diff --git a/src/validation.cpp b/src/validation.cpp
--- a/src/validation.cpp
+++ b/src/validation.cpp
@@ -909,6 +909,7 @@
chainActive.Height(), inChainInputValue,
fSpendsCoinbase, nSigOpsCount, lp);
unsigned int nSize = entry.GetTxSize();
+ size_t feeSize = tx.GetBillableSize();
// Check that the transaction doesn't have an excessive number of
// sigops, making it impossible to mine. Since the coinbase transaction
@@ -926,7 +927,7 @@
pool.GetMinFee(
gArgs.GetArg("-maxmempool", DEFAULT_MAX_MEMPOOL_SIZE) *
1000000)
- .GetFee(nSize);
+ .GetFee(feeSize);
if (mempoolRejectFee > Amount(0) && nModifiedFees < mempoolRejectFee) {
return state.DoS(0, false, REJECT_INSUFFICIENTFEE,
"mempool min fee not met", false,
@@ -934,7 +935,7 @@
}
if (gArgs.GetBoolArg("-relaypriority", DEFAULT_RELAYPRIORITY) &&
- nModifiedFees < minRelayTxFee.GetFee(nSize) &&
+ nModifiedFees < minRelayTxFee.GetFee(feeSize) &&
!AllowFree(entry.GetPriority(chainActive.Height() + 1))) {
// Require that free transactions have sufficient priority to be
// mined in the next block.
@@ -946,7 +947,7 @@
// This mitigates 'penny-flooding' -- sending thousands of free
// transactions just to be annoying or make others' transactions take
// longer to confirm.
- if (fLimitFree && nModifiedFees < minRelayTxFee.GetFee(nSize)) {
+ if (fLimitFree && nModifiedFees < minRelayTxFee.GetFee(feeSize)) {
static CCriticalSection csFreeLimiter;
static double dFreeCount;
static int64_t nLastTime;
@@ -959,6 +960,9 @@
nLastTime = nNow;
// -limitfreerelay unit is thousand-bytes-per-minute
// At default rate it would take over a month to fill 1GB
+
+ // NOTE: Use the actual size here, and not the fee size since this
+ // is counting real size for the rate limiter.
if (dFreeCount + nSize >=
gArgs.GetArg("-limitfreerelay", DEFAULT_LIMITFREERELAY) * 10 *
1000) {
diff --git a/src/wallet/wallet.cpp b/src/wallet/wallet.cpp
--- a/src/wallet/wallet.cpp
+++ b/src/wallet/wallet.cpp
@@ -2916,6 +2916,12 @@
CTransaction txNewConst(txNew);
unsigned int nBytes = txNewConst.GetTotalSize();
+
+ // Note: The relaying code has been changed to charge upfront for
+ // the minimum required bytes to spend a UTXO. This means that
+ // we need to calculate possible fees based that size.
+ size_t feeBytes = txNewConst.GetBillableSize();
+
dPriority = txNewConst.ComputePriority(dPriority, nBytes);
// Remove scriptSigs to eliminate the fee calculation dummy
@@ -2932,20 +2938,20 @@
}
Amount nFeeNeeded =
- GetMinimumFee(nBytes, currentConfirmationTarget, mempool);
+ GetMinimumFee(feeBytes, currentConfirmationTarget, mempool);
if (coinControl && nFeeNeeded > Amount(0) &&
coinControl->nMinimumTotalFee > nFeeNeeded) {
nFeeNeeded = coinControl->nMinimumTotalFee;
}
if (coinControl && coinControl->fOverrideFeeRate) {
- nFeeNeeded = coinControl->nFeeRate.GetFee(nBytes);
+ nFeeNeeded = coinControl->nFeeRate.GetFee(feeBytes);
}
// If we made it here and we aren't even able to meet the relay fee
// on the next pass, give up because we must be at the maximum
// allowed fee.
- Amount minFee = GetConfig().GetMinFeePerKB().GetFee(nBytes);
+ Amount minFee = GetConfig().GetMinFeePerKB().GetFee(feeBytes);
if (nFeeNeeded < minFee) {
strFailReason = _("Transaction too large for fee policy");
return false;
diff --git a/test/functional/mempool_limit.py b/test/functional/mempool_limit.py
--- a/test/functional/mempool_limit.py
+++ b/test/functional/mempool_limit.py
@@ -21,7 +21,7 @@
relayfee = self.nodes[0].getnetworkinfo()['relayfee']
txids = []
- utxos = create_confirmed_utxos(relayfee, self.nodes[0], 91)
+ utxos = create_confirmed_utxos(relayfee, self.nodes[0], 121)
# create a mempool tx that will be evicted
us0 = utxos.pop()
@@ -38,7 +38,7 @@
relayfee = self.nodes[0].getnetworkinfo()['relayfee']
base_fee = relayfee * 100
- for i in range(3):
+ for i in range(4):
txids.append([])
txids[i] = create_lots_of_big_transactions(
self.nodes[0], txouts, utxos[30 * i:30 * i + 30], 30, (i + 1) * base_fee)
diff --git a/test/functional/test_framework/mininode.py b/test/functional/test_framework/mininode.py
--- a/test/functional/test_framework/mininode.py
+++ b/test/functional/test_framework/mininode.py
@@ -402,6 +402,10 @@
self.sha256 = None
self.hash = None
+ def billable_size(self):
+ tx_size = len(ToHex(self)) + len(self.vout)*179 - len(self.vin)*179
+ return max(tx_size, 10 + 34 * len(self.vout))
+
def serialize(self):
r = b""
r += struct.pack("