Skip to content

Commit fbb66c0

Browse files
ashleyholmansipa
authored andcommitted
TxMemPool: Change mapTx to a boost::multi_index_container
Indexes on: - Tx Hash - Fee Rate (fee-per-kb)
1 parent c05f969 commit fbb66c0

File tree

5 files changed

+128
-32
lines changed

5 files changed

+128
-32
lines changed

src/miner.cpp

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@
1616
#include "pow.h"
1717
#include "primitives/transaction.h"
1818
#include "timedata.h"
19+
#include "txmempool.h"
1920
#include "util.h"
2021
#include "utilmoneystr.h"
2122
#include "validationinterface.h"
@@ -148,10 +149,10 @@ CBlockTemplate* CreateNewBlock(const CScript& scriptPubKeyIn)
148149
// This vector will be sorted into a priority queue:
149150
vector<TxPriority> vecPriority;
150151
vecPriority.reserve(mempool.mapTx.size());
151-
for (map<uint256, CTxMemPoolEntry>::iterator mi = mempool.mapTx.begin();
152+
for (CTxMemPool::indexed_transaction_set::iterator mi = mempool.mapTx.begin();
152153
mi != mempool.mapTx.end(); ++mi)
153154
{
154-
const CTransaction& tx = mi->second.GetTx();
155+
const CTransaction& tx = mi->GetTx();
155156
if (tx.IsCoinBase() || !IsFinalTx(tx, nHeight, pblock->nTime))
156157
continue;
157158

@@ -186,7 +187,7 @@ CBlockTemplate* CreateNewBlock(const CScript& scriptPubKeyIn)
186187
}
187188
mapDependers[txin.prevout.hash].push_back(porphan);
188189
porphan->setDependsOn.insert(txin.prevout.hash);
189-
nTotalIn += mempool.mapTx[txin.prevout.hash].GetTx().vout[txin.prevout.n].nValue;
190+
nTotalIn += mempool.mapTx.find(txin.prevout.hash)->GetTx().vout[txin.prevout.n].nValue;
190191
continue;
191192
}
192193
const CCoins* coins = view.AccessCoins(txin.prevout.hash);
@@ -216,7 +217,7 @@ CBlockTemplate* CreateNewBlock(const CScript& scriptPubKeyIn)
216217
porphan->feeRate = feeRate;
217218
}
218219
else
219-
vecPriority.push_back(TxPriority(dPriority, feeRate, &mi->second.GetTx()));
220+
vecPriority.push_back(TxPriority(dPriority, feeRate, &(mi->GetTx())));
220221
}
221222

222223
// Collect transactions into block

