Skip to content

Commit b0ce450

Browse files
committed
Merge pull request #6654
5add7a7 Track transaction packages in CTxMemPoolEntry (Suhas Daftuar) 34628a1 TxMemPool: Change mapTx to a boost::multi_index_container (Ashley Holman)
2 parents d5d1d2e + 5add7a7 commit b0ce450

File tree

11 files changed

+1098
-75
lines changed

11 files changed

+1098
-75
lines changed

qa/pull-tester/rpc-tests.sh

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -57,6 +57,7 @@ testScriptsExt=(
5757
'invalidblockrequest.py'
5858
# 'forknotify.py'
5959
'p2p-acceptblock.py'
60+
'mempool_packages.py'
6061
);
6162

6263
#if [ "x$ENABLE_ZMQ" = "x1" ]; then

qa/rpc-tests/mempool_packages.py

Lines changed: 107 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,107 @@
1+
#!/usr/bin/env python2
2+
# Copyright (c) 2014-2015 The Bitcoin Core developers
3+
# Distributed under the MIT software license, see the accompanying
4+
# file COPYING or http://www.opensource.org/licenses/mit-license.php.
5+
6+
# Test descendant package tracking code
7+
8+
from test_framework.test_framework import BitcoinTestFramework
9+
from test_framework.util import *
10+
11+
def satoshi_round(amount):
12+
return Decimal(amount).quantize(Decimal('0.00000001'), rounding=ROUND_DOWN)
13+
14+
class MempoolPackagesTest(BitcoinTestFramework):
15+
16+
def setup_network(self):
17+
self.nodes = []
18+
self.nodes.append(start_node(0, self.options.tmpdir, ["-maxorphantx=1000", "-relaypriority=0"]))
19+
self.is_network_split = False
20+
self.sync_all()
21+
22+
# Build a transaction that spends parent_txid:vout
23+
# Return amount sent
24+
def chain_transaction(self, parent_txid, vout, value, fee, num_outputs):
25+
send_value = satoshi_round((value - fee)/num_outputs)
26+
inputs = [ {'txid' : parent_txid, 'vout' : vout} ]
27+
outputs = {}
28+
for i in xrange(num_outputs):
29+
outputs[self.nodes[0].getnewaddress()] = send_value
30+
rawtx = self.nodes[0].createrawtransaction(inputs, outputs)
31+
signedtx = self.nodes[0].signrawtransaction(rawtx)
32+
txid = self.nodes[0].sendrawtransaction(signedtx['hex'])
33+
fulltx = self.nodes[0].getrawtransaction(txid, 1)
34+
assert(len(fulltx['vout']) == num_outputs) # make sure we didn't generate a change output
35+
return (txid, send_value)
36+
37+
def run_test(self):
38+
''' Mine some blocks and have them mature. '''
39+
self.nodes[0].generate(101)
40+
utxo = self.nodes[0].listunspent(10)
41+
txid = utxo[0]['txid']
42+
vout = utxo[0]['vout']
43+
value = utxo[0]['amount']
44+
45+
fee = Decimal("0.0001")
46+
# 100 transactions off a confirmed tx should be fine
47+
chain = []
48+
for i in xrange(100):
49+
(txid, sent_value) = self.chain_transaction(txid, 0, value, fee, 1)
50+
value = sent_value
51+
chain.append(txid)
52+
53+
# Check mempool has 100 transactions in it, and descendant
54+
# count and fees should look correct
55+
mempool = self.nodes[0].getrawmempool(True)
56+
assert_equal(len(mempool), 100)
57+
descendant_count = 1
58+
descendant_fees = 0
59+
descendant_size = 0
60+
SATOSHIS = 100000000
61+
62+
for x in reversed(chain):
63+
assert_equal(mempool[x]['descendantcount'], descendant_count)
64+
descendant_fees += mempool[x]['fee']
65+
assert_equal(mempool[x]['descendantfees'], SATOSHIS*descendant_fees)
66+
descendant_size += mempool[x]['size']
67+
assert_equal(mempool[x]['descendantsize'], descendant_size)
68+
descendant_count += 1
69+
70+
# Adding one more transaction on to the chain should fail.
71+
try:
72+
self.chain_transaction(txid, vout, value, fee, 1)
73+
except JSONRPCException as e:
74+
print "too-long-ancestor-chain successfully rejected"
75+
76+
# TODO: test ancestor size limits
77+
78+
# Now test descendant chain limits
79+
txid = utxo[1]['txid']
80+
value = utxo[1]['amount']
81+
vout = utxo[1]['vout']
82+
83+
transaction_package = []
84+
# First create one parent tx with 10 children
85+
(txid, sent_value) = self.chain_transaction(txid, vout, value, fee, 10)
86+
parent_transaction = txid
87+
for i in xrange(10):
88+
transaction_package.append({'txid': txid, 'vout': i, 'amount': sent_value})
89+
90+
for i in xrange(1000):
91+
utxo = transaction_package.pop(0)
92+
try:
93+
(txid, sent_value) = self.chain_transaction(utxo['txid'], utxo['vout'], utxo['amount'], fee, 10)
94+
for j in xrange(10):
95+
transaction_package.append({'txid': txid, 'vout': j, 'amount': sent_value})
96+
if i == 998:
97+
mempool = self.nodes[0].getrawmempool(True)
98+
assert_equal(mempool[parent_transaction]['descendantcount'], 1000)
99+
except JSONRPCException as e:
100+
print e.error['message']
101+
assert_equal(i, 999)
102+
print "tx that would create too large descendant package successfully rejected"
103+
104+
# TODO: test descendant size limits
105+
106+
if __name__ == '__main__':
107+
MempoolPackagesTest().main()

