View
@@ -0,0 +1,255 @@
+// Copyright (c) 2012-2017 The Bitcoin Core developers
+// Distributed under the MIT software license, see the accompanying
+// file COPYING or http://www.opensource.org/licenses/mit-license.php.
+
+#include "policy/coin_age_priority.h"
+
+#include "coins.h"
+#include "consensus/validation.h"
+#include "miner.h"
+#include "policy/policy.h"
+#include "primitives/transaction.h"
+#include "txmempool.h"
+#include "util.h"
+#include "validation.h"
+
+unsigned int CalculateModifiedSize(const CTransaction& tx, unsigned int nTxSize)
+{
+ // In order to avoid disincentivizing cleaning up the UTXO set we don't count
+ // the constant overhead for each txin and up to 110 bytes of scriptSig (which
+ // is enough to cover a compressed pubkey p2sh redemption) for priority.
+ // Providing any more cleanup incentive than making additional inputs free would
+ // risk encouraging people to create junk outputs to redeem later.
+ if (nTxSize == 0)
+ nTxSize = (GetTransactionWeight(tx) + WITNESS_SCALE_FACTOR - 1) / WITNESS_SCALE_FACTOR;
+ for (std::vector<CTxIn>::const_iterator it(tx.vin.begin()); it != tx.vin.end(); ++it)
+ {
+ unsigned int offset = 41U + std::min(110U, (unsigned int)it->scriptSig.size());
+ if (nTxSize > offset)
+ nTxSize -= offset;
+ }
+ return nTxSize;
+}
+
+double ComputePriority(const CTransaction& tx, double dPriorityInputs, unsigned int nTxSize)
+{
+ nTxSize = CalculateModifiedSize(tx, nTxSize);
+ if (nTxSize == 0) return 0.0;
+
+ return dPriorityInputs / nTxSize;
+}
+
+double GetPriority(const CTransaction &tx, const CCoinsViewCache& view, int nHeight, CAmount &inChainInputValue)
+{
+ inChainInputValue = 0;
+ if (tx.IsCoinBase())
+ return 0.0;
+ double dResult = 0.0;
+ for (const CTxIn& txin : tx.vin)
+ {
+ const Coin& coin = view.AccessCoin(txin.prevout);
+ if (coin.IsSpent()) {
+ continue;
+ }
+ if (coin.nHeight <= nHeight) {
+ dResult += (double)(coin.out.nValue) * (nHeight - coin.nHeight);
+ inChainInputValue += coin.out.nValue;
+ }
+ }
+ return ComputePriority(tx, dResult);
+}
+
+void CTxMemPoolEntry::UpdateCachedPriority(unsigned int currentHeight, CAmount valueInCurrentBlock)
+{
+ int heightDiff = currentHeight - cachedHeight;
+ double deltaPriority = ((double)heightDiff*inChainInputValue)/nModSize;
+ cachedPriority += deltaPriority;
+ cachedHeight = currentHeight;
+ inChainInputValue += valueInCurrentBlock;
+ assert(MoneyRange(inChainInputValue));
+}
+
+struct update_priority
+{
+ update_priority(unsigned int _height, CAmount _value) :
+ height(_height), value(_value)
+ {}
+
+ void operator() (CTxMemPoolEntry &e)
+ { e.UpdateCachedPriority(height, value); }
+
+ private:
+ unsigned int height;
+ CAmount value;
+};
+
+void CTxMemPool::UpdateDependentPriorities(const CTransaction &tx, unsigned int nBlockHeight, bool addToChain)
+{
+ LOCK(cs);
+ for (unsigned int i = 0; i < tx.vout.size(); i++) {
+ auto it = mapNextTx.find(COutPoint(tx.GetHash(), i));
+ if (it == mapNextTx.end())
+ continue;
+ uint256 hash = it->second->GetHash();
+ txiter iter = mapTx.find(hash);
+ mapTx.modify(iter, update_priority(nBlockHeight, addToChain ? tx.vout[i].nValue : -tx.vout[i].nValue));
+ }
+}
+
+double
+CTxMemPoolEntry::GetPriority(unsigned int currentHeight) const
+{
+ // This will only return accurate results when currentHeight >= the heights
+ // at which all the in-chain inputs of the tx were included in blocks.
+ // Typical usage of GetPriority with chainActive.Height() will ensure this.
+ int heightDiff = currentHeight - cachedHeight;
+ double deltaPriority = ((double)heightDiff*inChainInputValue)/nModSize;
+ double dResult = cachedPriority + deltaPriority;
+ if (dResult < 0) // This should only happen if it was called with an invalid height
+ dResult = 0;
+ return dResult;
+}
+
+// We want to sort transactions by coin age priority
+typedef std::pair<double, CTxMemPool::txiter> TxCoinAgePriority;
+
+struct TxCoinAgePriorityCompare
+{
+ bool operator()(const TxCoinAgePriority& a, const TxCoinAgePriority& b)
+ {
+ if (a.first == b.first)
+ return CompareTxMemPoolEntryByScore()(*(b.second), *(a.second)); //Reverse order to make sort less than
+ return a.first < b.first;
+ }
+};
+
+bool BlockAssembler::isStillDependent(CTxMemPool::txiter iter)
+{
+ for (CTxMemPool::txiter parent : mempool.GetMemPoolParents(iter))
+ {
+ if (!inBlock.count(parent)) {
+ return true;
+ }
+ }
+ return false;
+}
+
+bool BlockAssembler::TestForBlock(CTxMemPool::txiter iter)
+{
+ uint64_t packageSize = iter->GetSizeWithAncestors();
+ int64_t packageSigOps = iter->GetSigOpCostWithAncestors();
+ if (!TestPackage(packageSize, packageSigOps)) {
+ // If the block is so close to full that no more txs will fit
+ // or if we've tried more than 50 times to fill remaining space
+ // then flag that the block is finished
+ if (nBlockWeight > nBlockMaxWeight - 400 || nBlockSigOpsCost > MAX_BLOCK_SIGOPS_COST - 8 || lastFewTxs > 50) {
+ blockFinished = true;
+ return false;
+ }
+ // Once we're within 4000 weight of a full block, only look at 50 more txs
+ // to try to fill the remaining space.
+ if (nBlockWeight > nBlockMaxWeight - 4000) {
+ ++lastFewTxs;
+ }
+ return false;
+ }
+
+ CTxMemPool::setEntries package;
+ package.insert(iter);
+ if (!TestPackageTransactions(package)) {
+ if (nBlockSize > nBlockMaxSize - 100 || lastFewTxs > 50) {
+ blockFinished = true;
+ return false;
+ }
+ if (nBlockSize > nBlockMaxSize - 1000) {
+ ++lastFewTxs;
+ }
+ return false;
+ }
+
+ return true;
+}
+
+void BlockAssembler::addPriorityTxs(int &nPackagesSelected)
+{
+ // How much of the block should be dedicated to high-priority transactions,
+ // included regardless of the fees they pay
+ unsigned int nBlockPrioritySize = gArgs.GetArg("-blockprioritysize", DEFAULT_BLOCK_PRIORITY_SIZE);
+ nBlockPrioritySize = std::min(nBlockMaxSize, nBlockPrioritySize);
+
+ if (nBlockPrioritySize == 0) {
+ return;
+ }
+
+ bool fSizeAccounting = fNeedSizeAccounting;
+ fNeedSizeAccounting = true;
+
+ // This vector will be sorted into a priority queue:
+ std::vector<TxCoinAgePriority> vecPriority;
+ TxCoinAgePriorityCompare pricomparer;
+ std::map<CTxMemPool::txiter, double, CTxMemPool::CompareIteratorByHash> waitPriMap;
+ typedef std::map<CTxMemPool::txiter, double, CTxMemPool::CompareIteratorByHash>::iterator waitPriIter;
+ double actualPriority = -1;
+
+ vecPriority.reserve(mempool.mapTx.size());
+ for (CTxMemPool::indexed_transaction_set::iterator mi = mempool.mapTx.begin();
+ mi != mempool.mapTx.end(); ++mi)
+ {
+ double dPriority = mi->GetPriority(nHeight);
+ CAmount dummy;
+ mempool.ApplyDeltas(mi->GetTx().GetHash(), dPriority, dummy);
+ vecPriority.push_back(TxCoinAgePriority(dPriority, mi));
+ }
+ std::make_heap(vecPriority.begin(), vecPriority.end(), pricomparer);
+
+ CTxMemPool::txiter iter;
+ while (!vecPriority.empty() && !blockFinished) { // add a tx from priority queue to fill the blockprioritysize
+ iter = vecPriority.front().second;
+ actualPriority = vecPriority.front().first;
+ std::pop_heap(vecPriority.begin(), vecPriority.end(), pricomparer);
+ vecPriority.pop_back();
+
+ // If tx already in block, skip
+ if (inBlock.count(iter)) {
+ assert(false); // shouldn't happen for priority txs
+ continue;
+ }
+
+ // cannot accept witness transactions into a non-witness block
+ if (!fIncludeWitness && iter->GetTx().HasWitness())
+ continue;
+
+ // If tx is dependent on other mempool txs which haven't yet been included
+ // then put it in the waitSet
+ if (isStillDependent(iter)) {
+ waitPriMap.insert(std::make_pair(iter, actualPriority));
+ continue;
+ }
+
+ // If this tx fits in the block add it, otherwise keep looping
+ if (TestForBlock(iter)) {
+ AddToBlock(iter);
+
+ ++nPackagesSelected;
+
+ // If now that this txs is added we've surpassed our desired priority size
+ // or have dropped below the minimum priority threshold, then we're done adding priority txs
+ if (nBlockSize >= nBlockPrioritySize || actualPriority <= MINIMUM_TX_PRIORITY) {
+ break;
+ }
+
+ // This tx was successfully added, so
+ // add transactions that depend on this one to the priority queue to try again
+ for (CTxMemPool::txiter child : mempool.GetMemPoolChildren(iter))
+ {
+ waitPriIter wpiter = waitPriMap.find(child);
+ if (wpiter != waitPriMap.end()) {
+ vecPriority.push_back(TxCoinAgePriority(wpiter->second,child));
+ std::push_heap(vecPriority.begin(), vecPriority.end(), pricomparer);
+ waitPriMap.erase(wpiter);
+ }
+ }
+ }
+ }
+ fNeedSizeAccounting = fSizeAccounting;
+}
View
@@ -0,0 +1,26 @@
+// Copyright (c) 2012-2017 The Bitcoin Core developers
+// Distributed under the MIT software license, see the accompanying
+// file COPYING or http://www.opensource.org/licenses/mit-license.php.
+
+#ifndef BITCOIN_POLICY_COIN_AGE_PRIORITY_H
+#define BITCOIN_POLICY_COIN_AGE_PRIORITY_H
+
+#include "amount.h"
+
+class CCoinsViewCache;
+class CTransaction;
+
+// Compute modified tx size for priority calculation (optionally given tx size)
+unsigned int CalculateModifiedSize(const CTransaction& tx, unsigned int nTxSize=0);
+
+// Compute priority, given sum coin-age of inputs and (optionally) tx size
+double ComputePriority(const CTransaction& tx, double dPriorityInputs, unsigned int nTxSize=0);
+
+/**
+ * Return priority of tx at height nHeight. Also calculate the sum of the values of the inputs
+ * that are already in the chain. These are the inputs that will age and increase priority as
+ * new blocks are added to the chain.
+ */
+double GetPriority(const CTransaction &tx, const CCoinsViewCache& view, int nHeight, CAmount &inChainInputValue);
+
+#endif // BITCOIN_POLICY_COIN_AGE_PRIORITY_H
View
@@ -19,6 +19,10 @@ class CTxOut;
/** Default for -blockmaxsize, which controls the maximum size of block the mining code will create **/
static const unsigned int DEFAULT_BLOCK_MAX_SIZE = 750000;
+/** Default for -blockprioritysize, maximum space for zero/low-fee transactions **/
+static const unsigned int DEFAULT_BLOCK_PRIORITY_SIZE = 0;
+/** Minimum priority for transactions to be accepted into the priority area **/
+static const double MINIMUM_TX_PRIORITY = COIN * 144 / 250;
/** Default for -blockmaxweight, which controls the range of block weights the mining code will create **/
static const unsigned int DEFAULT_BLOCK_MAX_WEIGHT = 3000000;
/** Default for -blockmintxfee, which sets the minimum feerate for a transaction in blocks created by mining code **/
View
@@ -337,6 +337,8 @@ std::string EntryDescriptionString()
" \"modifiedfee\" : n, (numeric) transaction fee with fee deltas used for mining priority\n"
" \"time\" : n, (numeric) local time transaction entered pool in seconds since 1 Jan 1970 GMT\n"
" \"height\" : n, (numeric) block height when transaction entered pool\n"
+ " \"startingpriority\" : n, (numeric) Priority when transaction entered pool\n"
+ " \"currentpriority\" : n, (numeric) Transaction priority now\n"
" \"descendantcount\" : n, (numeric) number of in-mempool descendant transactions (including this one)\n"
" \"descendantsize\" : n, (numeric) virtual transaction size of in-mempool descendants (including this one)\n"
" \"descendantfees\" : n, (numeric) modified fees (see above) of in-mempool descendants (including this one)\n"
@@ -358,6 +360,8 @@ void entryToJSON(UniValue &info, const CTxMemPoolEntry &e)
info.push_back(Pair("modifiedfee", ValueFromAmount(e.GetModifiedFee())));
info.push_back(Pair("time", e.GetTime()));
info.push_back(Pair("height", (int)e.GetHeight()));
+ info.push_back(Pair("startingpriority", e.GetStartingPriority()));
+ info.push_back(Pair("currentpriority", e.GetPriority(chainActive.Height() + 1)));
info.push_back(Pair("descendantcount", e.GetCountWithDescendants()));
info.push_back(Pair("descendantsize", e.GetSizeWithDescendants()));
info.push_back(Pair("descendantfees", e.GetModFeesWithDescendants()));
View
@@ -123,7 +123,7 @@ static const CRPCConvertParam vRPCConvertParams[] =
{ "estimatesmartfee", 0, "nblocks" },
{ "estimaterawfee", 0, "nblocks" },
{ "estimaterawfee", 1, "threshold" },
- { "prioritisetransaction", 1, "dummy" },
+ { "prioritisetransaction", 1, "priority_delta" },
{ "prioritisetransaction", 2, "fee_delta" },
{ "setban", 2, "bantime" },
{ "setban", 3, "absolute" },
View
@@ -229,15 +229,16 @@ UniValue getmininginfo(const JSONRPCRequest& request)
// NOTE: Unlike wallet RPC (which use BTC values), mining RPCs follow GBT (BIP 22) in using satoshi amounts
UniValue prioritisetransaction(const JSONRPCRequest& request)
{
- if (request.fHelp || request.params.size() != 3)
+ if (request.fHelp || request.params.size() < 2 || request.params.size() > 3)
throw std::runtime_error(
- "prioritisetransaction <txid> <dummy value> <fee delta>\n"
+ "prioritisetransaction <txid> (priority delta) (fee delta)\n"
"Accepts the transaction into mined blocks at a higher (or lower) priority\n"
"\nArguments:\n"
"1. \"txid\" (string, required) The transaction id.\n"
- "2. dummy (numeric, optional) API-Compatibility for previous API. Must be zero or null.\n"
- " DEPRECATED. For forward compatibility use named arguments and omit this parameter.\n"
- "3. fee_delta (numeric, required) The fee value (in satoshis) to add (or subtract, if negative).\n"
+ "2. priority_delta (numeric, optional) The priority to add or subtract.\n"
+ " The transaction selection algorithm considers the tx as it would have a higher priority.\n"
+ " (priority of a transaction is calculated: coinage * value_in_satoshis / txsize) \n"
+ "3. fee_delta (numeric, optional) The fee value (in satoshis) to add (or subtract, if negative).\n"
" The fee is not actually paid, only the algorithm for selecting transactions into a block\n"
" considers the transaction as it would have paid a higher (or lower) fee.\n"
"\nResult:\n"
@@ -250,13 +251,17 @@ UniValue prioritisetransaction(const JSONRPCRequest& request)
LOCK(cs_main);
uint256 hash = ParseHashStr(request.params[0].get_str(), "txid");
- CAmount nAmount = request.params[2].get_int64();
+ double priority_delta = 0;
+ CAmount nAmount = 0;
- if (!(request.params[1].isNull() || request.params[1].get_real() == 0)) {
- throw JSONRPCError(RPC_INVALID_PARAMETER, "Priority is no longer supported, dummy argument to prioritisetransaction must be 0.");
+ if (!request.params[1].isNull()) {
+ priority_delta = request.params[1].get_real();
+ }
+ if (!request.params[2].isNull()) {
+ nAmount = request.params[2].get_int64();
}
- mempool.PrioritiseTransaction(hash, nAmount);
+ mempool.PrioritiseTransaction(hash, priority_delta, nAmount);
return true;
}
@@ -573,6 +578,9 @@ UniValue getblocktemplate(const JSONRPCRequest& request)
}
entry.push_back(Pair("sigops", nTxSigOps));
entry.push_back(Pair("weight", GetTransactionWeight(tx)));
+ if (index_in_template && !pblocktemplate->vTxPriorities.empty()) {
+ entry.push_back(Pair("priority", pblocktemplate->vTxPriorities[index_in_template]));
+ }
transactions.push_back(entry);
}
@@ -970,7 +978,7 @@ static const CRPCCommand commands[] =
// --------------------- ------------------------ ----------------------- ----------
{ "mining", "getnetworkhashps", &getnetworkhashps, true, {"nblocks","height"} },
{ "mining", "getmininginfo", &getmininginfo, true, {} },
- { "mining", "prioritisetransaction", &prioritisetransaction, true, {"txid","dummy","fee_delta"} },
+ { "mining", "prioritisetransaction", &prioritisetransaction, true, {"txid","priority_delta","fee_delta"} },
{ "mining", "getblocktemplate", &getblocktemplate, true, {"template_request"} },
{ "mining", "submitblock", &submitblock, true, {"hexdata","dummy"} },
View
@@ -157,6 +157,8 @@ CTxMemPoolEntry TestMemPoolEntryHelper::FromTx(const CMutableTransaction &tx) {
}
CTxMemPoolEntry TestMemPoolEntryHelper::FromTx(const CTransaction &txn) {
- return CTxMemPoolEntry(MakeTransactionRef(txn), nFee, nTime, nHeight,
- spendsCoinbase, sigOpCost, lp);
+ const double dPriority = 0;
+ const CAmount inChainValue = 0;
+ return CTxMemPoolEntry(MakeTransactionRef(txn), nFee, nTime, dPriority, nHeight,
+ inChainValue, spendsCoinbase, sigOpCost, lp);
}
View
@@ -9,6 +9,7 @@
#include "consensus/tx_verify.h"
#include "consensus/validation.h"
#include "validation.h"
+#include "policy/coin_age_priority.h"
#include "policy/policy.h"
#include "policy/fees.h"
#include "reverse_iterator.h"
@@ -19,20 +20,29 @@
#include "utiltime.h"
CTxMemPoolEntry::CTxMemPoolEntry(const CTransactionRef& _tx, const CAmount& _nFee,
- int64_t _nTime, unsigned int _entryHeight,
+ int64_t _nTime, double _entryPriority, unsigned int _entryHeight,
+ CAmount _inChainInputValue,
bool _spendsCoinbase, int64_t _sigOpsCost, LockPoints lp):
- tx(_tx), nFee(_nFee), nTime(_nTime), entryHeight(_entryHeight),
+ tx(_tx), nFee(_nFee), nTime(_nTime), entryPriority(_entryPriority), entryHeight(_entryHeight),
+ inChainInputValue(_inChainInputValue),
spendsCoinbase(_spendsCoinbase), sigOpCost(_sigOpsCost), lockPoints(lp)
{
nTxWeight = GetTransactionWeight(*tx);
+ nModSize = CalculateModifiedSize(*tx, GetTxSize());
nUsageSize = RecursiveDynamicUsage(tx);
nCountWithDescendants = 1;
nSizeWithDescendants = GetTxSize();
nModFeesWithDescendants = nFee;
+ CAmount nValueIn = tx->GetValueOut()+nFee;
+ assert(inChainInputValue <= nValueIn);
feeDelta = 0;
+ // Since entries arrive *after* the tip's height, their entry priority is for the height+1
+ cachedHeight = entryHeight + 1;
+ cachedPriority = entryPriority;
+
nCountWithAncestors = 1;
nSizeWithAncestors = GetTxSize();
nModFeesWithAncestors = nFee;
@@ -375,11 +385,11 @@ bool CTxMemPool::addUnchecked(const uint256& hash, const CTxMemPoolEntry &entry,
// Update transaction for any feeDelta created by PrioritiseTransaction
// TODO: refactor so that the fee delta is calculated before inserting
// into mapTx.
- std::map<uint256, CAmount>::const_iterator pos = mapDeltas.find(hash);
+ std::map<uint256, std::pair<double, CAmount> >::const_iterator pos = mapDeltas.find(hash);
if (pos != mapDeltas.end()) {
- const CAmount &delta = pos->second;
- if (delta) {
- mapTx.modify(newit, update_fee_delta(delta));
+ const std::pair<double, CAmount> &deltas = pos->second;
+ if (deltas.second) {
+ mapTx.modify(newit, update_fee_delta(deltas.second));
}
}
@@ -580,6 +590,7 @@ void CTxMemPool::removeForBlock(const std::vector<CTransactionRef>& vtx, unsigne
if (minerPolicyEstimator) {minerPolicyEstimator->processBlock(nBlockHeight, entries);}
for (const auto& tx : vtx)
{
+ UpdateDependentPriorities(*tx, nBlockHeight, true);
txiter it = mapTx.find(tx->GetHash());
if (it != mapTx.end()) {
setEntries stage;
@@ -629,10 +640,20 @@ void CTxMemPool::check(const CCoinsViewCache *pcoins) const
const int64_t nSpendHeight = GetSpendHeight(mempoolDuplicate);
LOCK(cs);
+ const unsigned int nBlockHeight = chainActive.Height();
+ CCoinsViewMemPool viewMemPool(pcoinsTip, *this);
+ CCoinsViewCache view(&viewMemPool);
std::list<const CTxMemPoolEntry*> waitingOnDependants;
for (indexed_transaction_set::const_iterator it = mapTx.begin(); it != mapTx.end(); it++) {
unsigned int i = 0;
checkTotal += it->GetTxSize();
+ CAmount dummyValue;
+ double freshPriority = GetPriority(it->GetTx(), view, nBlockHeight + 1, dummyValue);
+ double cachePriority = it->GetPriority(nBlockHeight + 1);
+ double priDiff = cachePriority > freshPriority ? cachePriority - freshPriority : freshPriority - cachePriority;
+ // Verify that the difference between the on the fly calculation and a fresh calculation
+ // is small enough to be a result of double imprecision.
+ assert(priDiff < .0001 * freshPriority + 1);
innerUsage += it->DynamicMemoryUsage();
const CTransaction& tx = it->GetTx();
txlinksMap::const_iterator linksiter = mapLinks.find(it);
@@ -849,15 +870,16 @@ TxMempoolInfo CTxMemPool::info(const uint256& hash) const
return GetInfo(i);
}
-void CTxMemPool::PrioritiseTransaction(const uint256& hash, const CAmount& nFeeDelta)
+void CTxMemPool::PrioritiseTransaction(const uint256& hash, double dPriorityDelta, const CAmount& nFeeDelta)
{
{
LOCK(cs);
- CAmount &delta = mapDeltas[hash];
- delta += nFeeDelta;
+ std::pair<double, CAmount> &deltas = mapDeltas[hash];
+ deltas.first += dPriorityDelta;
+ deltas.second += nFeeDelta;
txiter it = mapTx.find(hash);
if (it != mapTx.end()) {
- mapTx.modify(it, update_fee_delta(delta));
+ mapTx.modify(it, update_fee_delta(deltas.second));
// Now update all ancestors' modified fees with descendants
setEntries setAncestors;
uint64_t nNoLimit = std::numeric_limits<uint64_t>::max();
@@ -876,17 +898,18 @@ void CTxMemPool::PrioritiseTransaction(const uint256& hash, const CAmount& nFeeD
++nTransactionsUpdated;
}
}
- LogPrintf("PrioritiseTransaction: %s feerate += %s\n", hash.ToString(), FormatMoney(nFeeDelta));
+ LogPrintf("PrioritiseTransaction: %s priority += %f, fee += %d\n", hash.ToString(), dPriorityDelta, FormatMoney(nFeeDelta));
}
-void CTxMemPool::ApplyDelta(const uint256 hash, CAmount &nFeeDelta) const
+void CTxMemPool::ApplyDeltas(const uint256 hash, double &dPriorityDelta, CAmount &nFeeDelta) const
{
LOCK(cs);
- std::map<uint256, CAmount>::const_iterator pos = mapDeltas.find(hash);
+ std::map<uint256, std::pair<double, CAmount> >::const_iterator pos = mapDeltas.find(hash);
if (pos == mapDeltas.end())
return;
- const CAmount &delta = pos->second;
- nFeeDelta += delta;
+ const std::pair<double, CAmount> &deltas = pos->second;
+ dPriorityDelta += deltas.first;
+ nFeeDelta += deltas.second;
}
void CTxMemPool::ClearPrioritisation(const uint256 hash)
View
@@ -70,9 +70,14 @@ class CTxMemPoolEntry
CTransactionRef tx;
CAmount nFee; //!< Cached to avoid expensive parent-transaction lookups
size_t nTxWeight; //!< ... and avoid recomputing tx weight (also used for GetTxSize())
+ size_t nModSize; //!< ... and modified size for priority
size_t nUsageSize; //!< ... and total memory usage
int64_t nTime; //!< Local time when entering the mempool
+ double entryPriority; //!< Priority when entering the mempool
unsigned int entryHeight; //!< Chain height when entering the mempool
+ double cachedPriority; //!< Last calculated priority
+ unsigned int cachedHeight; //!< Height at which priority was last calculated
+ CAmount inChainInputValue; //!< Sum of all txin values that are already in blockchain
bool spendsCoinbase; //!< keep track of transactions that spend a coinbase
int64_t sigOpCost; //!< Total sigop cost
int64_t feeDelta; //!< Used for determining the priority of the transaction for mining in a block
@@ -93,14 +98,25 @@ class CTxMemPoolEntry
public:
CTxMemPoolEntry(const CTransactionRef& _tx, const CAmount& _nFee,
- int64_t _nTime, unsigned int _entryHeight,
- bool spendsCoinbase,
+ int64_t _nTime, double _entryPriority, unsigned int _entryHeight,
+ CAmount _inChainInputValue, bool spendsCoinbase,
int64_t nSigOpsCost, LockPoints lp);
CTxMemPoolEntry(const CTxMemPoolEntry& other);
const CTransaction& GetTx() const { return *this->tx; }
CTransactionRef GetSharedTx() const { return this->tx; }
+ double GetStartingPriority() const {return entryPriority; }
+ /**
+ * Fast calculation of priority as update from cached value, but only valid if
+ * currentHeight is greater than last height it was recalculated.
+ */
+ double GetPriority(unsigned int currentHeight) const;
+ /**
+ * Recalculate the cached priority as of currentHeight and adjust inChainInputValue by
+ * valueInCurrentBlock which represents input that was just added to or removed from the blockchain.
+ */
+ void UpdateCachedPriority(unsigned int currentHeight, CAmount valueInCurrentBlock);
const CAmount& GetFee() const { return nFee; }
size_t GetTxSize() const;
size_t GetTxWeight() const { return nTxWeight; }
@@ -499,7 +515,7 @@ class CTxMemPool
public:
indirectmap<COutPoint, const CTransaction*> mapNextTx;
- std::map<uint256, CAmount> mapDeltas;
+ std::map<uint256, std::pair<double, CAmount> > mapDeltas;
/** Create a new CTxMemPool.
*/
@@ -538,10 +554,16 @@ class CTxMemPool
* the tx is not dependent on other mempool transactions to be included in a block.
*/
bool HasNoInputsOf(const CTransaction& tx) const;
+ /**
+ * Update all transactions in the mempool which depend on tx to recalculate their priority
+ * and adjust the input value that will age to reflect that the inputs from this transaction have
+ * either just been added to the chain or just been removed.
+ */
+ void UpdateDependentPriorities(const CTransaction &tx, unsigned int nBlockHeight, bool addToChain);
/** Affect CreateNewBlock prioritisation of transactions */
- void PrioritiseTransaction(const uint256& hash, const CAmount& nFeeDelta);
- void ApplyDelta(const uint256 hash, CAmount &nFeeDelta) const;
+ void PrioritiseTransaction(const uint256& hash, double dPriorityDelta, const CAmount& nFeeDelta);
+ void ApplyDeltas(const uint256 hash, double &dPriorityDelta, CAmount &nFeeDelta) const;
void ClearPrioritisation(const uint256 hash);
public:
View
@@ -18,6 +18,7 @@
#include "fs.h"
#include "hash.h"
#include "init.h"
+#include "policy/coin_age_priority.h"
#include "policy/fees.h"
#include "policy/policy.h"
#include "policy/rbf.h"
@@ -615,7 +616,12 @@ static bool AcceptToMemoryPoolWorker(const CChainParams& chainparams, CTxMemPool
CAmount nFees = nValueIn-nValueOut;
// nModifiedFees includes any fee deltas from PrioritiseTransaction
CAmount nModifiedFees = nFees;
- pool.ApplyDelta(hash, nModifiedFees);
+ double nPriorityDummy = 0;
+ pool.ApplyDeltas(hash, nPriorityDummy, nModifiedFees);
+
+ CAmount inChainInputValue;
+ // Since entries arrive *after* the tip's height, their priority is for the height+1
+ double dPriority = GetPriority(tx, view, chainActive.Height() + 1, inChainInputValue);
// Keep track of transactions that spend a coinbase, which we re-scan
// during reorgs to ensure COINBASE_MATURITY is still met.
@@ -628,8 +634,8 @@ static bool AcceptToMemoryPoolWorker(const CChainParams& chainparams, CTxMemPool
}
}
- CTxMemPoolEntry entry(ptx, nFees, nAcceptTime, chainActive.Height(),
- fSpendsCoinbase, nSigOpsCost, lp);
+ CTxMemPoolEntry entry(ptx, nFees, nAcceptTime, dPriority, chainActive.Height(),
+ inChainInputValue, fSpendsCoinbase, nSigOpsCost, lp);
unsigned int nSize = entry.GetTxSize();
// Check that the transaction doesn't have an excessive number of
@@ -2129,6 +2135,7 @@ bool static DisconnectTip(CValidationState& state, const CChainParams& chainpara
if (disconnectpool) {
// Save transactions to re-add to mempool at end of reorg
for (auto it = block.vtx.rbegin(); it != block.vtx.rend(); ++it) {
+ mempool.UpdateDependentPriorities(*(*it), pindexDelete->nHeight, false);
disconnectpool->addTransaction(*it);
}
while (disconnectpool->DynamicMemoryUsage() > MAX_DISCONNECTED_TX_POOL_SIZE * 1000) {
@@ -4350,14 +4357,8 @@ bool LoadMempool(void)
if (it != mapData.end()) {
try {
CDataStream ss(it->second, SER_DISK, CLIENT_VERSION);
- std::map<uint256, std::pair<double, CAmount>> mapDeltas;
- ss >> mapDeltas;
LOCK(mempool.cs);
- for (const auto& it : mapDeltas) {
- const uint256& txid = it.first;
- const CAmount& amountdelta = it.second.second;
- mempool.PrioritiseTransaction(txid, amountdelta);
- }
+ ss >> mempool.mapDeltas;
} catch (const std::exception& e) {
LogPrintf("Failed to deserialize mempool %s from disk: %s. Continuing anyway.\n", "deltas", e.what());
}
@@ -4431,9 +4432,7 @@ bool DumpMempool(void)
{
LOCK(mempool.cs);
- for (const auto &i : mempool.mapDeltas) {
- mapDeltas[i.first] = std::make_pair(0.0, i.second);
- }
+ mapDeltas = mempool.mapDeltas;
vinfo = mempool.infoAll();
}
View
@@ -2966,7 +2966,7 @@ bool CWallet::CreateTransaction(const std::vector<CRecipient>& vecSend, CWalletT
if (gArgs.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, false, 0, lp);
CTxMemPool::setEntries setAncestors;
size_t nLimitAncestors = gArgs.GetArg("-limitancestorcount", DEFAULT_ANCESTOR_LIMIT);
size_t nLimitAncestorSize = gArgs.GetArg("-limitancestorsize", DEFAULT_ANCESTOR_SIZE_LIMIT)*1000;
View
@@ -21,7 +21,7 @@ def __init__(self):
super().__init__()
self.num_nodes = 2
self.setup_clean_chain = False
- self.extra_args = [[], ["-acceptnonstdtxn=0"]]
+ self.extra_args = [["-blockprioritysize=0"], ["-blockprioritysize=0", "-acceptnonstdtxn=0"]]
def run_test(self):
self.relayfee = self.nodes[0].getnetworkinfo()["relayfee"]
@@ -241,7 +241,7 @@ def test_nonzero_locks(orig_tx, node, relayfee, use_height_lock):
# Now mine some blocks, but make sure tx2 doesn't get mined.
# Use prioritisetransaction to lower the effective feerate to 0
- self.nodes[0].prioritisetransaction(txid=tx2.hash, fee_delta=int(-self.relayfee*COIN))
+ self.nodes[0].prioritisetransaction(txid=tx2.hash, priority_delta=-1e15, fee_delta=int(-self.relayfee*COIN))
cur_time = int(time.time())
for i in range(10):
self.nodes[0].setmocktime(cur_time + 600)
@@ -254,7 +254,7 @@ def test_nonzero_locks(orig_tx, node, relayfee, use_height_lock):
test_nonzero_locks(tx2, self.nodes[0], self.relayfee, use_height_lock=False)
# Mine tx2, and then try again
- self.nodes[0].prioritisetransaction(txid=tx2.hash, fee_delta=int(self.relayfee*COIN))
+ self.nodes[0].prioritisetransaction(txid=tx2.hash, priority_delta=1e15, fee_delta=int(self.relayfee*COIN))
# Advance the time on the node so that we can test timelocks
self.nodes[0].setmocktime(cur_time+600)
View
@@ -45,8 +45,10 @@ def run_test(self):
assert(sizes[i] > MAX_BLOCK_BASE_SIZE) # Fail => raise utxo_count
# add a fee delta to something in the cheapest bucket and make sure it gets mined
- # also check that a different entry in the cheapest bucket is NOT mined
+ # also check that a different entry in the cheapest bucket is NOT mined (lower
+ # the priority to ensure its not mined due to priority)
self.nodes[0].prioritisetransaction(txid=txids[0][0], fee_delta=int(3*base_fee*COIN))
+ self.nodes[0].prioritisetransaction(txid=txids[0][1], priority_delta=-1e15)
self.nodes[0].generate(1)
@@ -65,7 +67,7 @@ def run_test(self):
# Add a prioritisation before a tx is in the mempool (de-prioritising a
# high-fee transaction so that it's now low fee).
- self.nodes[0].prioritisetransaction(txid=high_fee_tx, fee_delta=-int(2*base_fee*COIN))
+ self.nodes[0].prioritisetransaction(txid=high_fee_tx, priority_delta=-1e15, fee_delta=-int(2*base_fee*COIN))
# Add everything back to mempool
self.nodes[0].invalidateblock(self.nodes[0].getbestblockhash())
@@ -97,9 +99,19 @@ def run_test(self):
inputs = []
outputs = {}
inputs.append({"txid" : utxo["txid"], "vout" : utxo["vout"]})
- outputs[self.nodes[0].getnewaddress()] = utxo["amount"]
+ outputs[self.nodes[0].getnewaddress()] = utxo["amount"] - self.relayfee
raw_tx = self.nodes[0].createrawtransaction(inputs, outputs)
tx_hex = self.nodes[0].signrawtransaction(raw_tx)["hex"]
+ txid = self.nodes[0].sendrawtransaction(tx_hex)
+
+ # A tx that spends an in-mempool tx has 0 priority, so we can use it to
+ # test the effect of using prioritise transaction for mempool acceptance
+ inputs = []
+ inputs.append({"txid": txid, "vout": 0})
+ outputs = {}
+ outputs[self.nodes[0].getnewaddress()] = utxo["amount"] - self.relayfee
+ raw_tx2 = self.nodes[0].createrawtransaction(inputs, outputs)
+ tx_hex = self.nodes[0].signrawtransaction(raw_tx2)["hex"]
tx_id = self.nodes[0].decoderawtransaction(tx_hex)["txid"]
# This will raise an exception due to min relay fee not being met
View
@@ -192,12 +192,13 @@ def setup_network(self):
# NOTE: the CreateNewBlock code starts counting block size at 1,000 bytes,
# (17k is room enough for 110 or so transactions)
self.nodes.append(self.start_node(1, self.options.tmpdir,
- ["-blockmaxsize=17000", "-maxorphantx=1000"]))
+ ["-blockprioritysize=0", "-blockmaxsize=17000",
+ "-maxorphantx=1000"]))
connect_nodes(self.nodes[1], 0)
# Node2 is a stingy miner, that
# produces too small blocks (room for only 55 or so transactions)
- node2args = ["-blockmaxsize=8000", "-maxorphantx=1000"]
+ node2args = ["-blockprioritysize=0", "-blockmaxsize=8000", "-maxorphantx=1000"]
self.nodes.append(self.start_node(2, self.options.tmpdir, node2args))
connect_nodes(self.nodes[0], 2)
View
@@ -4,20 +4,42 @@
# file COPYING or http://www.opensource.org/licenses/mit-license.php.
"""Utilities for manipulating blocks and transactions."""
+from binascii import a2b_hex, b2a_hex
+import io
+import struct
+
from .mininode import *
from .script import CScript, OP_TRUE, OP_CHECKSIG, OP_RETURN
# Create a block (with regtest difficulty)
-def create_block(hashprev, coinbase, nTime=None):
+def create_block(hashprev=None, coinbase=None, nTime=None, tmpl=None, txlist=None):
block = CBlock()
- if nTime is None:
+ if tmpl:
+ block.nVersion = tmpl.get('version', block.nVersion)
+ if not nTime is None:
+ block.nTime = nTime
+ elif tmpl and not tmpl.get('curtime') is None:
+ block.nTime = tmpl['curtime']
+ else:
import time
block.nTime = int(time.time()+600)
+ if not hashprev is None:
+ block.hashPrevBlock = hashprev
else:
- block.nTime = nTime
- block.hashPrevBlock = hashprev
- block.nBits = 0x207fffff # Will break after a difficulty adjustment...
- block.vtx.append(coinbase)
+ block.hashPrevBlock = int(tmpl['previousblockhash'], 0x10)
+ if tmpl and not tmpl.get('bits') is None:
+ block.nBits = struct.unpack('>I', a2b_hex(tmpl['bits']))[0]
+ else:
+ block.nBits = 0x207fffff # Will break after a difficulty adjustment...
+ if not coinbase is None:
+ block.vtx.append(coinbase)
+ if txlist:
+ for tx in txlist:
+ if not hasattr(tx, 'calc_sha256'):
+ txo = CTransaction()
+ txo.deserialize(io.BytesIO(tx))
+ tx = txo
+ block.vtx.append(tx)
block.hashMerkleRoot = block.calc_merkle_root()
block.calc_sha256()
return block
View
@@ -85,6 +85,8 @@
'sweepprivkeys.py',
'txn_doublespend.py --mineblock',
'txn_clone.py',
+ 'txn_priority.py',
+ 'txn_priority.py --gbt',
'getchaintips.py',
'rest.py',
'mempool_spendcoinbase.py',
View
@@ -0,0 +1,199 @@
+#!/usr/bin/env python3
+# Copyright (c) 2016 The Bitcoin Core developers
+# Distributed under the MIT/X11 software license, see the accompanying
+# file COPYING or http://www.opensource.org/licenses/mit-license.php.
+#
+
+from test_framework.blocktools import create_block, create_coinbase
+from test_framework.test_framework import BitcoinTestFramework
+from test_framework.util import *
+
+from binascii import a2b_hex, b2a_hex
+from decimal import Decimal
+
+def find_unspent(node, txid, amount):
+ for utxo in node.listunspent(0):
+ if utxo['txid'] != txid: continue
+ if utxo['amount'] != amount: continue
+ return {'txid': utxo['txid'], 'vout': utxo['vout']}
+
+def solve_template_hex(tmpl, txlist):
+ block = create_block(tmpl=tmpl, txlist=txlist)
+ block.solve()
+ b = block.serialize()
+ x = b2a_hex(b).decode('ascii')
+ return x
+
+def get_modified_size(node, txdata):
+ decoded = node.decoderawtransaction(txdata)
+ size = len(txdata) // 2
+ for inp in decoded['vin']:
+ offset = 41 + min(len(inp['scriptSig']['hex']) // 2, 110)
+ if offset <= size:
+ size -= offset
+ return size
+
+def assert_approximate(a, b):
+ assert_equal(int(a), int(b))
+
+BTC = Decimal('100000000')
+
+class PriorityTest(BitcoinTestFramework):
+
+ def __init__(self):
+ super().__init__()
+ self.num_nodes = 4
+ self.testmsg_num = 0
+
+ def add_options(self, parser):
+ parser.add_option("--gbt", dest="test_gbt", default=False, action="store_true",
+ help="Test priorities used by GBT")
+
+ def setup_nodes(self):
+ ppopt = []
+ if self.options.test_gbt:
+ ppopt.append('-printpriority')
+
+ nodes = []
+ nodes.append(self.start_node(0, self.options.tmpdir, ['-blockmaxsize=0']))
+ nodes.append(self.start_node(1, self.options.tmpdir, ['-blockprioritysize=1000000', '-blockmaxsize=1000000'] + ppopt))
+ nodes.append(self.start_node(2, self.options.tmpdir, ['-blockmaxsize=0']))
+ nodes.append(self.start_node(3, self.options.tmpdir, ['-blockmaxsize=0']))
+ self.nodes = nodes
+
+ def assert_prio(self, txid, starting, current):
+ node = self.nodes[1]
+
+ if self.options.test_gbt:
+ tmpl = node.getblocktemplate({})
+ tmplentry = None
+ for tx in tmpl['transactions']:
+ if tx['hash'] == txid:
+ tmplentry = tx
+ break
+ # GBT does not expose starting priority, so we don't check that
+ assert_approximate(tmplentry['priority'], current)
+ else:
+ mempoolentry = node.getrawmempool(True)[txid]
+ assert_approximate(mempoolentry['startingpriority'], starting)
+ assert_approximate(mempoolentry['currentpriority'], current)
+
+ def testmsg(self, msg):
+ self.testmsg_num += 1
+ print('Test %d: %s' % (self.testmsg_num, msg))
+
+ def run_test(self):
+ node = self.nodes[0]
+ miner = self.nodes[1]
+
+ node.generate(50)
+ self.sync_all()
+ miner.generate(101)
+ self.sync_all()
+
+ fee = Decimal('0.01')
+ amt = Decimal('11')
+
+ txid_a = node.sendtoaddress(node.getnewaddress(), amt)
+ txdata_b = node.createrawtransaction([find_unspent(node, txid_a, amt)], {node.getnewaddress(): amt - fee})
+ txdata_b = node.signrawtransaction(txdata_b)['hex']
+ txmodsize_b = get_modified_size(node, txdata_b)
+ txid_b = node.sendrawtransaction(txdata_b)
+ self.sync_all()
+
+ self.testmsg('priority starts at 0 with all unconfirmed inputs')
+ self.assert_prio(txid_b, 0, 0)
+
+ self.testmsg('priority increases correctly when that input is mined')
+
+ # Mine only the sendtoaddress transaction
+ tmpl = node.getblocktemplate()
+ rawblock = solve_template_hex(tmpl, [create_coinbase(tmpl['height']), a2b_hex(node.getrawtransaction(txid_a))])
+ assert_equal(node.submitblock(rawblock), None)
+ self.sync_all()
+
+ self.assert_prio(txid_b, 0, amt * BTC / txmodsize_b)
+
+ self.testmsg('priority continues to increase the deeper the block confirming its inputs gets buried')
+
+ node.generate(2)
+ self.sync_all()
+
+ self.assert_prio(txid_b, 0, amt * BTC * 3 / txmodsize_b)
+
+ self.testmsg('with a confirmed input, the initial priority is calculated correctly')
+
+ miner.generate(4)
+ self.sync_all()
+
+ amt_c = (amt - fee) / 2
+ amt_c2 = amt_c - (fee * 2) # could be just amt_c-fee, but then it'd be undiscernable later on
+ txdata_c = node.createrawtransaction([find_unspent(node, txid_b, amt - fee)], {node.getnewaddress(): amt_c, node.getnewaddress(): amt_c2})
+ txdata_c = node.signrawtransaction(txdata_c)['hex']
+ txmodsize_c = get_modified_size(node, txdata_c)
+ txid_c = node.sendrawtransaction(txdata_c)
+ self.sync_all()
+
+ txid_c_starting_prio = (amt - fee) * BTC * 4 / txmodsize_c
+ self.assert_prio(txid_c, txid_c_starting_prio, txid_c_starting_prio)
+
+ self.testmsg('with an input confirmed prior to the transaction, the priority gets incremented correctly as it gets buried deeper')
+
+ node.generate(1)
+ self.sync_all()
+
+ self.assert_prio(txid_c, txid_c_starting_prio, (amt - fee) * BTC * 5 / txmodsize_c)
+
+ self.testmsg('with an input confirmed prior to the transaction, the priority gets incremented correctly as it gets buried deeper and deeper')
+
+ node.generate(2)
+ self.sync_all()
+
+ self.assert_prio(txid_c, txid_c_starting_prio, (amt - fee) * BTC * 7 / txmodsize_c)
+
+ print('(preparing for reorg test)')
+
+ miner.generate(1)
+ self.sync_all()
+
+ self.split_network()
+ sync_groups = [self.nodes[:2], self.nodes[2:]]
+ node = self.nodes[0]
+ miner = self.nodes[1]
+ competing_miner = self.nodes[2]
+
+ txdata_d = node.createrawtransaction([find_unspent(node, txid_c, amt_c)], {node.getnewaddress(): amt_c - fee})
+ txdata_d = node.signrawtransaction(txdata_d)['hex']
+ txmodsize_d = get_modified_size(node, txdata_d)
+ txid_d = node.sendrawtransaction(txdata_d)
+ self.sync_all(sync_groups)
+
+ miner.generate(1)
+ self.sync_all(sync_groups)
+
+ txdata_e = node.createrawtransaction([find_unspent(node, txid_d, amt_c - fee), find_unspent(node, txid_c, amt_c2)], {node.getnewaddress(): (amt_c - fee) + amt_c2 - fee})
+ txdata_e = node.signrawtransaction(txdata_e)['hex']
+ txmodsize_e = get_modified_size(node, txdata_e)
+ txid_e = node.sendrawtransaction(txdata_e)
+ self.sync_all(sync_groups)
+
+ txid_e_starting_prio = (((amt_c - fee) * BTC) + (amt_c2 * BTC * 2)) / txmodsize_e
+ self.assert_prio(txid_e, txid_e_starting_prio, txid_e_starting_prio) # Sanity check 1
+
+ competing_miner.generate(5)
+ self.sync_all(sync_groups)
+
+ self.assert_prio(txid_e, txid_e_starting_prio, txid_e_starting_prio) # Sanity check 2
+
+ self.testmsg('priority is updated correctly when input-confirming block is reorganised out')
+
+ # NOTE: We cannot use join_network because it restarts the nodes (thus losing the mempool)
+ connect_nodes_bi(self.nodes, 1, 2)
+ self.is_network_split = False
+ sync_blocks(self.nodes)
+
+ txid_e_reorg_prio = (amt_c2 * BTC * 6) / txmodsize_e
+ self.assert_prio(txid_e, txid_e_starting_prio, txid_e_reorg_prio)
+
+if __name__ == '__main__':
+ PriorityTest().main()