src/rpcblockchain.cpp

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -211,10 +211,9 @@ UniValue getrawmempool(const UniValue& params, bool fHelp)
211211
{
212212
LOCK(mempool.cs);
213213
UniValue o(UniValue::VOBJ);
214-
BOOST_FOREACH(const PAIRTYPE(uint256, CTxMemPoolEntry)& entry, mempool.mapTx)
214+
BOOST_FOREACH(const CTxMemPoolEntry& e, mempool.mapTx)
215215
{
216-
const uint256& hash = entry.first;
217-
const CTxMemPoolEntry& e = entry.second;
216+
const uint256& hash = e.GetTx().GetHash();
218217
UniValue info(UniValue::VOBJ);
219218
info.push_back(Pair("size", (int)e.GetTxSize()));
220219
info.push_back(Pair("fee", ValueFromAmount(e.GetFee())));

src/test/mempool_tests.cpp

Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -101,4 +101,56 @@ BOOST_AUTO_TEST_CASE(MempoolRemoveTest)
101101
removed.clear();
102102
}
103103

104+
BOOST_AUTO_TEST_CASE(MempoolIndexingTest)
105+
{
106+
CTxMemPool pool(CFeeRate(0));
107+
108+
/* 3rd highest fee */
109+
CMutableTransaction tx1 = CMutableTransaction();
110+
tx1.vout.resize(1);
111+
tx1.vout[0].scriptPubKey = CScript() << OP_11 << OP_EQUAL;
112+
tx1.vout[0].nValue = 10 * COIN;
113+
pool.addUnchecked(tx1.GetHash(), CTxMemPoolEntry(tx1, 10000LL, 0, 10.0, 1, true));
114+
115+
/* highest fee */
116+
CMutableTransaction tx2 = CMutableTransaction();
117+
tx2.vout.resize(1);
118+
tx2.vout[0].scriptPubKey = CScript() << OP_11 << OP_EQUAL;
119+
tx2.vout[0].nValue = 2 * COIN;
120+
pool.addUnchecked(tx2.GetHash(), CTxMemPoolEntry(tx2, 20000LL, 0, 9.0, 1, true));
121+
122+
/* lowest fee */
123+
CMutableTransaction tx3 = CMutableTransaction();
124+
tx3.vout.resize(1);
125+
tx3.vout[0].scriptPubKey = CScript() << OP_11 << OP_EQUAL;
126+
tx3.vout[0].nValue = 5 * COIN;
127+
pool.addUnchecked(tx3.GetHash(), CTxMemPoolEntry(tx3, 0LL, 0, 100.0, 1, true));
128+
129+
/* 2nd highest fee */
130+
CMutableTransaction tx4 = CMutableTransaction();
131+
tx4.vout.resize(1);
132+
tx4.vout[0].scriptPubKey = CScript() << OP_11 << OP_EQUAL;
133+
tx4.vout[0].nValue = 6 * COIN;
134+
pool.addUnchecked(tx4.GetHash(), CTxMemPoolEntry(tx4, 15000LL, 0, 1.0, 1, true));
135+
136+
/* equal fee rate to tx1, but newer */
137+
CMutableTransaction tx5 = CMutableTransaction();
138+
tx5.vout.resize(1);
139+
tx5.vout[0].scriptPubKey = CScript() << OP_11 << OP_EQUAL;
140+
tx5.vout[0].nValue = 11 * COIN;
141+
pool.addUnchecked(tx5.GetHash(), CTxMemPoolEntry(tx5, 10000LL, 1, 10.0, 1, true));
142+
143+
// there should be 4 transactions in the mempool
144+
BOOST_CHECK_EQUAL(pool.size(), 5);
145+
146+
// Check the fee-rate index is in order, should be tx2, tx4, tx1, tx5, tx3
147+
CTxMemPool::indexed_transaction_set::nth_index<1>::type::iterator it = pool.mapTx.get<1>().begin();
148+
BOOST_CHECK_EQUAL(it++->GetTx().GetHash().ToString(), tx2.GetHash().ToString());
149+
BOOST_CHECK_EQUAL(it++->GetTx().GetHash().ToString(), tx4.GetHash().ToString());
150+
BOOST_CHECK_EQUAL(it++->GetTx().GetHash().ToString(), tx1.GetHash().ToString());
151+
BOOST_CHECK_EQUAL(it++->GetTx().GetHash().ToString(), tx5.GetHash().ToString());
152+
BOOST_CHECK_EQUAL(it++->GetTx().GetHash().ToString(), tx3.GetHash().ToString());
153+
BOOST_CHECK(it == pool.mapTx.get<1>().end());
154+
}
155+
104156
BOOST_AUTO_TEST_SUITE_END()

src/txmempool.cpp

Lines changed: 28 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,7 @@ CTxMemPoolEntry::CTxMemPoolEntry(const CTransaction& _tx, const CAmount& _nFee,
3232
nTxSize = ::GetSerializeSize(tx, SER_NETWORK, PROTOCOL_VERSION);
3333
nModSize = tx.CalculateModifiedSize(nTxSize);
3434
nUsageSize = tx.DynamicMemoryUsage();
35+
feeRate = CFeeRate(nFee, nTxSize);
3536
}
3637

3738
CTxMemPoolEntry::CTxMemPoolEntry(const CTxMemPoolEntry& other)
@@ -96,8 +97,8 @@ bool CTxMemPool::addUnchecked(const uint256& hash, const CTxMemPoolEntry &entry,
9697
// Used by main.cpp AcceptToMemoryPool(), which DOES do
9798
// all the appropriate checks.
9899
LOCK(cs);
99-
mapTx[hash] = entry;
100-
const CTransaction& tx = mapTx[hash].GetTx();
100+
mapTx.insert(entry);
101+
const CTransaction& tx = mapTx.find(hash)->GetTx();
101102
for (unsigned int i = 0; i < tx.vin.size(); i++)
102103
mapNextTx[tx.vin[i].prevout] = CInPoint(&tx, i);
103104
nTransactionsUpdated++;
@@ -134,7 +135,7 @@ void CTxMemPool::remove(const CTransaction &origTx, std::list<CTransaction>& rem
134135
txToRemove.pop_front();
135136
if (!mapTx.count(hash))
136137
continue;
137-
const CTransaction& tx = mapTx[hash].GetTx();
138+
const CTransaction& tx = mapTx.find(hash)->GetTx();
138139
if (fRecursive) {
139140
for (unsigned int i = 0; i < tx.vout.size(); i++) {
140141
std::map<COutPoint, CInPoint>::iterator it = mapNextTx.find(COutPoint(hash, i));
@@ -147,8 +148,8 @@ void CTxMemPool::remove(const CTransaction &origTx, std::list<CTransaction>& rem
147148
mapNextTx.erase(txin.prevout);
148149

149150
removed.push_back(tx);
150-
totalTxSize -= mapTx[hash].GetTxSize();
151-
cachedInnerUsage -= mapTx[hash].DynamicMemoryUsage();
151+
totalTxSize -= mapTx.find(hash)->GetTxSize();
152+
cachedInnerUsage -= mapTx.find(hash)->DynamicMemoryUsage();
152153
mapTx.erase(hash);
153154
nTransactionsUpdated++;
154155
minerPolicyEstimator->removeTx(hash);
@@ -161,10 +162,10 @@ void CTxMemPool::removeCoinbaseSpends(const CCoinsViewCache *pcoins, unsigned in
161162
// Remove transactions spending a coinbase which are now immature
162163
LOCK(cs);
163164
list<CTransaction> transactionsToRemove;
164-
for (std::map<uint256, CTxMemPoolEntry>::const_iterator it = mapTx.begin(); it != mapTx.end(); it++) {
165-
const CTransaction& tx = it->second.GetTx();
165+
for (indexed_transaction_set::const_iterator it = mapTx.begin(); it != mapTx.end(); it++) {
166+
const CTransaction& tx = it->GetTx();
166167
BOOST_FOREACH(const CTxIn& txin, tx.vin) {
167-
std::map<uint256, CTxMemPoolEntry>::const_iterator it2 = mapTx.find(txin.prevout.hash);
168+
indexed_transaction_set::const_iterator it2 = mapTx.find(txin.prevout.hash);
168169
if (it2 != mapTx.end())
169170
continue;
170171
const CCoins *coins = pcoins->AccessCoins(txin.prevout.hash);
@@ -209,8 +210,10 @@ void CTxMemPool::removeForBlock(const std::vector<CTransaction>& vtx, unsigned i
209210
BOOST_FOREACH(const CTransaction& tx, vtx)
210211
{
211212
uint256 hash = tx.GetHash();
212-
if (mapTx.count(hash))
213-
entries.push_back(mapTx[hash]);
213+
214+
indexed_transaction_set::iterator i = mapTx.find(hash);
215+
if (i != mapTx.end())
216+
entries.push_back(*i);
214217
}
215218
BOOST_FOREACH(const CTransaction& tx, vtx)
216219
{
@@ -247,17 +250,17 @@ void CTxMemPool::check(const CCoinsViewCache *pcoins) const
247250

248251
LOCK(cs);
249252
list<const CTxMemPoolEntry*> waitingOnDependants;
250-
for (std::map<uint256, CTxMemPoolEntry>::const_iterator it = mapTx.begin(); it != mapTx.end(); it++) {
253+
for (indexed_transaction_set::const_iterator it = mapTx.begin(); it != mapTx.end(); it++) {
251254
unsigned int i = 0;
252-
checkTotal += it->second.GetTxSize();
253-
innerUsage += it->second.DynamicMemoryUsage();
254-
const CTransaction& tx = it->second.GetTx();
255+
checkTotal += it->GetTxSize();
256+
innerUsage += it->DynamicMemoryUsage();
257+
const CTransaction& tx = it->GetTx();
255258
bool fDependsWait = false;
256259
BOOST_FOREACH(const CTxIn &txin, tx.vin) {
257260
// Check that every mempool transaction's inputs refer to available coins, or other mempool tx's.
258-
std::map<uint256, CTxMemPoolEntry>::const_iterator it2 = mapTx.find(txin.prevout.hash);
261+
indexed_transaction_set::const_iterator it2 = mapTx.find(txin.prevout.hash);
259262
if (it2 != mapTx.end()) {
260-
const CTransaction& tx2 = it2->second.GetTx();
263+
const CTransaction& tx2 = it2->GetTx();
261264
assert(tx2.vout.size() > txin.prevout.n && !tx2.vout[txin.prevout.n].IsNull());
262265
fDependsWait = true;
263266
} else {
@@ -272,7 +275,7 @@ void CTxMemPool::check(const CCoinsViewCache *pcoins) const
272275
i++;
273276
}
274277
if (fDependsWait)
275-
waitingOnDependants.push_back(&it->second);
278+
waitingOnDependants.push_back(&(*it));
276279
else {
277280
CValidationState state;
278281
assert(CheckInputs(tx, state, mempoolDuplicate, false, 0, false, NULL));
@@ -296,8 +299,8 @@ void CTxMemPool::check(const CCoinsViewCache *pcoins) const
296299
}
297300
for (std::map<COutPoint, CInPoint>::const_iterator it = mapNextTx.begin(); it != mapNextTx.end(); it++) {
298301
uint256 hash = it->second.ptx->GetHash();
299-
map<uint256, CTxMemPoolEntry>::const_iterator it2 = mapTx.find(hash);
300-
const CTransaction& tx = it2->second.GetTx();
302+
indexed_transaction_set::const_iterator it2 = mapTx.find(hash);
303+
const CTransaction& tx = it2->GetTx();
301304
assert(it2 != mapTx.end());
302305
assert(&tx == it->second.ptx);
303306
assert(tx.vin.size() > it->second.n);
@@ -314,16 +317,16 @@ void CTxMemPool::queryHashes(vector<uint256>& vtxid)
314317

315318
LOCK(cs);
316319
vtxid.reserve(mapTx.size());
317-
for (map<uint256, CTxMemPoolEntry>::iterator mi = mapTx.begin(); mi != mapTx.end(); ++mi)
318-
vtxid.push_back((*mi).first);
320+
for (indexed_transaction_set::iterator mi = mapTx.begin(); mi != mapTx.end(); ++mi)
321+
vtxid.push_back(mi->GetTx().GetHash());
319322
}
320323

321324
bool CTxMemPool::lookup(uint256 hash, CTransaction& result) const
322325
{
323326
LOCK(cs);
324-
map<uint256, CTxMemPoolEntry>::const_iterator i = mapTx.find(hash);
327+
indexed_transaction_set::const_iterator i = mapTx.find(hash);
325328
if (i == mapTx.end()) return false;
326-
result = i->second.GetTx();
329+
result = i->GetTx();
327330
return true;
328331
}
329332

@@ -429,5 +432,6 @@ bool CCoinsViewMemPool::HaveCoins(const uint256 &txid) const {
429432

430433
size_t CTxMemPool::DynamicMemoryUsage() const {
431434
LOCK(cs);
432-
return memusage::DynamicUsage(mapTx) + memusage::DynamicUsage(mapNextTx) + memusage::DynamicUsage(mapDeltas) + cachedInnerUsage;
435+
// Estimate the overhead of mapTx to be 5 pointers + an allocation, as no exact formula for boost::multi_index_contained is implemented.
436+
return memusage::MallocUsage(sizeof(CTxMemPoolEntry) + 5 * sizeof(void*)) * mapTx.size() + memusage::DynamicUsage(mapNextTx) + memusage::DynamicUsage(mapDeltas) + cachedInnerUsage;
433437
}

src/txmempool.h

Lines changed: 41 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,10 @@
1313
#include "primitives/transaction.h"
1414
#include "sync.h"
1515

16+
#undef foreach
17+
#include "boost/multi_index_container.hpp"
18+
#include "boost/multi_index/ordered_index.hpp"
19+
1620
class CAutoFile;
1721

1822
inline double AllowFreeThreshold()
@@ -41,6 +45,7 @@ class CTxMemPoolEntry
4145
size_t nTxSize; //! ... and avoid recomputing tx size
4246
size_t nModSize; //! ... and modified size for priority
4347
size_t nUsageSize; //! ... and total memory usage
48+
CFeeRate feeRate; //! ... and fee per kB
4449
int64_t nTime; //! Local time when entering the mempool
4550
double dPriority; //! Priority when entering the mempool
4651
unsigned int nHeight; //! Chain height when entering the mempool
@@ -55,13 +60,35 @@ class CTxMemPoolEntry
5560
const CTransaction& GetTx() const { return this->tx; }
5661
double GetPriority(unsigned int currentHeight) const;
5762
CAmount GetFee() const { return nFee; }
63+
CFeeRate GetFeeRate() const { return feeRate; }
5864
size_t GetTxSize() const { return nTxSize; }
5965
int64_t GetTime() const { return nTime; }
6066
unsigned int GetHeight() const { return nHeight; }
6167
bool WasClearAtEntry() const { return hadNoDependencies; }
6268
size_t DynamicMemoryUsage() const { return nUsageSize; }
6369
};
6470

71+
// extracts a TxMemPoolEntry's transaction hash
72+
struct mempoolentry_txid
73+
{
74+
typedef uint256 result_type;
75+
result_type operator() (const CTxMemPoolEntry &entry) const
76+
{
77+
return entry.GetTx().GetHash();
78+
}
79+
};
80+
81+
class CompareTxMemPoolEntryByFee
82+
{
83+
public:
84+
bool operator()(const CTxMemPoolEntry& a, const CTxMemPoolEntry& b)
85+
{
86+
if (a.GetFeeRate() == b.GetFeeRate())
87+
return a.GetTime() < b.GetTime();
88+
return a.GetFeeRate() > b.GetFeeRate();
89+
}
90+
};
91+
6592
class CBlockPolicyEstimator;
6693

6794
/** An inpoint - a combination of a transaction and an index n into its vin */
@@ -99,8 +126,21 @@ class CTxMemPool
99126
uint64_t cachedInnerUsage; //! sum of dynamic memory usage of all the map elements (NOT the maps themselves)
100127

101128
public:
129+
typedef boost::multi_index_container<
130+
CTxMemPoolEntry,
131+
boost::multi_index::indexed_by<
132+
// sorted by txid
133+
boost::multi_index::ordered_unique<mempoolentry_txid>,
134+
// sorted by fee rate
135+
boost::multi_index::ordered_non_unique<
136+
boost::multi_index::identity<CTxMemPoolEntry>,
137+
CompareTxMemPoolEntryByFee
138+
>
139+
>
140+
> indexed_transaction_set;
141+
102142
mutable CCriticalSection cs;
103-
std::map<uint256, CTxMemPoolEntry> mapTx;
143+
indexed_transaction_set mapTx;
104144
std::map<COutPoint, CInPoint> mapNextTx;
105145
std::map<uint256, std::pair<double, CAmount> > mapDeltas;
106146

0 commit comments

Comments
 (0)