Browse Source

Merge pull request #6898

553cad9 Rewrite CreateNewBlock (Alex Morcos)
5f12263 Expose FormatStateMessage (Alex Morcos)
1f09287 Make accessing mempool parents and children public (Alex Morcos)
7230187 Add TxPriority class and comparator (Alex Morcos)
f3fe836 Add a score index to the mempool. (Alex Morcos)
c49d5bc Store the total sig op count of a tx. (Alex Morcos)
0.13
Pieter Wuille 9 years ago
parent
commit
4077ad20d0
No known key found for this signature in database
GPG Key ID: DBA1A67379A1A931
  1. 4
      src/main.cpp
  2. 2
      src/main.h
  3. 301
      src/miner.cpp
  4. 2
      src/rpcblockchain.cpp
  5. 52
      src/test/mempool_tests.cpp
  6. 60
      src/test/miner_tests.cpp
  7. 2
      src/test/test_bitcoin.cpp
  8. 4
      src/test/test_bitcoin.h
  9. 28
      src/txmempool.cpp
  10. 62
      src/txmempool.h

4
src/main.cpp

@ -816,7 +816,7 @@ CAmount GetMinRelayFee(const CTransaction& tx, const CTxMemPool& pool, unsigned
} }
/** Convert CValidationState to a human-readable message for logging */ /** Convert CValidationState to a human-readable message for logging */
static std::string FormatStateMessage(const CValidationState &state) std::string FormatStateMessage(const CValidationState &state)
{ {
return strprintf("%s%s (code %i)", return strprintf("%s%s (code %i)",
state.GetRejectReason(), state.GetRejectReason(),
@ -964,7 +964,7 @@ bool AcceptToMemoryPool(CTxMemPool& pool, CValidationState &state, const CTransa
} }
} }
CTxMemPoolEntry entry(tx, nFees, GetTime(), dPriority, chainActive.Height(), pool.HasNoInputsOf(tx), inChainInputValue, fSpendsCoinbase); CTxMemPoolEntry entry(tx, nFees, GetTime(), dPriority, chainActive.Height(), pool.HasNoInputsOf(tx), inChainInputValue, fSpendsCoinbase, nSigOps);
unsigned int nSize = entry.GetTxSize(); unsigned int nSize = entry.GetTxSize();
// Don't accept it if it can't get into a block // Don't accept it if it can't get into a block

2
src/main.h

@ -257,6 +257,8 @@ void PruneAndFlush();
bool AcceptToMemoryPool(CTxMemPool& pool, CValidationState &state, const CTransaction &tx, bool fLimitFree, bool AcceptToMemoryPool(CTxMemPool& pool, CValidationState &state, const CTransaction &tx, bool fLimitFree,
bool* pfMissingInputs, bool fOverrideMempoolLimit=false, bool fRejectAbsurdFee=false); bool* pfMissingInputs, bool fOverrideMempoolLimit=false, bool fRejectAbsurdFee=false);
/** Convert CValidationState to a human-readable message for logging */
std::string FormatStateMessage(const CValidationState &state);
struct CNodeStateStats { struct CNodeStateStats {
int nMisbehavior; int nMisbehavior;

301
src/miner.cpp

@ -27,6 +27,7 @@
#include <boost/thread.hpp> #include <boost/thread.hpp>
#include <boost/tuple/tuple.hpp> #include <boost/tuple/tuple.hpp>
#include <queue>
using namespace std; using namespace std;
@ -40,48 +41,18 @@ using namespace std;
// transactions in the memory pool. When we select transactions from the // transactions in the memory pool. When we select transactions from the
// pool, we select by highest priority or fee rate, so we might consider // pool, we select by highest priority or fee rate, so we might consider
// transactions that depend on transactions that aren't yet in the block. // transactions that depend on transactions that aren't yet in the block.
// The COrphan class keeps track of these 'temporary orphans' while
// CreateBlock is figuring out which transactions to include.
//
class COrphan
{
public:
const CTransaction* ptx;
set<uint256> setDependsOn;
CFeeRate feeRate;
double dPriority;
COrphan(const CTransaction* ptxIn) : ptx(ptxIn), feeRate(0), dPriority(0)
{
}
};
uint64_t nLastBlockTx = 0; uint64_t nLastBlockTx = 0;
uint64_t nLastBlockSize = 0; uint64_t nLastBlockSize = 0;
// We want to sort transactions by priority and fee rate, so: class ScoreCompare
typedef boost::tuple<double, CFeeRate, const CTransaction*> TxPriority;
class TxPriorityCompare
{ {
bool byFee;
public: public:
TxPriorityCompare(bool _byFee) : byFee(_byFee) { } ScoreCompare() {}
bool operator()(const TxPriority& a, const TxPriority& b) bool operator()(const CTxMemPool::txiter a, const CTxMemPool::txiter b)
{ {
if (byFee) return CompareTxMemPoolEntryByScore()(*b,*a); // Convert to less than
{
if (a.get<1>() == b.get<1>())
return a.get<0>() < b.get<0>();
return a.get<1>() < b.get<1>();
}
else
{
if (a.get<0>() == b.get<0>())
return a.get<1>() < b.get<1>();
return a.get<0>() < b.get<0>();
}
} }
}; };
@ -141,6 +112,22 @@ CBlockTemplate* CreateNewBlock(const CChainParams& chainparams, const CScript& s
nBlockMinSize = std::min(nBlockMaxSize, nBlockMinSize); nBlockMinSize = std::min(nBlockMaxSize, nBlockMinSize);
// Collect memory pool transactions into the block // Collect memory pool transactions into the block
CTxMemPool::setEntries inBlock;
CTxMemPool::setEntries waitSet;
// This vector will be sorted into a priority queue:
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;
std::priority_queue<CTxMemPool::txiter, std::vector<CTxMemPool::txiter>, ScoreCompare> clearedTxs;
bool fPrintPriority = GetBoolArg("-printpriority", DEFAULT_PRINTPRIORITY);
uint64_t nBlockSize = 1000;
uint64_t nBlockTx = 0;
unsigned int nBlockSigOps = 100;
int lastFewTxs = 0;
CAmount nFees = 0; CAmount nFees = 0;
{ {
@ -149,157 +136,102 @@ CBlockTemplate* CreateNewBlock(const CChainParams& chainparams, const CScript& s
const int nHeight = pindexPrev->nHeight + 1; const int nHeight = pindexPrev->nHeight + 1;
pblock->nTime = GetAdjustedTime(); pblock->nTime = GetAdjustedTime();
const int64_t nMedianTimePast = pindexPrev->GetMedianTimePast(); const int64_t nMedianTimePast = pindexPrev->GetMedianTimePast();
CCoinsViewCache view(pcoinsTip);
// Priority order to process transactions
list<COrphan> vOrphan; // list memory doesn't move
map<uint256, vector<COrphan*> > mapDependers;
bool fPrintPriority = GetBoolArg("-printpriority", DEFAULT_PRINTPRIORITY);
// This vector will be sorted into a priority queue:
vector<TxPriority> vecPriority;
vecPriority.reserve(mempool.mapTx.size());
for (CTxMemPool::indexed_transaction_set::iterator mi = mempool.mapTx.begin();
mi != mempool.mapTx.end(); ++mi)
{
const CTransaction& tx = mi->GetTx();
int64_t nLockTimeCutoff = (STANDARD_LOCKTIME_VERIFY_FLAGS & LOCKTIME_MEDIAN_TIME_PAST)
? nMedianTimePast
: pblock->GetBlockTime();
if (tx.IsCoinBase() || !IsFinalTx(tx, nHeight, nLockTimeCutoff))
continue;
COrphan* porphan = NULL;
double dPriority = 0;
CAmount nTotalIn = 0;
bool fMissingInputs = false;
BOOST_FOREACH(const CTxIn& txin, tx.vin)
{
// Read prev transaction
if (!view.HaveCoins(txin.prevout.hash))
{
// This should never happen; all transactions in the memory
// pool should connect to either transactions in the chain
// or other transactions in the memory pool.
if (!mempool.mapTx.count(txin.prevout.hash))
{
LogPrintf("ERROR: mempool transaction missing input\n");
if (fDebug) assert("mempool transaction missing input" == 0);
fMissingInputs = true;
if (porphan)
vOrphan.pop_back();
break;
}
// Has to wait for dependencies
if (!porphan)
{
// Use list for automatic deletion
vOrphan.push_back(COrphan(&tx));
porphan = &vOrphan.back();
}
mapDependers[txin.prevout.hash].push_back(porphan);
porphan->setDependsOn.insert(txin.prevout.hash);
nTotalIn += mempool.mapTx.find(txin.prevout.hash)->GetTx().vout[txin.prevout.n].nValue;
continue;
}
const CCoins* coins = view.AccessCoins(txin.prevout.hash);
assert(coins);
CAmount nValueIn = coins->vout[txin.prevout.n].nValue;
nTotalIn += nValueIn;
int nConf = nHeight - coins->nHeight;
dPriority += (double)nValueIn * nConf;
}
if (fMissingInputs) continue;
// Priority is sum(valuein * age) / modified_txsize
unsigned int nTxSize = ::GetSerializeSize(tx, SER_NETWORK, PROTOCOL_VERSION);
dPriority = tx.ComputePriority(dPriority, nTxSize);
uint256 hash = tx.GetHash();
mempool.ApplyDeltas(hash, dPriority, nTotalIn);
CFeeRate feeRate(nTotalIn-tx.GetValueOut(), nTxSize); int64_t nLockTimeCutoff = (STANDARD_LOCKTIME_VERIFY_FLAGS & LOCKTIME_MEDIAN_TIME_PAST)
? nMedianTimePast
: pblock->GetBlockTime();
if (porphan) bool fPriorityBlock = nBlockPrioritySize > 0;
if (fPriorityBlock) {
vecPriority.reserve(mempool.mapTx.size());
for (CTxMemPool::indexed_transaction_set::iterator mi = mempool.mapTx.begin();
mi != mempool.mapTx.end(); ++mi)
{ {
porphan->dPriority = dPriority; double dPriority = mi->GetPriority(nHeight);
porphan->feeRate = feeRate; CAmount dummy;
mempool.ApplyDeltas(mi->GetTx().GetHash(), dPriority, dummy);
vecPriority.push_back(TxCoinAgePriority(dPriority, mi));
} }
else std::make_heap(vecPriority.begin(), vecPriority.end(), pricomparer);
vecPriority.push_back(TxPriority(dPriority, feeRate, &(mi->GetTx())));
} }
// Collect transactions into block CTxMemPool::indexed_transaction_set::nth_index<3>::type::iterator mi = mempool.mapTx.get<3>().begin();
uint64_t nBlockSize = 1000; CTxMemPool::txiter iter;
uint64_t nBlockTx = 0;
int nBlockSigOps = 100;
bool fSortedByFee = (nBlockPrioritySize <= 0);
TxPriorityCompare comparer(fSortedByFee); while (mi != mempool.mapTx.get<3>().end() || !clearedTxs.empty())
std::make_heap(vecPriority.begin(), vecPriority.end(), comparer);
while (!vecPriority.empty())
{ {
// Take highest priority transaction off the priority queue: bool priorityTx = false;
double dPriority = vecPriority.front().get<0>(); if (fPriorityBlock && !vecPriority.empty()) { // add a tx from priority queue to fill the blockprioritysize
CFeeRate feeRate = vecPriority.front().get<1>(); priorityTx = true;
const CTransaction& tx = *(vecPriority.front().get<2>()); iter = vecPriority.front().second;
actualPriority = vecPriority.front().first;
std::pop_heap(vecPriority.begin(), vecPriority.end(), comparer); std::pop_heap(vecPriority.begin(), vecPriority.end(), pricomparer);
vecPriority.pop_back(); vecPriority.pop_back();
}
// Size limits else if (clearedTxs.empty()) { // add tx with next highest score
unsigned int nTxSize = ::GetSerializeSize(tx, SER_NETWORK, PROTOCOL_VERSION); iter = mempool.mapTx.project<0>(mi);
if (nBlockSize + nTxSize >= nBlockMaxSize) mi++;
continue; }
else { // try to add a previously postponed child tx
iter = clearedTxs.top();
clearedTxs.pop();
}
// Legacy limits on sigOps: if (inBlock.count(iter))
unsigned int nTxSigOps = GetLegacySigOpCount(tx); continue; // could have been added to the priorityBlock
if (nBlockSigOps + nTxSigOps >= MAX_BLOCK_SIGOPS)
continue;
// Skip free transactions if we're past the minimum block size: const CTransaction& tx = iter->GetTx();
const uint256& hash = tx.GetHash();
double dPriorityDelta = 0;
CAmount nFeeDelta = 0;
mempool.ApplyDeltas(hash, dPriorityDelta, nFeeDelta);
if (fSortedByFee && (dPriorityDelta <= 0) && (nFeeDelta <= 0) && (feeRate < ::minRelayTxFee) && (nBlockSize + nTxSize >= nBlockMinSize))
continue;
// Prioritise by fee once past the priority size or we run out of high-priority bool fOrphan = false;
// transactions: BOOST_FOREACH(CTxMemPool::txiter parent, mempool.GetMemPoolParents(iter))
if (!fSortedByFee &&
((nBlockSize + nTxSize >= nBlockPrioritySize) || !AllowFree(dPriority)))
{ {
fSortedByFee = true; if (!inBlock.count(parent)) {
comparer = TxPriorityCompare(fSortedByFee); fOrphan = true;
std::make_heap(vecPriority.begin(), vecPriority.end(), comparer); break;
}
} }
if (fOrphan) {
if (!view.HaveInputs(tx)) if (priorityTx)
waitPriMap.insert(std::make_pair(iter,actualPriority));
else
waitSet.insert(iter);
continue; continue;
}
CAmount nTxFees = view.GetValueIn(tx)-tx.GetValueOut(); unsigned int nTxSize = iter->GetTxSize();
if (fPriorityBlock &&
nTxSigOps += GetP2SHSigOpCount(tx, view); (nBlockSize + nTxSize >= nBlockPrioritySize || !AllowFree(actualPriority))) {
if (nBlockSigOps + nTxSigOps >= MAX_BLOCK_SIGOPS) fPriorityBlock = false;
waitPriMap.clear();
}
if (!priorityTx &&
(iter->GetModifiedFee() < ::minRelayTxFee.GetFee(nTxSize) && nBlockSize >= nBlockMinSize)) {
break;
}
if (nBlockSize + nTxSize >= nBlockMaxSize) {
if (nBlockSize > nBlockMaxSize - 100 || lastFewTxs > 50) {
break;
}
// Once we're within 1000 bytes of a full block, only look at 50 more txs
// to try to fill the remaining space.
if (nBlockSize > nBlockMaxSize - 1000) {
lastFewTxs++;
}
continue; continue;
}
// Note that flags: we don't want to set mempool/IsStandard() if (!IsFinalTx(tx, nHeight, nLockTimeCutoff))
// policy here, but we still have to ensure that the block we
// create only contains transactions that are valid in new blocks.
CValidationState state;
if (!CheckInputs(tx, state, view, true, MANDATORY_SCRIPT_VERIFY_FLAGS, true))
continue; continue;
UpdateCoins(tx, state, view, nHeight); unsigned int nTxSigOps = iter->GetSigOpCount();
if (nBlockSigOps + nTxSigOps >= MAX_BLOCK_SIGOPS) {
if (nBlockSigOps > MAX_BLOCK_SIGOPS - 2) {
break;
}
continue;
}
CAmount nTxFees = iter->GetFee();
// Added // Added
pblock->vtx.push_back(tx); pblock->vtx.push_back(tx);
pblocktemplate->vTxFees.push_back(nTxFees); pblocktemplate->vTxFees.push_back(nTxFees);
@ -311,31 +243,37 @@ CBlockTemplate* CreateNewBlock(const CChainParams& chainparams, const CScript& s
if (fPrintPriority) if (fPrintPriority)
{ {
double dPriority = iter->GetPriority(nHeight);
CAmount dummy;
mempool.ApplyDeltas(tx.GetHash(), dPriority, dummy);
LogPrintf("priority %.1f fee %s txid %s\n", LogPrintf("priority %.1f fee %s txid %s\n",
dPriority, feeRate.ToString(), tx.GetHash().ToString()); dPriority , CFeeRate(iter->GetModifiedFee(), nTxSize).ToString(), tx.GetHash().ToString());
} }
inBlock.insert(iter);
// Add transactions that depend on this one to the priority queue // Add transactions that depend on this one to the priority queue
if (mapDependers.count(hash)) BOOST_FOREACH(CTxMemPool::txiter child, mempool.GetMemPoolChildren(iter))
{ {
BOOST_FOREACH(COrphan* porphan, mapDependers[hash]) if (fPriorityBlock) {
{ waitPriIter wpiter = waitPriMap.find(child);
if (!porphan->setDependsOn.empty()) if (wpiter != waitPriMap.end()) {
{ vecPriority.push_back(TxCoinAgePriority(wpiter->second,child));
porphan->setDependsOn.erase(hash); std::push_heap(vecPriority.begin(), vecPriority.end(), pricomparer);
if (porphan->setDependsOn.empty()) waitPriMap.erase(wpiter);
{ }
vecPriority.push_back(TxPriority(porphan->dPriority, porphan->feeRate, porphan->ptx)); }
std::push_heap(vecPriority.begin(), vecPriority.end(), comparer); else {
} if (waitSet.count(child)) {
clearedTxs.push(child);
waitSet.erase(child);
} }
} }
} }
} }
nLastBlockTx = nBlockTx; nLastBlockTx = nBlockTx;
nLastBlockSize = nBlockSize; nLastBlockSize = nBlockSize;
LogPrintf("CreateNewBlock(): total size %u\n", nBlockSize); LogPrintf("CreateNewBlock(): total size %u txs: %u fees: %ld sigops %d\n", nBlockSize, nBlockTx, nFees, nBlockSigOps);
// Compute final coinbase transaction. // Compute final coinbase transaction.
txNew.vout[0].nValue = nFees + GetBlockSubsidy(nHeight, chainparams.GetConsensus()); txNew.vout[0].nValue = nFees + GetBlockSubsidy(nHeight, chainparams.GetConsensus());
@ -351,8 +289,9 @@ CBlockTemplate* CreateNewBlock(const CChainParams& chainparams, const CScript& s
pblocktemplate->vTxSigOps[0] = GetLegacySigOpCount(pblock->vtx[0]); pblocktemplate->vTxSigOps[0] = GetLegacySigOpCount(pblock->vtx[0]);
CValidationState state; CValidationState state;
if (!TestBlockValidity(state, chainparams, *pblock, pindexPrev, false, false)) if (!TestBlockValidity(state, chainparams, *pblock, pindexPrev, false, false)) {
throw std::runtime_error("CreateNewBlock(): TestBlockValidity failed"); throw std::runtime_error(strprintf("%s: TestBlockValidity failed: %s", __func__, FormatStateMessage(state)));
}
} }
return pblocktemplate.release(); return pblocktemplate.release();

2
src/rpcblockchain.cpp

@ -190,6 +190,7 @@ UniValue mempoolToJSON(bool fVerbose = false)
UniValue info(UniValue::VOBJ); UniValue info(UniValue::VOBJ);
info.push_back(Pair("size", (int)e.GetTxSize())); info.push_back(Pair("size", (int)e.GetTxSize()));
info.push_back(Pair("fee", ValueFromAmount(e.GetFee()))); info.push_back(Pair("fee", ValueFromAmount(e.GetFee())));
info.push_back(Pair("modifiedfee", ValueFromAmount(e.GetModifiedFee())));
info.push_back(Pair("time", e.GetTime())); info.push_back(Pair("time", e.GetTime()));
info.push_back(Pair("height", (int)e.GetHeight())); info.push_back(Pair("height", (int)e.GetHeight()));
info.push_back(Pair("startingpriority", e.GetPriority(e.GetHeight()))); info.push_back(Pair("startingpriority", e.GetPriority(e.GetHeight())));
@ -247,6 +248,7 @@ UniValue getrawmempool(const UniValue& params, bool fHelp)
" \"transactionid\" : { (json object)\n" " \"transactionid\" : { (json object)\n"
" \"size\" : n, (numeric) transaction size in bytes\n" " \"size\" : n, (numeric) transaction size in bytes\n"
" \"fee\" : n, (numeric) transaction fee in " + CURRENCY_UNIT + "\n" " \"fee\" : n, (numeric) transaction fee in " + CURRENCY_UNIT + "\n"
" \"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" " \"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" " \"height\" : n, (numeric) block height when transaction entered pool\n"
" \"startingpriority\" : n, (numeric) priority when transaction entered pool\n" " \"startingpriority\" : n, (numeric) priority when transaction entered pool\n"

52
src/test/mempool_tests.cpp

@ -102,12 +102,13 @@ BOOST_AUTO_TEST_CASE(MempoolRemoveTest)
removed.clear(); removed.clear();
} }
template<int index>
void CheckSort(CTxMemPool &pool, std::vector<std::string> &sortedOrder) void CheckSort(CTxMemPool &pool, std::vector<std::string> &sortedOrder)
{ {
BOOST_CHECK_EQUAL(pool.size(), sortedOrder.size()); BOOST_CHECK_EQUAL(pool.size(), sortedOrder.size());
CTxMemPool::indexed_transaction_set::nth_index<1>::type::iterator it = pool.mapTx.get<1>().begin(); typename CTxMemPool::indexed_transaction_set::nth_index<index>::type::iterator it = pool.mapTx.get<index>().begin();
int count=0; int count=0;
for (; it != pool.mapTx.get<1>().end(); ++it, ++count) { for (; it != pool.mapTx.get<index>().end(); ++it, ++count) {
BOOST_CHECK_EQUAL(it->GetTx().GetHash().ToString(), sortedOrder[count]); BOOST_CHECK_EQUAL(it->GetTx().GetHash().ToString(), sortedOrder[count]);
} }
} }
@ -163,7 +164,7 @@ BOOST_AUTO_TEST_CASE(MempoolIndexingTest)
sortedOrder[2] = tx1.GetHash().ToString(); // 10000 sortedOrder[2] = tx1.GetHash().ToString(); // 10000
sortedOrder[3] = tx4.GetHash().ToString(); // 15000 sortedOrder[3] = tx4.GetHash().ToString(); // 15000
sortedOrder[4] = tx2.GetHash().ToString(); // 20000 sortedOrder[4] = tx2.GetHash().ToString(); // 20000
CheckSort(pool, sortedOrder); CheckSort<1>(pool, sortedOrder);
/* low fee but with high fee child */ /* low fee but with high fee child */
/* tx6 -> tx7 -> tx8, tx9 -> tx10 */ /* tx6 -> tx7 -> tx8, tx9 -> tx10 */
@ -175,7 +176,7 @@ BOOST_AUTO_TEST_CASE(MempoolIndexingTest)
BOOST_CHECK_EQUAL(pool.size(), 6); BOOST_CHECK_EQUAL(pool.size(), 6);
// Check that at this point, tx6 is sorted low // Check that at this point, tx6 is sorted low
sortedOrder.insert(sortedOrder.begin(), tx6.GetHash().ToString()); sortedOrder.insert(sortedOrder.begin(), tx6.GetHash().ToString());
CheckSort(pool, sortedOrder); CheckSort<1>(pool, sortedOrder);
CTxMemPool::setEntries setAncestors; CTxMemPool::setEntries setAncestors;
setAncestors.insert(pool.mapTx.find(tx6.GetHash())); setAncestors.insert(pool.mapTx.find(tx6.GetHash()));
@ -201,7 +202,7 @@ BOOST_AUTO_TEST_CASE(MempoolIndexingTest)
sortedOrder.erase(sortedOrder.begin()); sortedOrder.erase(sortedOrder.begin());
sortedOrder.push_back(tx6.GetHash().ToString()); sortedOrder.push_back(tx6.GetHash().ToString());
sortedOrder.push_back(tx7.GetHash().ToString()); sortedOrder.push_back(tx7.GetHash().ToString());
CheckSort(pool, sortedOrder); CheckSort<1>(pool, sortedOrder);
/* low fee child of tx7 */ /* low fee child of tx7 */
CMutableTransaction tx8 = CMutableTransaction(); CMutableTransaction tx8 = CMutableTransaction();
@ -216,7 +217,7 @@ BOOST_AUTO_TEST_CASE(MempoolIndexingTest)
// Now tx8 should be sorted low, but tx6/tx both high // Now tx8 should be sorted low, but tx6/tx both high
sortedOrder.insert(sortedOrder.begin(), tx8.GetHash().ToString()); sortedOrder.insert(sortedOrder.begin(), tx8.GetHash().ToString());
CheckSort(pool, sortedOrder); CheckSort<1>(pool, sortedOrder);
/* low fee child of tx7 */ /* low fee child of tx7 */
CMutableTransaction tx9 = CMutableTransaction(); CMutableTransaction tx9 = CMutableTransaction();
@ -231,7 +232,7 @@ BOOST_AUTO_TEST_CASE(MempoolIndexingTest)
// tx9 should be sorted low // tx9 should be sorted low
BOOST_CHECK_EQUAL(pool.size(), 9); BOOST_CHECK_EQUAL(pool.size(), 9);
sortedOrder.insert(sortedOrder.begin(), tx9.GetHash().ToString()); sortedOrder.insert(sortedOrder.begin(), tx9.GetHash().ToString());
CheckSort(pool, sortedOrder); CheckSort<1>(pool, sortedOrder);
std::vector<std::string> snapshotOrder = sortedOrder; std::vector<std::string> snapshotOrder = sortedOrder;
@ -273,7 +274,7 @@ BOOST_AUTO_TEST_CASE(MempoolIndexingTest)
sortedOrder.insert(sortedOrder.begin()+5, tx9.GetHash().ToString()); sortedOrder.insert(sortedOrder.begin()+5, tx9.GetHash().ToString());
sortedOrder.insert(sortedOrder.begin()+6, tx8.GetHash().ToString()); sortedOrder.insert(sortedOrder.begin()+6, tx8.GetHash().ToString());
sortedOrder.insert(sortedOrder.begin()+7, tx10.GetHash().ToString()); // tx10 is just before tx6 sortedOrder.insert(sortedOrder.begin()+7, tx10.GetHash().ToString()); // tx10 is just before tx6
CheckSort(pool, sortedOrder); CheckSort<1>(pool, sortedOrder);
// there should be 10 transactions in the mempool // there should be 10 transactions in the mempool
BOOST_CHECK_EQUAL(pool.size(), 10); BOOST_CHECK_EQUAL(pool.size(), 10);
@ -281,9 +282,42 @@ BOOST_AUTO_TEST_CASE(MempoolIndexingTest)
// Now try removing tx10 and verify the sort order returns to normal // Now try removing tx10 and verify the sort order returns to normal
std::list<CTransaction> removed; std::list<CTransaction> removed;
pool.remove(pool.mapTx.find(tx10.GetHash())->GetTx(), removed, true); pool.remove(pool.mapTx.find(tx10.GetHash())->GetTx(), removed, true);
CheckSort(pool, snapshotOrder); CheckSort<1>(pool, snapshotOrder);
pool.remove(pool.mapTx.find(tx9.GetHash())->GetTx(), removed, true);
pool.remove(pool.mapTx.find(tx8.GetHash())->GetTx(), removed, true);
/* Now check the sort on the mining score index.
* Final order should be:
*
* tx7 (2M)
* tx2 (20k)
* tx4 (15000)
* tx1/tx5 (10000)
* tx3/6 (0)
* (Ties resolved by hash)
*/
sortedOrder.clear();
sortedOrder.push_back(tx7.GetHash().ToString());
sortedOrder.push_back(tx2.GetHash().ToString());
sortedOrder.push_back(tx4.GetHash().ToString());
if (tx1.GetHash() < tx5.GetHash()) {
sortedOrder.push_back(tx5.GetHash().ToString());
sortedOrder.push_back(tx1.GetHash().ToString());
} else {
sortedOrder.push_back(tx1.GetHash().ToString());
sortedOrder.push_back(tx5.GetHash().ToString());
}
if (tx3.GetHash() < tx6.GetHash()) {
sortedOrder.push_back(tx6.GetHash().ToString());
sortedOrder.push_back(tx3.GetHash().ToString());
} else {
sortedOrder.push_back(tx3.GetHash().ToString());
sortedOrder.push_back(tx6.GetHash().ToString());
}
CheckSort<3>(pool, sortedOrder);
} }
BOOST_AUTO_TEST_CASE(MempoolSizeLimitTest) BOOST_AUTO_TEST_CASE(MempoolSizeLimitTest)
{ {
CTxMemPool pool(CFeeRate(1000)); CTxMemPool pool(CFeeRate(1000));

60
src/test/miner_tests.cpp

@ -120,7 +120,22 @@ BOOST_AUTO_TEST_CASE(CreateNewBlock_validity)
tx.vout[0].nValue -= 1000000; tx.vout[0].nValue -= 1000000;
hash = tx.GetHash(); hash = tx.GetHash();
bool spendsCoinbase = (i == 0) ? true : false; // only first tx spends coinbase bool spendsCoinbase = (i == 0) ? true : false; // only first tx spends coinbase
mempool.addUnchecked(hash, entry.Time(GetTime()).SpendsCoinbase(spendsCoinbase).FromTx(tx)); // If we don't set the # of sig ops in the CTxMemPoolEntry, template creation fails
mempool.addUnchecked(hash, entry.Fee(1000000).Time(GetTime()).SpendsCoinbase(spendsCoinbase).FromTx(tx));
tx.vin[0].prevout.hash = hash;
}
BOOST_CHECK_THROW(CreateNewBlock(chainparams, scriptPubKey), std::runtime_error);
mempool.clear();
tx.vin[0].prevout.hash = txFirst[0]->GetHash();
tx.vout[0].nValue = 5000000000LL;
for (unsigned int i = 0; i < 1001; ++i)
{
tx.vout[0].nValue -= 1000000;
hash = tx.GetHash();
bool spendsCoinbase = (i == 0) ? true : false; // only first tx spends coinbase
// If we do set the # of sig ops in the CTxMemPoolEntry, template creation passes
mempool.addUnchecked(hash, entry.Fee(1000000).Time(GetTime()).SpendsCoinbase(spendsCoinbase).SigOps(20).FromTx(tx));
tx.vin[0].prevout.hash = hash; tx.vin[0].prevout.hash = hash;
} }
BOOST_CHECK(pblocktemplate = CreateNewBlock(chainparams, scriptPubKey)); BOOST_CHECK(pblocktemplate = CreateNewBlock(chainparams, scriptPubKey));
@ -141,18 +156,17 @@ BOOST_AUTO_TEST_CASE(CreateNewBlock_validity)
tx.vout[0].nValue -= 10000000; tx.vout[0].nValue -= 10000000;
hash = tx.GetHash(); hash = tx.GetHash();
bool spendsCoinbase = (i == 0) ? true : false; // only first tx spends coinbase bool spendsCoinbase = (i == 0) ? true : false; // only first tx spends coinbase
mempool.addUnchecked(hash, entry.Time(GetTime()).SpendsCoinbase(spendsCoinbase).FromTx(tx)); mempool.addUnchecked(hash, entry.Fee(1000000).Time(GetTime()).SpendsCoinbase(spendsCoinbase).FromTx(tx));
tx.vin[0].prevout.hash = hash; tx.vin[0].prevout.hash = hash;
} }
BOOST_CHECK(pblocktemplate = CreateNewBlock(chainparams, scriptPubKey)); BOOST_CHECK(pblocktemplate = CreateNewBlock(chainparams, scriptPubKey));
delete pblocktemplate; delete pblocktemplate;
mempool.clear(); mempool.clear();
// orphan in mempool // orphan in mempool, template creation fails
hash = tx.GetHash(); hash = tx.GetHash();
mempool.addUnchecked(hash, entry.Time(GetTime()).FromTx(tx)); mempool.addUnchecked(hash, entry.Fee(1000000).Time(GetTime()).FromTx(tx));
BOOST_CHECK(pblocktemplate = CreateNewBlock(chainparams, scriptPubKey)); BOOST_CHECK_THROW(CreateNewBlock(chainparams, scriptPubKey), std::runtime_error);
delete pblocktemplate;
mempool.clear(); mempool.clear();
// child with higher priority than parent // child with higher priority than parent
@ -160,7 +174,7 @@ BOOST_AUTO_TEST_CASE(CreateNewBlock_validity)
tx.vin[0].prevout.hash = txFirst[1]->GetHash(); tx.vin[0].prevout.hash = txFirst[1]->GetHash();
tx.vout[0].nValue = 4900000000LL; tx.vout[0].nValue = 4900000000LL;
hash = tx.GetHash(); hash = tx.GetHash();
mempool.addUnchecked(hash, entry.Time(GetTime()).SpendsCoinbase(true).FromTx(tx)); mempool.addUnchecked(hash, entry.Fee(100000000LL).Time(GetTime()).SpendsCoinbase(true).FromTx(tx));
tx.vin[0].prevout.hash = hash; tx.vin[0].prevout.hash = hash;
tx.vin.resize(2); tx.vin.resize(2);
tx.vin[1].scriptSig = CScript() << OP_1; tx.vin[1].scriptSig = CScript() << OP_1;
@ -168,23 +182,23 @@ BOOST_AUTO_TEST_CASE(CreateNewBlock_validity)
tx.vin[1].prevout.n = 0; tx.vin[1].prevout.n = 0;
tx.vout[0].nValue = 5900000000LL; tx.vout[0].nValue = 5900000000LL;
hash = tx.GetHash(); hash = tx.GetHash();
mempool.addUnchecked(hash, entry.Time(GetTime()).SpendsCoinbase(true).FromTx(tx)); mempool.addUnchecked(hash, entry.Fee(400000000LL).Time(GetTime()).SpendsCoinbase(true).FromTx(tx));
BOOST_CHECK(pblocktemplate = CreateNewBlock(chainparams, scriptPubKey)); BOOST_CHECK(pblocktemplate = CreateNewBlock(chainparams, scriptPubKey));
delete pblocktemplate; delete pblocktemplate;
mempool.clear(); mempool.clear();
// coinbase in mempool // coinbase in mempool, template creation fails
tx.vin.resize(1); tx.vin.resize(1);
tx.vin[0].prevout.SetNull(); tx.vin[0].prevout.SetNull();
tx.vin[0].scriptSig = CScript() << OP_0 << OP_1; tx.vin[0].scriptSig = CScript() << OP_0 << OP_1;
tx.vout[0].nValue = 0; tx.vout[0].nValue = 0;
hash = tx.GetHash(); hash = tx.GetHash();
mempool.addUnchecked(hash, entry.Time(GetTime()).SpendsCoinbase(false).FromTx(tx)); // give it a fee so it'll get mined
BOOST_CHECK(pblocktemplate = CreateNewBlock(chainparams, scriptPubKey)); mempool.addUnchecked(hash, entry.Fee(100000).Time(GetTime()).SpendsCoinbase(false).FromTx(tx));
delete pblocktemplate; BOOST_CHECK_THROW(CreateNewBlock(chainparams, scriptPubKey), std::runtime_error);
mempool.clear(); mempool.clear();
// invalid (pre-p2sh) txn in mempool // invalid (pre-p2sh) txn in mempool, template creation fails
tx.vin[0].prevout.hash = txFirst[0]->GetHash(); tx.vin[0].prevout.hash = txFirst[0]->GetHash();
tx.vin[0].prevout.n = 0; tx.vin[0].prevout.n = 0;
tx.vin[0].scriptSig = CScript() << OP_1; tx.vin[0].scriptSig = CScript() << OP_1;
@ -192,28 +206,26 @@ BOOST_AUTO_TEST_CASE(CreateNewBlock_validity)
script = CScript() << OP_0; script = CScript() << OP_0;
tx.vout[0].scriptPubKey = GetScriptForDestination(CScriptID(script)); tx.vout[0].scriptPubKey = GetScriptForDestination(CScriptID(script));
hash = tx.GetHash(); hash = tx.GetHash();
mempool.addUnchecked(hash, entry.Time(GetTime()).SpendsCoinbase(true).FromTx(tx)); mempool.addUnchecked(hash, entry.Fee(10000000L).Time(GetTime()).SpendsCoinbase(true).FromTx(tx));
tx.vin[0].prevout.hash = hash; tx.vin[0].prevout.hash = hash;
tx.vin[0].scriptSig = CScript() << std::vector<unsigned char>(script.begin(), script.end()); tx.vin[0].scriptSig = CScript() << std::vector<unsigned char>(script.begin(), script.end());
tx.vout[0].nValue -= 1000000; tx.vout[0].nValue -= 1000000;
hash = tx.GetHash(); hash = tx.GetHash();
mempool.addUnchecked(hash, entry.Time(GetTime()).SpendsCoinbase(false).FromTx(tx)); mempool.addUnchecked(hash, entry.Fee(1000000).Time(GetTime()).SpendsCoinbase(false).FromTx(tx));
BOOST_CHECK(pblocktemplate = CreateNewBlock(chainparams, scriptPubKey)); BOOST_CHECK_THROW(CreateNewBlock(chainparams, scriptPubKey), std::runtime_error);
delete pblocktemplate;
mempool.clear(); mempool.clear();
// double spend txn pair in mempool // double spend txn pair in mempool, template creation fails
tx.vin[0].prevout.hash = txFirst[0]->GetHash(); tx.vin[0].prevout.hash = txFirst[0]->GetHash();
tx.vin[0].scriptSig = CScript() << OP_1; tx.vin[0].scriptSig = CScript() << OP_1;
tx.vout[0].nValue = 4900000000LL; tx.vout[0].nValue = 4900000000LL;
tx.vout[0].scriptPubKey = CScript() << OP_1; tx.vout[0].scriptPubKey = CScript() << OP_1;
hash = tx.GetHash(); hash = tx.GetHash();
mempool.addUnchecked(hash, entry.Time(GetTime()).SpendsCoinbase(true).FromTx(tx)); mempool.addUnchecked(hash, entry.Fee(100000000L).Time(GetTime()).SpendsCoinbase(true).FromTx(tx));
tx.vout[0].scriptPubKey = CScript() << OP_2; tx.vout[0].scriptPubKey = CScript() << OP_2;
hash = tx.GetHash(); hash = tx.GetHash();
mempool.addUnchecked(hash, entry.Time(GetTime()).SpendsCoinbase(true).FromTx(tx)); mempool.addUnchecked(hash, entry.Fee(100000000L).Time(GetTime()).SpendsCoinbase(true).FromTx(tx));
BOOST_CHECK(pblocktemplate = CreateNewBlock(chainparams, scriptPubKey)); BOOST_CHECK_THROW(CreateNewBlock(chainparams, scriptPubKey), std::runtime_error);
delete pblocktemplate;
mempool.clear(); mempool.clear();
// subsidy changing // subsidy changing
@ -237,7 +249,7 @@ BOOST_AUTO_TEST_CASE(CreateNewBlock_validity)
tx.vout[0].scriptPubKey = CScript() << OP_1; tx.vout[0].scriptPubKey = CScript() << OP_1;
tx.nLockTime = chainActive.Tip()->nHeight+1; tx.nLockTime = chainActive.Tip()->nHeight+1;
hash = tx.GetHash(); hash = tx.GetHash();
mempool.addUnchecked(hash, entry.Time(GetTime()).SpendsCoinbase(true).FromTx(tx)); mempool.addUnchecked(hash, entry.Fee(100000000L).Time(GetTime()).SpendsCoinbase(true).FromTx(tx));
BOOST_CHECK(!CheckFinalTx(tx, LOCKTIME_MEDIAN_TIME_PAST)); BOOST_CHECK(!CheckFinalTx(tx, LOCKTIME_MEDIAN_TIME_PAST));
// time locked // time locked
@ -251,7 +263,7 @@ BOOST_AUTO_TEST_CASE(CreateNewBlock_validity)
tx2.vout[0].scriptPubKey = CScript() << OP_1; tx2.vout[0].scriptPubKey = CScript() << OP_1;
tx2.nLockTime = chainActive.Tip()->GetMedianTimePast()+1; tx2.nLockTime = chainActive.Tip()->GetMedianTimePast()+1;
hash = tx2.GetHash(); hash = tx2.GetHash();
mempool.addUnchecked(hash, entry.Time(GetTime()).SpendsCoinbase(true).FromTx(tx2)); mempool.addUnchecked(hash, entry.Fee(100000000L).Time(GetTime()).SpendsCoinbase(true).FromTx(tx2));
BOOST_CHECK(!CheckFinalTx(tx2, LOCKTIME_MEDIAN_TIME_PAST)); BOOST_CHECK(!CheckFinalTx(tx2, LOCKTIME_MEDIAN_TIME_PAST));
BOOST_CHECK(pblocktemplate = CreateNewBlock(chainparams, scriptPubKey)); BOOST_CHECK(pblocktemplate = CreateNewBlock(chainparams, scriptPubKey));

