Skip to content

Commit

Permalink
CORE: Add a transaction fee discount for mining/witness inputs
Browse files Browse the repository at this point in the history
Allow miners/witnesses to consolodate inputs without paying as much as is normally required, we don't have to worry about transaction spam as much with coinbase inputs as they are limited in number by the codebase
  • Loading branch information
mjmacleod committed Aug 20, 2021
1 parent b93972f commit 8de5c7d
Show file tree
Hide file tree
Showing 11 changed files with 71 additions and 31 deletions.
20 changes: 20 additions & 0 deletions src/policy/policy.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -321,3 +321,23 @@ int64_t GetVirtualTransactionSize(const CTransaction& tx, int64_t nSigOpCost)
{
return GetVirtualTransactionSize(GetTransactionWeight(tx), nSigOpCost);
}

const uint64_t STANDARD_COINBASE_INPUT_SIZE_DISCOUNT = 70;
const uint64_t STANDARD_COINBASE_SIGOP_COST_DISCOUNT = 1;
int64_t GetVirtualTransactionSizeDiscounted(int64_t nWeight, uint64_t numCoinbaseInputs, int64_t nSigOpCost)
{
uint64_t coinbaseInputsToDiscount=(numCoinbaseInputs>2?numCoinbaseInputs-2:numCoinbaseInputs);
uint64_t weightDiscount = STANDARD_COINBASE_INPUT_SIZE_DISCOUNT * coinbaseInputsToDiscount;
uint64_t sigOpCostDiscount = STANDARD_COINBASE_SIGOP_COST_DISCOUNT * coinbaseInputsToDiscount;
uint64_t discountedWeight = nWeight>weightDiscount?nWeight-weightDiscount:nWeight;
uint64_t discountedSigOps = nSigOpCost>sigOpCostDiscount?nSigOpCost-sigOpCostDiscount:nSigOpCost;

return (std::max(discountedWeight, discountedSigOps * nBytesPerSigOp));
}


int64_t GetVirtualTransactionSizeDiscounted(const CTransaction& tx, uint64_t numCoinbaseInputs, int64_t nSigOpCost)
{
uint64_t nTxWeight = GetTransactionWeight(tx);
return GetVirtualTransactionSizeDiscounted(nTxWeight, numCoinbaseInputs, nSigOpCost);
}
2 changes: 2 additions & 0 deletions src/policy/policy.h
Original file line number Diff line number Diff line change
Expand Up @@ -100,5 +100,7 @@ extern unsigned int nBytesPerSigOp;
/** Compute the virtual transaction size (weight reinterpreted as bytes). */
int64_t GetVirtualTransactionSize(int64_t nWeight, int64_t nSigOpCost);
int64_t GetVirtualTransactionSize(const CTransaction& tx, int64_t nSigOpCost = 0);
int64_t GetVirtualTransactionSizeDiscounted(int64_t nWeight, uint64_t numCoinbaseInputs, int64_t nSigOpCost);
int64_t GetVirtualTransactionSizeDiscounted(const CTransaction& tx, uint64_t numCoinbaseInputs, int64_t nSigOpCost=0);

#endif
1 change: 0 additions & 1 deletion src/primitives/transaction.h
Original file line number Diff line number Diff line change
Expand Up @@ -1330,7 +1330,6 @@ typedef std::shared_ptr<const CTransaction> CTransactionRef;
static inline CTransactionRef MakeTransactionRef(int32_t nVersion_) { return std::make_shared<const CTransaction>(nVersion_); }
template <typename Tx> static inline CTransactionRef MakeTransactionRef(Tx&& txIn) { return std::make_shared<const CTransaction>(std::forward<Tx>(txIn)); }

/** Compute the weight of a transaction, as defined by BIP 141 */
int64_t GetTransactionWeight(const CTransaction &tx);

#endif
2 changes: 1 addition & 1 deletion src/test/test.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -174,5 +174,5 @@ CTxMemPoolEntry TestMemPoolEntryHelper::FromTx(const CMutableTransaction &tx) {

CTxMemPoolEntry TestMemPoolEntryHelper::FromTx(const CTransaction &txn) {
return CTxMemPoolEntry(MakeTransactionRef(txn), nFee, nTime, nHeight,
spendsCoinbase, sigOpCost, lp);
spendsCoinbase?0:1, sigOpCost, lp);
}
11 changes: 8 additions & 3 deletions src/txmempool.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -29,11 +29,11 @@