src/init.cpp

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -411,6 +411,10 @@ std::string HelpMessage(HelpMessageMode mode)
411411
strUsage += HelpMessageOpt("-fuzzmessagestest=<n>", "Randomly fuzz 1 of every <n> network messages");
412412
strUsage += HelpMessageOpt("-flushwallet", strprintf("Run a thread to flush wallet periodically (default: %u)", 1));
413413
strUsage += HelpMessageOpt("-stopafterblockimport", strprintf("Stop running after importing blocks from disk (default: %u)", 0));
414+
strUsage += HelpMessageOpt("-limitancestorcount=<n>", strprintf("Do not accept transactions if number of in-mempool ancestors is <n> or more (default: %u)", DEFAULT_ANCESTOR_LIMIT));
415+
strUsage += HelpMessageOpt("-limitancestorsize=<n>", strprintf("Do not accept transactions whose size with all in-mempool ancestors exceeds <n> kilobytes (default: %u)", DEFAULT_ANCESTOR_SIZE_LIMIT));
416+
strUsage += HelpMessageOpt("-limitdescendantcount=<n>", strprintf("Do not accept transactions if any ancestor would have <n> or more in-mempool descendants (default: %u)", DEFAULT_DESCENDANT_LIMIT));
417+
strUsage += HelpMessageOpt("-limitdescendantsize=<n>", strprintf("Do not accept transactions if any ancestor would have more than <n> kilobytes of in-mempool descendants (default: %u).", DEFAULT_DESCENDANT_SIZE_LIMIT));
414418
}
415419
string debugCategories = "addrman, alert, bench, coindb, db, lock, rand, rpc, selectcoins, mempool, mempoolrej, net, proxy, prune, http"; // Don't translate these and qt below
416420
if (mode == HMM_BITCOIN_QT)

src/main.cpp

Lines changed: 24 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -921,6 +921,17 @@ bool AcceptToMemoryPool(CTxMemPool& pool, CValidationState &state, const CTransa
921921
REJECT_HIGHFEE, "absurdly-high-fee",
922922
strprintf("%d > %d", nFees, ::minRelayTxFee.GetFee(nSize) * 10000));
923923

924+
// Calculate in-mempool ancestors, up to a limit.
925+
CTxMemPool::setEntries setAncestors;
926+
size_t nLimitAncestors = GetArg("-limitancestorcount", DEFAULT_ANCESTOR_LIMIT);
927+
size_t nLimitAncestorSize = GetArg("-limitancestorsize", DEFAULT_ANCESTOR_SIZE_LIMIT)*1000;
928+
size_t nLimitDescendants = GetArg("-limitdescendantcount", DEFAULT_DESCENDANT_LIMIT);
929+
size_t nLimitDescendantSize = GetArg("-limitdescendantsize", DEFAULT_DESCENDANT_SIZE_LIMIT)*1000;
930+
std::string errString;
931+
if (!pool.CalculateMemPoolAncestors(entry, setAncestors, nLimitAncestors, nLimitAncestorSize, nLimitDescendants, nLimitDescendantSize, errString)) {
932+
return state.DoS(0, false, REJECT_NONSTANDARD, "too-long-mempool-chain", false, errString);
933+
}
934+
924935
// Check against previous transactions
925936
// This is done last to help prevent CPU exhaustion denial-of-service attacks.
926937
if (!CheckInputs(tx, state, view, true, STANDARD_SCRIPT_VERIFY_FLAGS, true))
@@ -942,7 +953,7 @@ bool AcceptToMemoryPool(CTxMemPool& pool, CValidationState &state, const CTransa
942953
}
943954

944955
// Store transaction in memory
945-
pool.addUnchecked(hash, entry, !IsInitialBlockDownload());
956+
pool.addUnchecked(hash, entry, setAncestors, !IsInitialBlockDownload());
946957
}
947958