2
src/test/test_bitcoin.cpp

@ -150,7 +150,7 @@ CTxMemPoolEntry TestMemPoolEntryHelper::FromTx(CMutableTransaction &tx, CTxMemPo
CAmount inChainValue = hasNoDependencies ? txn.GetValueOut() : 0; CAmount inChainValue = hasNoDependencies ? txn.GetValueOut() : 0;
return CTxMemPoolEntry(txn, nFee, nTime, dPriority, nHeight, return CTxMemPoolEntry(txn, nFee, nTime, dPriority, nHeight,
hasNoDependencies, inChainValue, spendsCoinbase); hasNoDependencies, inChainValue, spendsCoinbase, sigOpCount);
} }
void Shutdown(void* parg) void Shutdown(void* parg)

4
src/test/test_bitcoin.h

@ -66,10 +66,11 @@ struct TestMemPoolEntryHelper
unsigned int nHeight; unsigned int nHeight;
bool hadNoDependencies; bool hadNoDependencies;
bool spendsCoinbase; bool spendsCoinbase;
unsigned int sigOpCount;
TestMemPoolEntryHelper() : TestMemPoolEntryHelper() :
nFee(0), nTime(0), dPriority(0.0), nHeight(1), nFee(0), nTime(0), dPriority(0.0), nHeight(1),
hadNoDependencies(false), spendsCoinbase(false) { } hadNoDependencies(false), spendsCoinbase(false), sigOpCount(1) { }
CTxMemPoolEntry FromTx(CMutableTransaction &tx, CTxMemPool *pool = NULL); CTxMemPoolEntry FromTx(CMutableTransaction &tx, CTxMemPool *pool = NULL);
@ -80,5 +81,6 @@ struct TestMemPoolEntryHelper
TestMemPoolEntryHelper &Height(unsigned int _height) { nHeight = _height; return *this; } TestMemPoolEntryHelper &Height(unsigned int _height) { nHeight = _height; return *this; }
TestMemPoolEntryHelper &HadNoDependencies(bool _hnd) { hadNoDependencies = _hnd; return *this; } TestMemPoolEntryHelper &HadNoDependencies(bool _hnd) { hadNoDependencies = _hnd; return *this; }
TestMemPoolEntryHelper &SpendsCoinbase(bool _flag) { spendsCoinbase = _flag; return *this; } TestMemPoolEntryHelper &SpendsCoinbase(bool _flag) { spendsCoinbase = _flag; return *this; }
TestMemPoolEntryHelper &SigOps(unsigned int _sigops) { sigOpCount = _sigops; return *this; }
}; };
#endif #endif

