Index: src/miner.h =================================================================== --- src/miner.h +++ src/miner.h @@ -36,12 +36,14 @@ CTxMemPoolModifiedEntry(CTxMemPool::txiter entry) { iter = entry; nSizeWithAncestors = entry->GetSizeWithAncestors(); + nBillableSizeWithAncestors = entry->GetBillableSizeWithAncestors(); nModFeesWithAncestors = entry->GetModFeesWithAncestors(); nSigOpCountWithAncestors = entry->GetSigOpCountWithAncestors(); } CTxMemPool::txiter iter; uint64_t nSizeWithAncestors; + uint64_t nBillableSizeWithAncestors; Amount nModFeesWithAncestors; int64_t nSigOpCountWithAncestors; }; @@ -118,6 +120,7 @@ void operator()(CTxMemPoolModifiedEntry &e) { e.nModFeesWithAncestors -= iter->GetFee(); e.nSizeWithAncestors -= iter->GetTxSize(); + e.nBillableSizeWithAncestors -= iter->GetTxBillableSize(); e.nSigOpCountWithAncestors -= iter->GetSigOpCount(); } Index: src/miner.cpp =================================================================== --- src/miner.cpp +++ src/miner.cpp @@ -390,6 +390,7 @@ if (mit == mapModifiedTx.end()) { CTxMemPoolModifiedEntry modEntry(desc); modEntry.nSizeWithAncestors -= it->GetTxSize(); + modEntry.nBillableSizeWithAncestors -= it->GetTxBillableSize(); modEntry.nModFeesWithAncestors -= it->GetModifiedFee(); modEntry.nSigOpCountWithAncestors -= it->GetSigOpCount(); mapModifiedTx.insert(modEntry); @@ -510,15 +511,18 @@ assert(!inBlock.count(iter)); uint64_t packageSize = iter->GetSizeWithAncestors(); + uint64_t packageBillableSize = iter->GetBillableSizeWithAncestors(); + Amount packageFees = iter->GetModFeesWithAncestors(); int64_t packageSigOps = iter->GetSigOpCountWithAncestors(); if (fUsingModified) { packageSize = modit->nSizeWithAncestors; + packageBillableSize = modit->nBillableSizeWithAncestors; packageFees = modit->nModFeesWithAncestors; packageSigOps = modit->nSigOpCountWithAncestors; } - if (packageFees < blockMinFeeRate.GetFee(packageSize)) { + if (packageFees < blockMinFeeRate.GetFee(packageBillableSize)) { // Everything else we might consider has a lower fee rate return; } Index: src/primitives/transaction.h =================================================================== --- src/primitives/transaction.h +++ src/primitives/transaction.h @@ -300,6 +300,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 Index: src/primitives/transaction.cpp =================================================================== --- src/primitives/transaction.cpp +++ src/primitives/transaction.cpp @@ -114,6 +114,10 @@ return nTxSize; } +size_t CTransaction::GetBillableSize() const { + return ::GetSerializeSize(*this, SER_NETWORK, PROTOCOL_VERSION); +} + unsigned int CTransaction::GetTotalSize() const { return ::GetSerializeSize(*this, SER_NETWORK, PROTOCOL_VERSION); } Index: src/test/miner_tests.cpp =================================================================== --- src/test/miner_tests.cpp +++ src/test/miner_tests.cpp @@ -139,7 +139,7 @@ tx.vout[0].nValue = int64_t(5000000000LL - 1000 - 50000) * SATOSHI; TxId freeTxId = tx.GetId(); mempool.addUnchecked(freeTxId, entry.Fee(Amount::zero()).FromTx(tx)); - size_t freeTxSize = ::GetSerializeSize(tx, SER_NETWORK, PROTOCOL_VERSION); + size_t freeTxSize = CTransaction(tx).GetBillableSize(); // Calculate a fee on child transaction that will put the package just // below the block min tx fee (assuming 1 child tx of the same size). Index: src/test/transaction_tests.cpp =================================================================== --- src/test/transaction_tests.cpp +++ src/test/transaction_tests.cpp @@ -780,4 +780,24 @@ BOOST_CHECK_EQUAL(state.GetRejectReason(), "bad-txns-undersize"); } +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 txBillableSize = tx.GetBillableSize(); + auto txSize = tx.GetTotalSize(); + BOOST_CHECK(txBillableSize > 0); + if (inputs > outputs) { + BOOST_CHECK(txBillableSize <= txSize); + } else { + BOOST_CHECK(txBillableSize >= txSize); + } + } + } +} + BOOST_AUTO_TEST_SUITE_END() Index: src/txmempool.h =================================================================== --- src/txmempool.h +++ src/txmempool.h @@ -86,6 +86,8 @@ Amount nFee; //!< ... and avoid recomputing tx size size_t nTxSize; + //!< ... and billable size for billing + size_t nTxBillableSize; //!< ... and modified size for priority size_t nModSize; //!< ... and total memory usage @@ -117,12 +119,15 @@ uint64_t nCountWithDescendants; //!< ... and size uint64_t nSizeWithDescendants; + uint64_t nBillableSizeWithDescendants; + //!< ... and total fees (all including us) Amount nModFeesWithDescendants; // Analogous statistics for ancestor transactions uint64_t nCountWithAncestors; uint64_t nSizeWithAncestors; + uint64_t nBillableSizeWithAncestors; Amount nModFeesWithAncestors; int64_t nSigOpCountWithAncestors; @@ -143,6 +148,8 @@ double GetPriority(unsigned int currentHeight) const; const Amount GetFee() const { return nFee; } size_t GetTxSize() const { return nTxSize; } + size_t GetTxBillableSize() const { return nTxBillableSize; } + int64_t GetTime() const { return nTime; } unsigned int GetHeight() const { return entryHeight; } int64_t GetSigOpCount() const { return sigOpCount; } @@ -151,11 +158,12 @@ const LockPoints &GetLockPoints() const { return lockPoints; } // Adjusts the descendant state, if this entry is not dirty. - void UpdateDescendantState(int64_t modifySize, Amount modifyFee, - int64_t modifyCount); + void UpdateDescendantState(int64_t modifySize, int64_t modifyBillableSize, + Amount modifyFee, int64_t modifyCount); // Adjusts the ancestor state - void UpdateAncestorState(int64_t modifySize, Amount modifyFee, - int64_t modifyCount, int modifySigOps); + void UpdateAncestorState(int64_t modifySize, int64_t modifyBillableSize, + Amount modifyFee, int64_t modifyCount, + int modifySigOps); // Updates the fee delta used for mining priority score, and the // modified fees with descendants. void UpdateFeeDelta(Amount feeDelta); @@ -164,12 +172,18 @@ uint64_t GetCountWithDescendants() const { return nCountWithDescendants; } uint64_t GetSizeWithDescendants() const { return nSizeWithDescendants; } + uint64_t GetBillableSizeWithDescendants() const { + return nBillableSizeWithDescendants; + } Amount GetModFeesWithDescendants() const { return nModFeesWithDescendants; } bool GetSpendsCoinbase() const { return spendsCoinbase; } uint64_t GetCountWithAncestors() const { return nCountWithAncestors; } uint64_t GetSizeWithAncestors() const { return nSizeWithAncestors; } + uint64_t GetBillableSizeWithAncestors() const { + return nBillableSizeWithAncestors; + } Amount GetModFeesWithAncestors() const { return nModFeesWithAncestors; } int64_t GetSigOpCountWithAncestors() const { return nSigOpCountWithAncestors; @@ -181,34 +195,39 @@ // Helpers for modifying CTxMemPool::mapTx, which is a boost multi_index. struct update_descendant_state { - update_descendant_state(int64_t _modifySize, Amount _modifyFee, - int64_t _modifyCount) - : modifySize(_modifySize), modifyFee(_modifyFee), - modifyCount(_modifyCount) {} + update_descendant_state(int64_t _modifySize, int64_t _modifyBillableSize, + Amount _modifyFee, int64_t _modifyCount) + : modifySize(_modifySize), modifyBillableSize(_modifyBillableSize), + modifyFee(_modifyFee), modifyCount(_modifyCount) {} void operator()(CTxMemPoolEntry &e) { - e.UpdateDescendantState(modifySize, modifyFee, modifyCount); + e.UpdateDescendantState(modifySize, modifyBillableSize, modifyFee, + modifyCount); } private: int64_t modifySize; + int64_t modifyBillableSize; Amount modifyFee; int64_t modifyCount; }; struct update_ancestor_state { - update_ancestor_state(int64_t _modifySize, Amount _modifyFee, - int64_t _modifyCount, int64_t _modifySigOpsCost) - : modifySize(_modifySize), modifyFee(_modifyFee), - modifyCount(_modifyCount), modifySigOpsCost(_modifySigOpsCost) {} + update_ancestor_state(int64_t _modifySize, int64_t _modifyBillableSize, + Amount _modifyFee, int64_t _modifyCount, + int64_t _modifySigOpsCost) + : modifySize(_modifySize), modifyBillableSize(_modifyBillableSize), + modifyFee(_modifyFee), modifyCount(_modifyCount), + modifySigOpsCost(_modifySigOpsCost) {} void operator()(CTxMemPoolEntry &e) { - e.UpdateAncestorState(modifySize, modifyFee, modifyCount, - modifySigOpsCost); + e.UpdateAncestorState(modifySize, modifyBillableSize, modifyFee, + modifyCount, modifySigOpsCost); } private: int64_t modifySize; + int64_t modifyBillableSize; Amount modifyFee; int64_t modifyCount; int64_t modifySigOpsCost; @@ -258,14 +277,14 @@ double aModFee = (fUseADescendants ? a.GetModFeesWithDescendants() : a.GetModifiedFee()) / SATOSHI; - double aSize = - fUseADescendants ? a.GetSizeWithDescendants() : a.GetTxSize(); + double aSize = fUseADescendants ? a.GetBillableSizeWithDescendants() + : a.GetTxBillableSize(); double bModFee = (fUseBDescendants ? b.GetModFeesWithDescendants() : b.GetModifiedFee()) / SATOSHI; - double bSize = - fUseBDescendants ? b.GetSizeWithDescendants() : b.GetTxSize(); + double bSize = fUseBDescendants ? b.GetBillableSizeWithDescendants() + : b.GetTxBillableSize(); // Avoid division by rewriting (a/b > c/d) as (a*d > c*b). double f1 = aModFee * bSize; @@ -279,8 +298,10 @@ // Calculate which score to use for an entry (avoiding division). bool UseDescendantScore(const CTxMemPoolEntry &a) const { - double f1 = a.GetSizeWithDescendants() * (a.GetModifiedFee() / SATOSHI); - double f2 = a.GetTxSize() * (a.GetModFeesWithDescendants() / SATOSHI); + double f1 = + a.GetBillableSizeWithDescendants() * (a.GetModifiedFee() / SATOSHI); + double f2 = + a.GetTxBillableSize() * (a.GetModFeesWithDescendants() / SATOSHI); return f2 > f1; } }; @@ -292,8 +313,8 @@ class CompareTxMemPoolEntryByScore { public: bool operator()(const CTxMemPoolEntry &a, const CTxMemPoolEntry &b) const { - double f1 = b.GetTxSize() * (a.GetModifiedFee() / SATOSHI); - double f2 = a.GetTxSize() * (b.GetModifiedFee() / SATOSHI); + double f1 = b.GetTxBillableSize() * (a.GetModifiedFee() / SATOSHI); + double f2 = a.GetTxBillableSize() * (b.GetModifiedFee() / SATOSHI); if (f1 == f2) { return b.GetTx().GetId() < a.GetTx().GetId(); } @@ -312,10 +333,10 @@ public: bool operator()(const CTxMemPoolEntry &a, const CTxMemPoolEntry &b) const { double aFees = a.GetModFeesWithAncestors() / SATOSHI; - double aSize = a.GetSizeWithAncestors(); + double aSize = a.GetBillableSizeWithAncestors(); double bFees = b.GetModFeesWithAncestors() / SATOSHI; - double bSize = b.GetSizeWithAncestors(); + double bSize = b.GetBillableSizeWithAncestors(); // Avoid division by rewriting (a/b > c/d) as (a*d > c*b). double f1 = aFees * bSize; Index: src/txmempool.cpp =================================================================== --- src/txmempool.cpp +++ src/txmempool.cpp @@ -33,11 +33,13 @@ spendsCoinbase(_spendsCoinbase), sigOpCount(_sigOpsCount), lockPoints(lp) { nTxSize = tx->GetTotalSize(); + nTxBillableSize = tx->GetBillableSize(); nModSize = tx->CalculateModifiedSize(GetTxSize()); nUsageSize = RecursiveDynamicUsage(tx); nCountWithDescendants = 1; nSizeWithDescendants = GetTxSize(); + nBillableSizeWithDescendants = GetTxBillableSize(); nModFeesWithDescendants = nFee; Amount nValueIn = tx->GetValueOut() + nFee; assert(inChainInputValue <= nValueIn); @@ -46,6 +48,7 @@ nCountWithAncestors = 1; nSizeWithAncestors = GetTxSize(); + nBillableSizeWithAncestors = GetTxBillableSize(); nModFeesWithAncestors = nFee; nSigOpCountWithAncestors = sigOpCount; } @@ -107,23 +110,27 @@ // setAllDescendants now contains all in-mempool descendants of updateIt. // Update and add to cached descendant map int64_t modifySize = 0; - Amount modifyFee = Amount::zero(); + int64_t modifyBillableSize = 0; int64_t modifyCount = 0; + Amount modifyFee = Amount::zero(); for (txiter cit : setAllDescendants) { if (!setExclude.count(cit->GetTx().GetId())) { modifySize += cit->GetTxSize(); + modifyBillableSize += cit->GetTxBillableSize(); modifyFee += cit->GetModifiedFee(); modifyCount++; cachedDescendants[updateIt].insert(cit); // Update ancestor state for each descendant mapTx.modify(cit, update_ancestor_state(updateIt->GetTxSize(), + updateIt->GetTxBillableSize(), updateIt->GetModifiedFee(), 1, updateIt->GetSigOpCount())); } } mapTx.modify(updateIt, - update_descendant_state(modifySize, modifyFee, modifyCount)); + update_descendant_state(modifySize, modifyBillableSize, + modifyFee, modifyCount)); } // txidsToUpdate is the set of transaction hashes from a disconnected block @@ -271,10 +278,12 @@ } const int64_t updateCount = (add ? 1 : -1); const int64_t updateSize = updateCount * it->GetTxSize(); + const int64_t updateBillableSize = updateCount * it->GetTxBillableSize(); const Amount updateFee = updateCount * it->GetModifiedFee(); for (txiter ancestorIt : setAncestors) { - mapTx.modify(ancestorIt, update_descendant_state(updateSize, updateFee, - updateCount)); + mapTx.modify(ancestorIt, + update_descendant_state(updateSize, updateBillableSize, + updateFee, updateCount)); } } @@ -282,14 +291,18 @@ const setEntries &setAncestors) { int64_t updateCount = setAncestors.size(); int64_t updateSize = 0; - Amount updateFee = Amount::zero(); + int64_t updateBillableSize = 0; int64_t updateSigOpsCount = 0; + Amount updateFee = Amount::zero(); + for (txiter ancestorIt : setAncestors) { updateSize += ancestorIt->GetTxSize(); + updateBillableSize += ancestorIt->GetTxBillableSize(); updateFee += ancestorIt->GetModifiedFee(); updateSigOpsCount += ancestorIt->GetSigOpCount(); } - mapTx.modify(it, update_ancestor_state(updateSize, updateFee, updateCount, + mapTx.modify(it, update_ancestor_state(updateSize, updateBillableSize, + updateFee, updateCount, updateSigOpsCount)); } @@ -315,12 +328,15 @@ setEntries setDescendants; CalculateDescendants(removeIt, setDescendants); setDescendants.erase(removeIt); // don't update state for self - int64_t modifySize = -((int64_t)removeIt->GetTxSize()); + int64_t modifySize = -int64_t(removeIt->GetTxSize()); + int64_t modifyBillableSize = + -int64_t(removeIt->GetTxBillableSize()); Amount modifyFee = -1 * removeIt->GetModifiedFee(); int modifySigOps = -removeIt->GetSigOpCount(); for (txiter dit : setDescendants) { - mapTx.modify(dit, update_ancestor_state(modifySize, modifyFee, - -1, modifySigOps)); + mapTx.modify( + dit, update_ancestor_state(modifySize, modifyBillableSize, + modifyFee, -1, modifySigOps)); } } } @@ -361,20 +377,26 @@ } void CTxMemPoolEntry::UpdateDescendantState(int64_t modifySize, + int64_t modifyBillableSize, Amount modifyFee, int64_t modifyCount) { nSizeWithDescendants += modifySize; assert(int64_t(nSizeWithDescendants) > 0); + nBillableSizeWithDescendants += modifyBillableSize; + assert(int64_t(nBillableSizeWithDescendants) >= 0); nModFeesWithDescendants += modifyFee; nCountWithDescendants += modifyCount; assert(int64_t(nCountWithDescendants) > 0); } -void CTxMemPoolEntry::UpdateAncestorState(int64_t modifySize, Amount modifyFee, - int64_t modifyCount, +void CTxMemPoolEntry::UpdateAncestorState(int64_t modifySize, + int64_t modifyBillableSize, + Amount modifyFee, int64_t modifyCount, int modifySigOps) { nSizeWithAncestors += modifySize; assert(int64_t(nSizeWithAncestors) > 0); + nBillableSizeWithAncestors += modifyBillableSize; + assert(int64_t(nBillableSizeWithAncestors) >= 0); nModFeesWithAncestors += modifyFee; nCountWithAncestors += modifyCount; assert(int64_t(nCountWithAncestors) > 0); @@ -903,7 +925,7 @@ static TxMempoolInfo GetInfo(CTxMemPool::indexed_transaction_set::const_iterator it) { return TxMempoolInfo{it->GetSharedTx(), it->GetTime(), - CFeeRate(it->GetFee(), it->GetTxSize()), + CFeeRate(it->GetFee(), it->GetTxBillableSize()), it->GetModifiedFee() - it->GetFee()}; } @@ -1007,7 +1029,7 @@ nNoLimit, nNoLimit, dummy, false); for (txiter ancestorIt : setAncestors) { mapTx.modify(ancestorIt, - update_descendant_state(0, nFeeDelta, 0)); + update_descendant_state(0, 0, nFeeDelta, 0)); } // Now update all descendants' modified fees with ancestors @@ -1016,7 +1038,7 @@ setDescendants.erase(it); for (txiter descendantIt : setDescendants) { mapTx.modify(descendantIt, - update_ancestor_state(0, nFeeDelta, 0, 0)); + update_ancestor_state(0, 0, nFeeDelta, 0, 0)); } } } @@ -1224,7 +1246,7 @@ // mempool with feerate equal to txn which were removed with no block in // between. CFeeRate removed(it->GetModFeesWithDescendants(), - it->GetSizeWithDescendants()); + it->GetBillableSizeWithDescendants()); removed += MEMPOOL_FULL_FEE_INCREMENT; trackPackageRemoved(removed); Index: src/validation.cpp =================================================================== --- src/validation.cpp +++ src/validation.cpp @@ -524,6 +524,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 @@ -541,7 +542,7 @@ pool.GetMinFee( gArgs.GetArg("-maxmempool", DEFAULT_MAX_MEMPOOL_SIZE) * 1000000) - .GetFee(nSize); + .GetFee(feeSize); if (mempoolRejectFee > Amount::zero() && nModifiedFees < mempoolRejectFee) { return state.DoS(0, false, REJECT_INSUFFICIENTFEE, @@ -550,7 +551,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. @@ -562,7 +563,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; @@ -575,6 +576,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) { Index: src/wallet/wallet.cpp =================================================================== --- src/wallet/wallet.cpp +++ src/wallet/wallet.cpp @@ -2955,6 +2955,11 @@ 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 @@ -2971,20 +2976,21 @@ } Amount nFeeNeeded = - GetMinimumFee(nBytes, currentConfirmationTarget, mempool); + GetMinimumFee(feeBytes, currentConfirmationTarget, mempool); + if (coinControl && nFeeNeeded > Amount::zero() && coinControl->nMinimumTotalFee > nFeeNeeded) { nFeeNeeded = coinControl->nMinimumTotalFee; } if (coinControl && coinControl->fOverrideFeeRate) { - nFeeNeeded = coinControl->nFeeRate.GetFeeCeiling(nBytes); + nFeeNeeded = coinControl->nFeeRate.GetFeeCeiling(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().GetFeeCeiling(nBytes); + Amount minFee = GetConfig().GetMinFeePerKB().GetFee(feeBytes); if (nFeeNeeded < minFee) { strFailReason = _("Transaction too large for fee policy"); return false; @@ -3031,6 +3037,7 @@ // Include more fee and try again. nFeeRet = nFeeNeeded; + continue; } Index: test/functional/fundrawtransaction.py =================================================================== --- test/functional/fundrawtransaction.py +++ test/functional/fundrawtransaction.py @@ -152,7 +152,7 @@ inputs = [{'txid': utx['txid'], 'vout': utx['vout']}] outputs = { - self.nodes[0].getnewaddress(): Decimal(5.0) - fee - feeTolerance} + self.nodes[0].getnewaddress(): satoshi_round(Decimal(5.0) - fee - feeTolerance)} rawtx = self.nodes[2].createrawtransaction(inputs, outputs) dec_tx = self.nodes[2].decoderawtransaction(rawtx) assert_equal(utx['txid'], dec_tx['vin'][0]['txid']) @@ -651,13 +651,12 @@ rawtx) # uses min_relay_tx_fee (set by settxfee) result2 = self.nodes[3].fundrawtransaction( rawtx, {"feeRate": 2 * min_relay_tx_fee}) + assert_fee_amount( + result2['fee'], FromHex(CTransaction(), result2['hex']).billable_size(), 2 * min_relay_tx_fee) result3 = self.nodes[3].fundrawtransaction( rawtx, {"feeRate": 10 * min_relay_tx_fee}) - result_fee_rate = result['fee'] * 1000 / count_bytes(result['hex']) - assert_fee_amount( - result2['fee'], FromHex(CTransaction(), result2['hex']).billable_size(), 2 * result_fee_rate) assert_fee_amount( - result3['fee'], FromHex(CTransaction(), result3['hex']).billable_size(), 10 * result_fee_rate) + result3['fee'], FromHex(CTransaction(), result3['hex']).billable_size(), 10 * min_relay_tx_fee) # # Test address reuse option # Index: test/functional/test_framework/test_node.py =================================================================== --- test/functional/test_framework/test_node.py +++ test/functional/test_framework/test_node.py @@ -168,7 +168,7 @@ def calculate_fee(self, tx): # Relay fee is in satoshis per KB. Thus the 1000, and the COIN added # to get back to an amount of satoshis. - return int(self.relay_fee() / 1000 * len(ToHex(tx)) * COIN) + return int(self.relay_fee() / 1000 * tx.billable_size() * COIN) def calculate_fee_from_txid(self, txid): ctx = FromHex(CTransaction(), self.getrawtransaction(txid)) Index: test/functional/test_framework/util.py =================================================================== --- test/functional/test_framework/util.py +++ test/functional/test_framework/util.py @@ -28,11 +28,11 @@ def assert_fee_amount(fee, tx_size, fee_per_kB): """Assert the fee was in range""" target_fee = tx_size * fee_per_kB / 1000 - if fee < target_fee: + if fee < (tx_size - 10) * fee_per_kB / 1000: raise AssertionError( "Fee of %s BTC too low! (Should be %s BTC)" % (str(fee), str(target_fee))) # allow the wallet's estimation to be at most 2 bytes off - if fee > (tx_size + 2) * fee_per_kB / 1000: + if fee > (tx_size + 10) * fee_per_kB / 1000: raise AssertionError( "Fee of %s BTC too high! (Should be %s BTC)" % (str(fee), str(target_fee)))