948959
SyncWithWallets(tx, NULL);
@@ -2033,13 +2044,23 @@ bool static DisconnectTip(CValidationState &state) {
20332044
if (!FlushStateToDisk(state, FLUSH_STATE_IF_NEEDED))
20342045
return false;
20352046
// Resurrect mempool transactions from the disconnected block.
2047+
std::vector<uint256> vHashUpdate;
20362048
BOOST_FOREACH(const CTransaction &tx, block.vtx) {
20372049
// ignore validation errors in resurrected transactions
20382050
list<CTransaction> removed;
20392051
CValidationState stateDummy;
2040-
if (tx.IsCoinBase() || !AcceptToMemoryPool(mempool, stateDummy, tx, false, NULL))
2052+
if (tx.IsCoinBase() || !AcceptToMemoryPool(mempool, stateDummy, tx, false, NULL)) {
20412053
mempool.remove(tx, removed, true);
2054+
} else if (mempool.exists(tx.GetHash())) {
2055+
vHashUpdate.push_back(tx.GetHash());
2056+
}
20422057
}
2058+
// AcceptToMemoryPool/addUnchecked all assume that new mempool entries have
2059+
// no in-mempool children, which is generally not true when adding
2060+
// previously-confirmed transactions back to the mempool.
2061+
// UpdateTransactionsFromBlock finds descendants of any transactions in this
2062+
// block that were added back and cleans up the mempool state.
2063+
mempool.UpdateTransactionsFromBlock(vHashUpdate);
20432064
mempool.removeCoinbaseSpends(pcoinsTip, pindexDelete->nHeight);
20442065
mempool.check(pcoinsTip);
20452066
// Update chainActive and related variables.
@@ -4258,7 +4279,7 @@ bool static ProcessMessage(CNode* pfrom, string strCommand, CDataStream& vRecv,
42584279
LogPrint("mempool", "AcceptToMemoryPool: peer=%d %s: accepted %s (poolsz %u)\n",
42594280
pfrom->id, pfrom->cleanSubVer,
42604281
tx.GetHash().ToString(),
4261-
mempool.mapTx.size());
4282+
mempool.size());
42624283

42634284
// Recursively process any orphan transactions that depended on this one
42644285
set<NodeId> setMisbehaving;

src/main.h

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,14 @@ struct CNodeStateStats;
4343
static const bool DEFAULT_ALERTS = true;
4444
/** Default for -maxorphantx, maximum number of orphan transactions kept in memory */
4545
static const unsigned int DEFAULT_MAX_ORPHAN_TRANSACTIONS = 100;
46+
/** Default for -limitancestorcount, max number of in-mempool ancestors */
47+
static const unsigned int DEFAULT_ANCESTOR_LIMIT = 100;
48+
/** Default for -limitancestorsize, maximum kilobytes of tx + all in-mempool ancestors */
49+
static const unsigned int DEFAULT_ANCESTOR_SIZE_LIMIT = 900;
50+
/** Default for -limitdescendantcount, max number of in-mempool descendants */
51+
static const unsigned int DEFAULT_DESCENDANT_LIMIT = 1000;
52+
/** Default for -limitdescendantsize, maximum kilobytes of in-mempool descendants */
53+
static const unsigned int DEFAULT_DESCENDANT_SIZE_LIMIT = 2500;
4654
/** The maximum size of a blk?????.dat file (since 0.8) */
4755
static const unsigned int MAX_BLOCKFILE_SIZE = 0x8000000; // 128 MiB
4856
/** The pre-allocation chunk size for blk?????.dat files (since 0.8) */

src/memusage.h

Lines changed: 15 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -74,18 +74,30 @@ static inline size_t DynamicUsage(const std::vector<X>& v)
7474
return MallocUsage(v.capacity() * sizeof(X));
7575
}
7676

77-
template<typename X>
78-
static inline size_t DynamicUsage(const std::set<X>& s)
77+
template<typename X, typename Y>
78+
static inline size_t DynamicUsage(const std::set<X, Y>& s)
7979
{
8080
return MallocUsage(sizeof(stl_tree_node<X>)) * s.size();
8181
}
8282

8383
template<typename X, typename Y>
84-
static inline size_t DynamicUsage(const std::map<X, Y>& m)
84+
static inline size_t IncrementalDynamicUsage(const std::set<X, Y>& s)
85+
{
86+
return MallocUsage(sizeof(stl_tree_node<X>));
87+
}
88+
89+
template<typename X, typename Y, typename Z>
90+
static inline size_t DynamicUsage(const std::map<X, Y, Z>& m)
8591
{
8692
return MallocUsage(sizeof(stl_tree_node<std::pair<const X, Y> >)) * m.size();
8793
}
8894