28
src/txmempool.cpp

@ -22,10 +22,10 @@ using namespace std;
CTxMemPoolEntry::CTxMemPoolEntry(const CTransaction& _tx, const CAmount& _nFee, CTxMemPoolEntry::CTxMemPoolEntry(const CTransaction& _tx, const CAmount& _nFee,
int64_t _nTime, double _entryPriority, unsigned int _entryHeight, int64_t _nTime, double _entryPriority, unsigned int _entryHeight,
bool poolHasNoInputsOf, CAmount _inChainInputValue, bool poolHasNoInputsOf, CAmount _inChainInputValue,
bool _spendsCoinbase): bool _spendsCoinbase, unsigned int _sigOps):
tx(_tx), nFee(_nFee), nTime(_nTime), entryPriority(_entryPriority), entryHeight(_entryHeight), tx(_tx), nFee(_nFee), nTime(_nTime), entryPriority(_entryPriority), entryHeight(_entryHeight),
hadNoDependencies(poolHasNoInputsOf), inChainInputValue(_inChainInputValue), hadNoDependencies(poolHasNoInputsOf), inChainInputValue(_inChainInputValue),
spendsCoinbase(_spendsCoinbase) spendsCoinbase(_spendsCoinbase), sigOpCount(_sigOps)
{ {
nTxSize = ::GetSerializeSize(tx, SER_NETWORK, PROTOCOL_VERSION); nTxSize = ::GetSerializeSize(tx, SER_NETWORK, PROTOCOL_VERSION);
nModSize = tx.CalculateModifiedSize(nTxSize); nModSize = tx.CalculateModifiedSize(nTxSize);
@ -36,6 +36,8 @@ CTxMemPoolEntry::CTxMemPoolEntry(const CTransaction& _tx, const CAmount& _nFee,
nFeesWithDescendants = nFee; nFeesWithDescendants = nFee;
CAmount nValueIn = tx.GetValueOut()+nFee; CAmount nValueIn = tx.GetValueOut()+nFee;
assert(inChainInputValue <= nValueIn); assert(inChainInputValue <= nValueIn);
feeDelta = 0;
} }
CTxMemPoolEntry::CTxMemPoolEntry(const CTxMemPoolEntry& other) CTxMemPoolEntry::CTxMemPoolEntry(const CTxMemPoolEntry& other)
@ -53,6 +55,11 @@ CTxMemPoolEntry::GetPriority(unsigned int currentHeight) const
return dResult; return dResult;
} }
void CTxMemPoolEntry::UpdateFeeDelta(int64_t newFeeDelta)
{
feeDelta = newFeeDelta;
}
// Update the given tx for any in-mempool descendants. // Update the given tx for any in-mempool descendants.
// Assumes that setMemPoolChildren is correct for the given tx and all // Assumes that setMemPoolChildren is correct for the given tx and all
// descendants. // descendants.
@ -392,6 +399,15 @@ bool CTxMemPool::addUnchecked(const uint256& hash, const CTxMemPoolEntry &entry,
} }
UpdateAncestorsOf(true, newit, setAncestors); UpdateAncestorsOf(true, newit, setAncestors);
// Update transaction's score for any feeDelta created by PrioritiseTransaction
std::map<uint256, std::pair<double, CAmount> >::const_iterator pos = mapDeltas.find(hash);
if (pos != mapDeltas.end()) {
const std::pair<double, CAmount> &deltas = pos->second;
if (deltas.second) {
mapTx.modify(newit, update_fee_delta(deltas.second));
}
}
nTransactionsUpdated++; nTransactionsUpdated++;
totalTxSize += entry.GetTxSize(); totalTxSize += entry.GetTxSize();
minerPolicyEstimator->processTransaction(entry, fCurrentEstimate); minerPolicyEstimator->processTransaction(entry, fCurrentEstimate);
@ -769,6 +785,10 @@ void CTxMemPool::PrioritiseTransaction(const uint256 hash, const string strHash,
std::pair<double, CAmount> &deltas = mapDeltas[hash]; std::pair<double, CAmount> &deltas = mapDeltas[hash];
deltas.first += dPriorityDelta; deltas.first += dPriorityDelta;
deltas.second += nFeeDelta; deltas.second += nFeeDelta;
txiter it = mapTx.find(hash);
if (it != mapTx.end()) {
mapTx.modify(it, update_fee_delta(deltas.second));
}
} }
LogPrintf("PrioritiseTransaction: %s priority += %f, fee += %d\n", strHash, dPriorityDelta, FormatMoney(nFeeDelta)); LogPrintf("PrioritiseTransaction: %s priority += %f, fee += %d\n", strHash, dPriorityDelta, FormatMoney(nFeeDelta));
} }
@ -818,8 +838,8 @@ bool CCoinsViewMemPool::HaveCoins(const uint256 &txid) const {
size_t CTxMemPool::DynamicMemoryUsage() const { size_t CTxMemPool::DynamicMemoryUsage() const {
LOCK(cs); LOCK(cs);
// Estimate the overhead of mapTx to be 9 pointers + an allocation, as no exact formula for boost::multi_index_contained is implemented. // Estimate the overhead of mapTx to be 12 pointers + an allocation, as no exact formula for boost::multi_index_contained is implemented.
return memusage::MallocUsage(sizeof(CTxMemPoolEntry) + 9 * sizeof(void*)) * mapTx.size() + memusage::DynamicUsage(mapNextTx) + memusage::DynamicUsage(mapDeltas) + memusage::DynamicUsage(mapLinks) + cachedInnerUsage; return memusage::MallocUsage(sizeof(CTxMemPoolEntry) + 12 * sizeof(void*)) * mapTx.size() + memusage::DynamicUsage(mapNextTx) + memusage::DynamicUsage(mapDeltas) + memusage::DynamicUsage(mapLinks) + cachedInnerUsage;
} }
void CTxMemPool::RemoveStaged(setEntries &stage) { void CTxMemPool::RemoveStaged(setEntries &stage) {

62
src/txmempool.h

@ -68,6 +68,8 @@ private:
bool hadNoDependencies; //! Not dependent on any other txs when it entered the mempool bool hadNoDependencies; //! Not dependent on any other txs when it entered the mempool
CAmount inChainInputValue; //! Sum of all txin values that are already in blockchain CAmount inChainInputValue; //! Sum of all txin values that are already in blockchain
bool spendsCoinbase; //! keep track of transactions that spend a coinbase bool spendsCoinbase; //! keep track of transactions that spend a coinbase
unsigned int sigOpCount; //! Legacy sig ops plus P2SH sig op count
int64_t feeDelta; //! Used for determining the priority of the transaction for mining in a block
// Information about descendants of this transaction that are in the // Information about descendants of this transaction that are in the
// mempool; if we remove this transaction we must remove all of these // mempool; if we remove this transaction we must remove all of these
@ -81,7 +83,8 @@ private:
public: public:
CTxMemPoolEntry(const CTransaction& _tx, const CAmount& _nFee, CTxMemPoolEntry(const CTransaction& _tx, const CAmount& _nFee,
int64_t _nTime, double _entryPriority, unsigned int _entryHeight, int64_t _nTime, double _entryPriority, unsigned int _entryHeight,
bool poolHasNoInputsOf, CAmount _inChainInputValue, bool spendsCoinbase); bool poolHasNoInputsOf, CAmount _inChainInputValue, bool spendsCoinbase,
unsigned int nSigOps);
CTxMemPoolEntry(const CTxMemPoolEntry& other); CTxMemPoolEntry(const CTxMemPoolEntry& other);
const CTransaction& GetTx() const { return this->tx; } const CTransaction& GetTx() const { return this->tx; }
@ -95,10 +98,14 @@ public:
int64_t GetTime() const { return nTime; } int64_t GetTime() const { return nTime; }
unsigned int GetHeight() const { return entryHeight; } unsigned int GetHeight() const { return entryHeight; }
bool WasClearAtEntry() const { return hadNoDependencies; } bool WasClearAtEntry() const { return hadNoDependencies; }
unsigned int GetSigOpCount() const { return sigOpCount; }
int64_t GetModifiedFee() const { return nFee + feeDelta; }
size_t DynamicMemoryUsage() const { return nUsageSize; } size_t DynamicMemoryUsage() const { return nUsageSize; }
// Adjusts the descendant state, if this entry is not dirty. // Adjusts the descendant state, if this entry is not dirty.
void UpdateState(int64_t modifySize, CAmount modifyFee, int64_t modifyCount); void UpdateState(int64_t modifySize, CAmount modifyFee, int64_t modifyCount);
// Updates the fee delta used for mining priority score
void UpdateFeeDelta(int64_t feeDelta);
/** We can set the entry to be dirty if doing the full calculation of in- /** We can set the entry to be dirty if doing the full calculation of in-
* mempool descendants will be too expensive, which can potentially happen * mempool descendants will be too expensive, which can potentially happen
@ -136,6 +143,16 @@ struct set_dirty
{ e.SetDirty(); } { e.SetDirty(); }
}; };
struct update_fee_delta
{
update_fee_delta(int64_t _feeDelta) : feeDelta(_feeDelta) { }
void operator() (CTxMemPoolEntry &e) { e.UpdateFeeDelta(feeDelta); }
private:
int64_t feeDelta;
};
// extracts a TxMemPoolEntry's transaction hash // extracts a TxMemPoolEntry's transaction hash
struct mempoolentry_txid struct mempoolentry_txid
{ {
@ -183,6 +200,24 @@ public:
} }
}; };
/** \class CompareTxMemPoolEntryByScore
*
* Sort by score of entry ((fee+delta)/size) in descending order
*/
class CompareTxMemPoolEntryByScore
{
public:
bool operator()(const CTxMemPoolEntry& a, const CTxMemPoolEntry& b)
{
double f1 = (double)a.GetModifiedFee() * b.GetTxSize();
double f2 = (double)b.GetModifiedFee() * a.GetTxSize();
if (f1 == f2) {
return b.GetTx().GetHash() < a.GetTx().GetHash();
}
return f1 > f2;
}
};
class CompareTxMemPoolEntryByEntryTime class CompareTxMemPoolEntryByEntryTime
{ {
public: public:
@ -220,10 +255,11 @@ public:
* *
* CTxMemPool::mapTx, and CTxMemPoolEntry bookkeeping: * CTxMemPool::mapTx, and CTxMemPoolEntry bookkeeping:
* *
* mapTx is a boost::multi_index that sorts the mempool on 3 criteria: * mapTx is a boost::multi_index that sorts the mempool on 4 criteria:
* - transaction hash * - transaction hash
* - feerate [we use max(feerate of tx, feerate of tx with all descendants)] * - feerate [we use max(feerate of tx, feerate of tx with all descendants)]
* - time in mempool * - time in mempool
* - mining score (feerate modified by any fee deltas from PrioritiseTransaction)
* *
* Note: the term "descendant" refers to in-mempool transactions that depend on * Note: the term "descendant" refers to in-mempool transactions that depend on
* this one, while "ancestor" refers to in-mempool transactions that a given * this one, while "ancestor" refers to in-mempool transactions that a given
@ -320,6 +356,11 @@ public:
boost::multi_index::ordered_non_unique< boost::multi_index::ordered_non_unique<
boost::multi_index::identity<CTxMemPoolEntry>, boost::multi_index::identity<CTxMemPoolEntry>,
CompareTxMemPoolEntryByEntryTime CompareTxMemPoolEntryByEntryTime
>,
// sorted by score (for mining prioritization)
boost::multi_index::ordered_unique<
boost::multi_index::identity<CTxMemPoolEntry>,
CompareTxMemPoolEntryByScore
> >
> >
> indexed_transaction_set; > indexed_transaction_set;
@ -334,6 +375,8 @@ public:
}; };
typedef std::set<txiter, CompareIteratorByHash> setEntries; typedef std::set<txiter, CompareIteratorByHash> setEntries;
const setEntries & GetMemPoolParents(txiter entry) const;
const setEntries & GetMemPoolChildren(txiter entry) const;
private: private:
typedef std::map<txiter, setEntries, CompareIteratorByHash> cacheMap; typedef std::map<txiter, setEntries, CompareIteratorByHash> cacheMap;
@ -345,8 +388,6 @@ private:
typedef std::map<txiter, TxLinks, CompareIteratorByHash> txlinksMap; typedef std::map<txiter, TxLinks, CompareIteratorByHash> txlinksMap;
txlinksMap mapLinks; txlinksMap mapLinks;
const setEntries & GetMemPoolParents(txiter entry) const;
const setEntries & GetMemPoolChildren(txiter entry) const;
void UpdateParent(txiter entry, txiter parent, bool add); void UpdateParent(txiter entry, txiter parent, bool add);
void UpdateChild(txiter entry, txiter child, bool add); void UpdateChild(txiter entry, txiter child, bool add);
@ -546,4 +587,17 @@ public:
bool HaveCoins(const uint256 &txid) const; bool HaveCoins(const uint256 &txid) const;
}; };
// 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;
}
};
#endif // BITCOIN_TXMEMPOOL_H #endif // BITCOIN_TXMEMPOOL_H

Loading…
Cancel
Save