CTxMemPoolEntry::CTxMemPoolEntry(const CTransactionRef& _tx, const CAmount& _nFee,
int64_t _nTime, unsigned int _entryHeight,
bool _spendsCoinbase, int64_t _sigOpsCost, LockPoints lp):
uint64_t _spendCoinbaseCount, int64_t _sigOpsCost, LockPoints lp):
tx(_tx), nFee(_nFee), nTime(_nTime), entryHeight(_entryHeight),
spendsCoinbase(_spendsCoinbase), sigOpCost(_sigOpsCost), lockPoints(lp)
spendCoinbaseCount(_spendCoinbaseCount), sigOpCost(_sigOpsCost), lockPoints(lp)
{
nTxWeight = GetTransactionWeight(*tx);
nTxWeight = tx->GetTotalSize();
nUsageSize = RecursiveDynamicUsage(tx);

nCountWithDescendants = 1;
Expand Down Expand Up @@ -70,6 +70,11 @@ size_t CTxMemPoolEntry::GetTxSize() const
return GetVirtualTransactionSize(nTxWeight, sigOpCost);
}

size_t CTxMemPoolEntry::GetTxSizeDiscounted() const
{
return GetVirtualTransactionSizeDiscounted(nTxWeight, spendCoinbaseCount, sigOpCost);
}

// Update the given tx for any in-mempool descendants.
// Assumes that setMemPoolChildren is correct for the given tx and all
// descendants.
Expand Down
7 changes: 4 additions & 3 deletions src/txmempool.h
Original file line number Diff line number Diff line change
Expand Up @@ -81,7 +81,7 @@ class CTxMemPoolEntry
size_t nUsageSize; //!< ... and total memory usage
int64_t nTime; //!< Local time when entering the mempool
unsigned int entryHeight; //!< Chain height when entering the mempool
bool spendsCoinbase; //!< keep track of transactions that spend a coinbase
uint64_t spendCoinbaseCount; //!< keep track of transactions that spend a coinbase, and how many they spend
int64_t sigOpCost; //!< Total sigop cost
int64_t feeDelta; //!< Used for determining the priority of the transaction for mining in a block
LockPoints lockPoints; //!< Track the height and time at which tx was final
Expand All @@ -102,7 +102,7 @@ class CTxMemPoolEntry
public:
CTxMemPoolEntry(const CTransactionRef& _tx, const CAmount& _nFee,
int64_t _nTime, unsigned int _entryHeight,
bool spendsCoinbase,
uint64_t spendCoinbaseCount,
int64_t nSigOpsCost, LockPoints lp);

CTxMemPoolEntry(const CTxMemPoolEntry& other);
Expand All @@ -111,6 +111,7 @@ class CTxMemPoolEntry
CTransactionRef GetSharedTx() const { return this->tx; }
const CAmount& GetFee() const { return nFee; }
size_t GetTxSize() const;
size_t GetTxSizeDiscounted() const;
size_t GetTxWeight() const { return nTxWeight; }
int64_t GetTime() const { return nTime; }
unsigned int GetHeight() const { return entryHeight; }
Expand All @@ -133,7 +134,7 @@ class CTxMemPoolEntry
uint64_t GetSizeWithDescendants() const { return nSizeWithDescendants; }
CAmount GetModFeesWithDescendants() const { return nModFeesWithDescendants; }

bool GetSpendsCoinbase() const { return spendsCoinbase; }
bool GetSpendsCoinbase() const { return spendCoinbaseCount>0; }