95+
template<typename X, typename Y, typename Z>
96+
static inline size_t IncrementalDynamicUsage(const std::map<X, Y, Z>& m)
97+
{
98+
return MallocUsage(sizeof(stl_tree_node<std::pair<const X, Y> >));
99+
}
100+
89101
// Boost data structures
90102

91103
template<typename X>

src/miner.cpp

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -158,10 +158,10 @@ CBlockTemplate* CreateNewBlock(const CScript& scriptPubKeyIn)
158158
// This vector will be sorted into a priority queue:
159159
vector<TxPriority> vecPriority;
160160
vecPriority.reserve(mempool.mapTx.size());
161-
for (map<uint256, CTxMemPoolEntry>::iterator mi = mempool.mapTx.begin();
161+
for (CTxMemPool::indexed_transaction_set::iterator mi = mempool.mapTx.begin();
162162
mi != mempool.mapTx.end(); ++mi)
163163
{
164-
const CTransaction& tx = mi->second.GetTx();
164+
const CTransaction& tx = mi->GetTx();
165165
if (tx.IsCoinBase() || !IsFinalTx(tx, nHeight, pblock->nTime))
166166
continue;
167167

@@ -196,7 +196,7 @@ CBlockTemplate* CreateNewBlock(const CScript& scriptPubKeyIn)
196196
}
197197
mapDependers[txin.prevout.hash].push_back(porphan);
198198
porphan->setDependsOn.insert(txin.prevout.hash);
199-
nTotalIn += mempool.mapTx[txin.prevout.hash].GetTx().vout[txin.prevout.n].nValue;
199+
nTotalIn += mempool.mapTx.find(txin.prevout.hash)->GetTx().vout[txin.prevout.n].nValue;
200200
continue;
201201
}
202202
const CCoins* coins = view.AccessCoins(txin.prevout.hash);
@@ -226,7 +226,7 @@ CBlockTemplate* CreateNewBlock(const CScript& scriptPubKeyIn)
226226
porphan->feeRate = feeRate;
227227
}
228228
else
229-
vecPriority.push_back(TxPriority(dPriority, feeRate, &mi->second.GetTx()));
229+
vecPriority.push_back(TxPriority(dPriority, feeRate, &(mi->GetTx())));
230230
}
231231

232232
// Collect transactions into block

src/rpcblockchain.cpp

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -181,17 +181,19 @@ UniValue mempoolToJSON(bool fVerbose = false)
181181
{
182182
LOCK(mempool.cs);
183183
UniValue o(UniValue::VOBJ);
184-
BOOST_FOREACH(const PAIRTYPE(uint256, CTxMemPoolEntry)& entry, mempool.mapTx)
184+
BOOST_FOREACH(const CTxMemPoolEntry& e, mempool.mapTx)
185185
{
186-
const uint256& hash = entry.first;
187-
const CTxMemPoolEntry& e = entry.second;
186+
const uint256& hash = e.GetTx().GetHash();
188187
UniValue info(UniValue::VOBJ);
189188
info.push_back(Pair("size", (int)e.GetTxSize()));
190189
info.push_back(Pair("fee", ValueFromAmount(e.GetFee())));
191190
info.push_back(Pair("time", e.GetTime()));
192191
info.push_back(Pair("height", (int)e.GetHeight()));
193192
info.push_back(Pair("startingpriority", e.GetPriority(e.GetHeight())));
194193
info.push_back(Pair("currentpriority", e.GetPriority(chainActive.Height())));
194+
info.push_back(Pair("descendantcount", e.GetCountWithDescendants()));
195+
info.push_back(Pair("descendantsize", e.GetSizeWithDescendants()));
196+
info.push_back(Pair("descendantfees", e.GetFeesWithDescendants()));
195197
const CTransaction& tx = e.GetTx();
196198
set<string> setDepends;
197199
BOOST_FOREACH(const CTxIn& txin, tx.vin)
@@ -246,6 +248,9 @@ UniValue getrawmempool(const UniValue& params, bool fHelp)
246248
" \"height\" : n, (numeric) block height when transaction entered pool\n"
247249
" \"startingpriority\" : n, (numeric) priority when transaction entered pool\n"
248250
" \"currentpriority\" : n, (numeric) transaction priority now\n"
251+
" \"descendantcount\" : n, (numeric) number of in-mempool descendant transactions (including this one)\n"
252+
" \"descendantsize\" : n, (numeric) size of in-mempool descendants (including this one)\n"
253+
" \"descendantfees\" : n, (numeric) fees of in-mempool descendants (including this one)\n"
249254
" \"depends\" : [ (array) unconfirmed transactions used as inputs for this transaction\n"
250255
" \"transactionid\", (string) parent transaction id\n"
251256
" ... ]\n"

0 commit comments

Comments
 (0)