uint64_t GetCountWithAncestors() const { return nCountWithAncestors; }
uint64_t GetSizeWithAncestors() const { return nSizeWithAncestors; }
Expand Down
21 changes: 11 additions & 10 deletions src/validation/validation_mempool.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -242,7 +242,7 @@ static bool AcceptToMemoryPoolWorker(const CChainParams& chainparams, CTxMemPool
if (fPartialChecksOnly)
{
LOCK(pool.cs);
CTxMemPoolEntry entry(ptx, 0, nAcceptTime, chain.Height(), false, 0, LockPoints());
CTxMemPoolEntry entry(ptx, 0, nAcceptTime, chain.Height(), 0, 0, LockPoints());

// Store transaction in memory pool
pool.addUnchecked(hash, entry, false);
Expand Down Expand Up @@ -322,18 +322,19 @@ static bool AcceptToMemoryPoolWorker(const CChainParams& chainparams, CTxMemPool

// Keep track of transactions that spend a coinbase, which we re-scan
// during reorgs to ensure COINBASE_MATURITY is still met.
bool fSpendsCoinbase = false;
uint64_t spendsCoinbaseCount = 0;
for(const CTxIn &txin : tx.vin) {
const Coin &coin = view.AccessCoin(txin.GetPrevOut());
if (coin.IsCoinBase()) {
fSpendsCoinbase = true;
break;
++spendsCoinbaseCount;
}
}

CTxMemPoolEntry entry(ptx, nFees, nAcceptTime, chainActive.Height(),
fSpendsCoinbase, nSigOpsCost, lp);
spendsCoinbaseCount, nSigOpsCost, lp);

unsigned int nSize = entry.GetTxSize();
unsigned int nDiscountedSize = entry.GetTxSizeDiscounted();

// Check that the transaction doesn't have an excessive number of
// sigops, making it impossible to mine. Since the coinbase transaction
Expand All @@ -344,13 +345,13 @@ static bool AcceptToMemoryPoolWorker(const CChainParams& chainparams, CTxMemPool
return state.DoS(0, false, REJECT_NONSTANDARD, "bad-txns-too-many-sigops", false,
strprintf("%d", nSigOpsCost));

CAmount mempoolRejectFee = pool.GetMinFee(GetArg("-maxmempool", DEFAULT_MAX_MEMPOOL_SIZE) * 1000000).GetFee(nSize);
CAmount mempoolRejectFee = pool.GetMinFee(GetArg("-maxmempool", DEFAULT_MAX_MEMPOOL_SIZE) * 1000000).GetFee(nDiscountedSize);
if (mempoolRejectFee > 0 && nModifiedFees < mempoolRejectFee) {
return state.DoS(0, false, REJECT_INSUFFICIENTFEE, "mempool min fee not met", false, strprintf("%d < %d", nFees, mempoolRejectFee));
}

// No transactions are allowed below minRelayTxFee except from disconnected blocks
if (fLimitFree && nModifiedFees < ::minRelayTxFee.GetFee(nSize)) {
if (fLimitFree && nModifiedFees < ::minRelayTxFee.GetFee(nDiscountedSize)) {
return state.DoS(0, false, REJECT_INSUFFICIENTFEE, "min relay fee not met");
}

Expand Down Expand Up @@ -401,7 +402,7 @@ static bool AcceptToMemoryPoolWorker(const CChainParams& chainparams, CTxMemPool
const bool fReplacementTransaction = setConflicts.size();
if (fReplacementTransaction)
{
CFeeRate newFeeRate(nModifiedFees, nSize);
CFeeRate newFeeRate(nModifiedFees, nDiscountedSize);
std::set<uint256> setConflictsParents;
const int maxDescendantsToVisit = 100;
CTxMemPool::setEntries setIterConflicting;
Expand Down Expand Up @@ -507,14 +508,14 @@ static bool AcceptToMemoryPoolWorker(const CChainParams& chainparams, CTxMemPool
// Finally in addition to paying more fees than the conflicts the
// new transaction must pay for its own bandwidth.
CAmount nDeltaFees = nModifiedFees - nConflictingFees;
if (nDeltaFees < ::incrementalRelayFee.GetFee(nSize))
if (nDeltaFees < ::incrementalRelayFee.GetFee(nDiscountedSize))
{
return state.DoS(0, false,
REJECT_INSUFFICIENTFEE, "insufficient fee", false,
strprintf("rejecting replacement %s, not enough additional fees to relay; %s < %s",
hash.ToString(),
FormatMoney(nDeltaFees),
FormatMoney(::incrementalRelayFee.GetFee(nSize))));
FormatMoney(::incrementalRelayFee.GetFee(nDiscountedSize))));
}
}

Expand Down
2 changes: 1 addition & 1 deletion src/validation/witnessvalidation.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@
#include "wallet/wallet.h"
#endif

#include <boost/foreach.hpp> // Zreverse_foreach
#include <boost/foreach.hpp> // reverse_foreach
#include <boost/algorithm/string/replace.hpp>
#include <boost/algorithm/string/join.hpp>
#include <boost/thread.hpp>
Expand Down
6 changes: 5 additions & 1 deletion src/wallet/wallet.h
Original file line number Diff line number Diff line change
Expand Up @@ -221,6 +221,8 @@ class CInputCoin {
throw std::invalid_argument("walletTx should not be null");
if (i >= walletTx->tx->vout.size())
throw std::out_of_range("The output index is out of range");

isCoinBase = walletTx->tx->IsCoinBase();

if (allowIndexBased && walletTx->GetDepthInMainChain() > COINBASE_MATURITY && walletTx->nHeight > 1 && walletTx->nIndex >= 0)
{
Expand All @@ -236,7 +238,8 @@ class CInputCoin {
txout = walletTx->tx->vout[i];
}

CInputCoin(const COutPoint& outpoint_, const CTxOut& txout_, bool allowIndexBased, uint64_t nBlockHeight=0, uint64_t nTxIndex=0)
CInputCoin(const COutPoint& outpoint_, const CTxOut& txout_, bool allowIndexBased, bool isCoinBase_, uint64_t nBlockHeight=0, uint64_t nTxIndex=0)
: isCoinBase(isCoinBase_)
{
if (allowIndexBased && nBlockHeight < (uint64_t)chainActive.Tip()->nHeight && ((uint64_t)chainActive.Tip()->nHeight - nBlockHeight > (uint64_t)COINBASE_MATURITY))
{
Expand All @@ -253,6 +256,7 @@ class CInputCoin {

COutPoint outpoint;
CTxOut txout;
bool isCoinBase;

bool operator<(const CInputCoin& rhs) const {
return outpoint < rhs.outpoint;
Expand Down
24 changes: 16 additions & 8 deletions src/wallet/wallet_transaction.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -550,7 +550,15 @@ bool CWallet::CreateTransaction(std::vector<CKeyStore*>& accountsToTry, const st
}
}

unsigned int nBytes = GetVirtualTransactionSize(txNew);
unsigned int numCoinBase = 0;
for (const auto& coin : setCoins)
{
if (coin.isCoinBase)
{
++numCoinBase;
}
}
unsigned int nBytesDiscounted = GetVirtualTransactionSizeDiscounted(txNew, numCoinBase);

CTransaction txNewConst(txNew);

Expand All @@ -565,13 +573,13 @@ bool CWallet::CreateTransaction(std::vector<CKeyStore*>& accountsToTry, const st
if (coinControl && coinControl->nConfirmTarget > 0)
currentConfirmationTarget = coinControl->nConfirmTarget;

CAmount nFeeNeeded = GetMinimumFee(nBytes, currentConfirmationTarget, ::mempool, ::feeEstimator);
CAmount nFeeNeeded = GetMinimumFee(nBytesDiscounted, currentConfirmationTarget, ::mempool, ::feeEstimator);
if (coinControl && coinControl->fOverrideFeeRate)
nFeeNeeded = coinControl->nFeeRate.GetFee(nBytes);
nFeeNeeded = coinControl->nFeeRate.GetFee(nBytesDiscounted);

// 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.
if (nFeeNeeded < ::minRelayTxFee.GetFee(nBytes))
if (nFeeNeeded < ::minRelayTxFee.GetFee(nBytesDiscounted))
{
strFailReason = _("Transaction too large for fee policy");
return false;
Expand Down Expand Up @@ -653,7 +661,7 @@ bool CWallet::CreateTransaction(std::vector<CKeyStore*>& accountsToTry, const st
if (GetBoolArg("-walletrejectlongchains", DEFAULT_WALLET_REJECT_LONG_CHAINS)) {
// Lastly, ensure this tx will pass the mempool's chain limits
LockPoints lp;
CTxMemPoolEntry entry(wtxNew.tx, 0, 0, 0, false, 0, lp);
CTxMemPoolEntry entry(wtxNew.tx, 0, 0, 0, 0, 0, lp);
CTxMemPool::setEntries setAncestors;
size_t nLimitAncestors = GetArg("-limitancestorcount", DEFAULT_ANCESTOR_LIMIT);
size_t nLimitAncestorSize = GetArg("-limitancestorsize", DEFAULT_ANCESTOR_SIZE_LIMIT)*1000;
Expand Down Expand Up @@ -927,7 +935,7 @@ bool CWallet::AddFeeForTransaction(CAccount* forAccount, CMutableTransaction& tx
if (GetBoolArg("-walletrejectlongchains", DEFAULT_WALLET_REJECT_LONG_CHAINS)) {
// Lastly, ensure this tx will pass the mempool's chain limits
LockPoints lp;
CTxMemPoolEntry entry(wtxNew.tx, 0, 0, 0, false, 0, lp);
CTxMemPoolEntry entry(wtxNew.tx, 0, 0, 0, 0, 0, lp);
CTxMemPool::setEntries setAncestors;
size_t nLimitAncestors = GetArg("-limitancestorcount", DEFAULT_ANCESTOR_LIMIT);
size_t nLimitAncestorSize = GetArg("-limitancestorsize", DEFAULT_ANCESTOR_SIZE_LIMIT)*1000;
Expand Down Expand Up @@ -976,7 +984,7 @@ bool CWallet::PrepareRenewWitnessAccountTransaction(CAccount* funderAccount, CAc
addedAny = true;

// Add witness input
AddTxInput(tx, CInputCoin(witCoin.outpoint, witCoin.coin.out, allowIndexBased, witCoin.coin.nHeight, witCoin.coin.nTxIndex), false);
AddTxInput(tx, CInputCoin(witCoin.outpoint, witCoin.coin.out, allowIndexBased, false, witCoin.coin.nHeight, witCoin.coin.nTxIndex), false);

// Add witness output
CTxOut renewedWitnessTxOutput;
Expand Down Expand Up @@ -1070,7 +1078,7 @@ void CWallet::PrepareUpgradeWitnessAccountTransaction(CAccount* funderAccount, C
if (::IsMine(*targetWitnessAccount, witCoin.coin.out))
{
// Add witness input
AddTxInput(tx, CInputCoin(witCoin.outpoint, witCoin.coin.out, true, witCoin.coin.nHeight, witCoin.coin.nTxIndex), false);
AddTxInput(tx, CInputCoin(witCoin.outpoint, witCoin.coin.out, true, false,witCoin.coin.nHeight, witCoin.coin.nTxIndex), false);

// Add witness output
CTxOut renewedWitnessTxOutput;
Expand Down
6 changes: 3 additions & 3 deletions src/wallet/witness_operations.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -124,7 +124,7 @@ void extendwitnessaddresshelper(CAccount* fundingAccount, witnessOutputsInfoVect
CMutableTransaction extendWitnessTransaction(CTransaction::SEGSIG_ACTIVATION_VERSION);
{
// Add the existing witness output as an input
pwallet->AddTxInput(extendWitnessTransaction, CInputCoin(currentWitnessOutpoint, currentWitnessTxOut, true, currentWitnessHeight, currentWitnessTxIndex), false);
pwallet->AddTxInput(extendWitnessTransaction, CInputCoin(currentWitnessOutpoint, currentWitnessTxOut, true, false, currentWitnessHeight, currentWitnessTxIndex), false);

// Add new witness output
CTxOut extendedWitnessTxOutput;
Expand Down Expand Up @@ -398,7 +398,7 @@ void rotatewitnessaddresshelper(CAccount* fundingAccount, witnessOutputsInfoVect
for (const auto& [currentWitnessTxOut, currentWitnessHeight, currentWitnessTxIndex, currentWitnessOutpoint]: unspentWitnessOutputs)
{
// Add input
pwallet->AddTxInput(rotateWitnessTransaction, CInputCoin(currentWitnessOutpoint, currentWitnessTxOut, true, currentWitnessHeight, currentWitnessTxIndex), false);
pwallet->AddTxInput(rotateWitnessTransaction, CInputCoin(currentWitnessOutpoint, currentWitnessTxOut, true, false, currentWitnessHeight, currentWitnessTxIndex), false);

// Get witness details
CTxOutPoW2Witness currentWitnessDetails;
Expand Down Expand Up @@ -835,7 +835,7 @@ void redistributeandextendwitnessaccount(CWallet* pwallet, CAccount* fundingAcco
// Add all original outputs as inputs
for (const auto& [currentWitnessTxOut, currentWitnessHeight, currentWitnessTxIndex, currentWitnessOutpoint]: unspentWitnessOutputs)
{
pwallet->AddTxInput(witnessTransaction, CInputCoin(currentWitnessOutpoint, currentWitnessTxOut, true, currentWitnessHeight, currentWitnessTxIndex), false);
pwallet->AddTxInput(witnessTransaction, CInputCoin(currentWitnessOutpoint, currentWitnessTxOut, true, false, currentWitnessHeight, currentWitnessTxIndex), false);

CTxOutPoW2Witness details;
if (!GetPow2WitnessOutput(currentWitnessTxOut, details))
Expand Down

0 comments on commit 8de5c7d

Please sign in to comment.