aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--doc/build-osx.md20
-rw-r--r--qa/replace-by-fee/.gitignore1
-rw-r--r--qa/replace-by-fee/README.md13
-rwxr-xr-xqa/replace-by-fee/rbf-tests.py360
-rw-r--r--src/coins.cpp14
-rw-r--r--src/coins.h13
-rw-r--r--src/main.cpp53
-rw-r--r--src/main.h2
-rw-r--r--src/miner.cpp301
-rw-r--r--src/rpcblockchain.cpp10
-rw-r--r--src/test/mempool_tests.cpp52
-rw-r--r--src/test/miner_tests.cpp60
-rw-r--r--src/test/scheduler_tests.cpp2
-rw-r--r--src/test/test_bitcoin.cpp2
-rw-r--r--src/test/test_bitcoin.h8
-rw-r--r--src/txmempool.cpp50
-rw-r--r--src/txmempool.h69
17 files changed, 401 insertions, 629 deletions
diff --git a/doc/build-osx.md b/doc/build-osx.md
index 02498e5c4b..c3cb1b7891 100644
--- a/doc/build-osx.md
+++ b/doc/build-osx.md
@@ -5,7 +5,7 @@ This guide will show you how to build bitcoind (headless client) for OS X.
Notes
-----
-* Tested on OS X 10.7 through 10.10 on 64-bit Intel processors only.
+* Tested on OS X 10.7 through 10.11 on 64-bit Intel processors only.
* All of the commands should be executed in a Terminal application. The
built-in one is located in `/Applications/Utilities`.
@@ -24,7 +24,7 @@ be re-done or updated every time Xcode is updated.
You will also need to install [Homebrew](http://brew.sh) in order to install library
dependencies.
-The installation of the actual dependencies is covered in the Instructions
+The installation of the actual dependencies is covered in the instructions
sections below.
Instructions: Homebrew
@@ -36,17 +36,19 @@ Instructions: Homebrew
NOTE: Building with Qt4 is still supported, however, could result in a broken UI. As such, building with Qt5 is recommended.
-### Building `bitcoind`
+### Building `bitcoin`
1. Clone the GitHub tree to get the source code and go into the directory.
git clone https://github.com/bitcoin/bitcoin.git
cd bitcoin
-2. Build bitcoind:
+2. Build bitcoin-core:
+ This will configure and build the headless bitcoin binaries as well as the gui (if Qt is found).
+ You can disable the gui build by passing `--without-gui` to configure.
./autogen.sh
- ./configure --with-gui=qt5
+ ./configure
make
3. It is also a good idea to build and run the unit tests:
@@ -60,10 +62,10 @@ NOTE: Building with Qt4 is still supported, however, could result in a broken UI
Use Qt Creator as IDE
------------------------
You can use Qt Creator as IDE, for debugging and for manipulating forms, etc.
-Download Qt Creator from http://www.qt.io/download/. Download the "community edition" and only install Qt Creator (uncheck the rest during the installation process).
+Download Qt Creator from https://www.qt.io/download/. Download the "community edition" and only install Qt Creator (uncheck the rest during the installation process).
1. Make sure you installed everything through Homebrew mentioned above
-2. Do a proper ./configure --with-gui=qt5 --enable-debug
+2. Do a proper ./configure --enable-debug
3. In Qt Creator do "New Project" -> Import Project -> Import Existing Project
4. Enter "bitcoin-qt" as project name, enter src/qt as location
5. Leave the file selection as it is
@@ -79,7 +81,7 @@ You can ignore this section if you are building `bitcoind` for your own use.
bitcoind/bitcoin-cli binaries are not included in the Bitcoin-Qt.app bundle.
-If you are building `bitcoind` or `Bitcoin-Qt` for others, your build machine should be set up
+If you are building `bitcoind` or `Bitcoin Core` for others, your build machine should be set up
as follows for maximum compatibility:
All dependencies should be compiled with these flags:
@@ -88,7 +90,7 @@ All dependencies should be compiled with these flags:
-arch x86_64
-isysroot $(xcode-select --print-path)/Platforms/MacOSX.platform/Developer/SDKs/MacOSX10.7.sdk
-Once dependencies are compiled, see [doc/release-process.md](release-process.md) for how the Bitcoin-Qt.app
+Once dependencies are compiled, see [doc/release-process.md](release-process.md) for how the Bitcoin Core
bundle is packaged and signed to create the .dmg disk image that is distributed.
Running
diff --git a/qa/replace-by-fee/.gitignore b/qa/replace-by-fee/.gitignore
deleted file mode 100644
index b2c4f4657a..0000000000
--- a/qa/replace-by-fee/.gitignore
+++ /dev/null
@@ -1 +0,0 @@
-python-bitcoinlib
diff --git a/qa/replace-by-fee/README.md b/qa/replace-by-fee/README.md
deleted file mode 100644
index baad86de9a..0000000000
--- a/qa/replace-by-fee/README.md
+++ /dev/null
@@ -1,13 +0,0 @@
-Replace-by-fee regression tests
-===============================
-
-First get version v0.5.0 of the python-bitcoinlib library. In this directory
-run:
-
- git clone -n https://github.com/petertodd/python-bitcoinlib
- (cd python-bitcoinlib && git checkout 8270bfd9c6ac37907d75db3d8b9152d61c7255cd)
-
-Then run the tests themselves with a bitcoind available running in regtest
-mode:
-
- ./rbf-tests.py
diff --git a/qa/replace-by-fee/rbf-tests.py b/qa/replace-by-fee/rbf-tests.py
deleted file mode 100755
index 1ee6c83875..0000000000
--- a/qa/replace-by-fee/rbf-tests.py
+++ /dev/null
@@ -1,360 +0,0 @@
-#!/usr/bin/env python3
-# Copyright (c) 2015 The Bitcoin Core developers
-# Distributed under the MIT software license, see the accompanying
-# file COPYING or http://www.opensource.org/licenses/mit-license.php.
-
-#
-# Test replace-by-fee
-#
-
-import os
-import sys
-
-# Add python-bitcoinlib to module search path, prior to any system-wide
-# python-bitcoinlib.
-sys.path.insert(0, os.path.join(os.path.dirname(os.path.abspath(__file__)), "python-bitcoinlib"))
-
-import unittest
-
-import bitcoin
-bitcoin.SelectParams('regtest')
-
-import bitcoin.rpc
-
-from bitcoin.core import *
-from bitcoin.core.script import *
-from bitcoin.wallet import *
-
-MAX_REPLACEMENT_LIMIT = 100
-
-class Test_ReplaceByFee(unittest.TestCase):
- proxy = None
-
- @classmethod
- def setUpClass(cls):
- if cls.proxy is None:
- cls.proxy = bitcoin.rpc.Proxy()
-
- @classmethod
- def mine_mempool(cls):
- """Mine until mempool is empty"""
- mempool_size = 1
- while mempool_size:
- cls.proxy.call('generate', 1)
- new_mempool_size = len(cls.proxy.getrawmempool())
-
- # It's possible to get stuck in a loop here if the mempool has
- # transactions that can't be mined.
- assert(new_mempool_size != mempool_size)
- mempool_size = new_mempool_size
-
- @classmethod
- def tearDownClass(cls):
- # Make sure mining works
- cls.mine_mempool()
-
- def make_txout(self, amount, confirmed=True, scriptPubKey=CScript([1])):
- """Create a txout with a given amount and scriptPubKey
-
- Mines coins as needed.
-
- confirmed - txouts created will be confirmed in the blockchain;
- unconfirmed otherwise.
- """
- fee = 1*COIN
- while self.proxy.getbalance() < amount + fee:
- self.proxy.call('generate', 100)
-
- addr = P2SHBitcoinAddress.from_redeemScript(CScript([]))
- txid = self.proxy.sendtoaddress(addr, amount + fee)
-
- tx1 = self.proxy.getrawtransaction(txid)
-
- i = None
- for i, txout in enumerate(tx1.vout):
- if txout.scriptPubKey == addr.to_scriptPubKey():
- break
- assert i is not None
-
- tx2 = CTransaction([CTxIn(COutPoint(txid, i), CScript([1, CScript([])]), nSequence=0)],
- [CTxOut(amount, scriptPubKey)])
-
- tx2_txid = self.proxy.sendrawtransaction(tx2, True)
-
- # If requested, ensure txouts are confirmed.
- if confirmed:
- self.mine_mempool()
-
- return COutPoint(tx2_txid, 0)
-
- def test_simple_doublespend(self):
- """Simple doublespend"""
- tx0_outpoint = self.make_txout(1.1*COIN)
-
- tx1a = CTransaction([CTxIn(tx0_outpoint, nSequence=0)],
- [CTxOut(1*COIN, CScript([b'a']))])
- tx1a_txid = self.proxy.sendrawtransaction(tx1a, True)
-
- # Should fail because we haven't changed the fee
- tx1b = CTransaction([CTxIn(tx0_outpoint, nSequence=0)],
- [CTxOut(1*COIN, CScript([b'b']))])
-
- try:
- tx1b_txid = self.proxy.sendrawtransaction(tx1b, True)
- except bitcoin.rpc.JSONRPCException as exp:
- self.assertEqual(exp.error['code'], -26) # insufficient fee
- else:
- self.fail()
-
- # Extra 0.1 BTC fee
- tx1b = CTransaction([CTxIn(tx0_outpoint, nSequence=0)],
- [CTxOut(0.9*COIN, CScript([b'b']))])
- tx1b_txid = self.proxy.sendrawtransaction(tx1b, True)
-
- # tx1a is in fact replaced
- with self.assertRaises(IndexError):
- self.proxy.getrawtransaction(tx1a_txid)
-
- self.assertEqual(tx1b, self.proxy.getrawtransaction(tx1b_txid))
-
- def test_doublespend_chain(self):
- """Doublespend of a long chain"""
-
- initial_nValue = 50*COIN
- tx0_outpoint = self.make_txout(initial_nValue)
-
- prevout = tx0_outpoint
- remaining_value = initial_nValue
- chain_txids = []
- while remaining_value > 10*COIN:
- remaining_value -= 1*COIN
- tx = CTransaction([CTxIn(prevout, nSequence=0)],
- [CTxOut(remaining_value, CScript([1]))])
- txid = self.proxy.sendrawtransaction(tx, True)
- chain_txids.append(txid)
- prevout = COutPoint(txid, 0)
-
- # Whether the double-spend is allowed is evaluated by including all
- # child fees - 40 BTC - so this attempt is rejected.
- dbl_tx = CTransaction([CTxIn(tx0_outpoint, nSequence=0)],
- [CTxOut(initial_nValue - 30*COIN, CScript([1]))])
-
- try:
- self.proxy.sendrawtransaction(dbl_tx, True)
- except bitcoin.rpc.JSONRPCException as exp:
- self.assertEqual(exp.error['code'], -26) # insufficient fee
- else:
- self.fail()
-
- # Accepted with sufficient fee
- dbl_tx = CTransaction([CTxIn(tx0_outpoint, nSequence=0)],
- [CTxOut(1*COIN, CScript([1]))])
- self.proxy.sendrawtransaction(dbl_tx, True)
-
- for doublespent_txid in chain_txids:
- with self.assertRaises(IndexError):
- self.proxy.getrawtransaction(doublespent_txid)
-
- def test_doublespend_tree(self):
- """Doublespend of a big tree of transactions"""
-
- initial_nValue = 50*COIN
- tx0_outpoint = self.make_txout(initial_nValue)
-
- def branch(prevout, initial_value, max_txs, *, tree_width=5, fee=0.0001*COIN, _total_txs=None):
- if _total_txs is None:
- _total_txs = [0]
- if _total_txs[0] >= max_txs:
- return
-
- txout_value = (initial_value - fee) // tree_width
- if txout_value < fee:
- return
-
- vout = [CTxOut(txout_value, CScript([i+1]))
- for i in range(tree_width)]
- tx = CTransaction([CTxIn(prevout, nSequence=0)],
- vout)
-
- self.assertTrue(len(tx.serialize()) < 100000)
- txid = self.proxy.sendrawtransaction(tx, True)
- yield tx
- _total_txs[0] += 1
-
- for i, txout in enumerate(tx.vout):
- yield from branch(COutPoint(txid, i), txout_value,
- max_txs,
- tree_width=tree_width, fee=fee,
- _total_txs=_total_txs)
-
- fee = 0.0001*COIN
- n = MAX_REPLACEMENT_LIMIT
- tree_txs = list(branch(tx0_outpoint, initial_nValue, n, fee=fee))
- self.assertEqual(len(tree_txs), n)
-
- # Attempt double-spend, will fail because too little fee paid
- dbl_tx = CTransaction([CTxIn(tx0_outpoint, nSequence=0)],
- [CTxOut(initial_nValue - fee*n, CScript([1]))])
- try:
- self.proxy.sendrawtransaction(dbl_tx, True)
- except bitcoin.rpc.JSONRPCException as exp:
- self.assertEqual(exp.error['code'], -26) # insufficient fee
- else:
- self.fail()
-
- # 1 BTC fee is enough
- dbl_tx = CTransaction([CTxIn(tx0_outpoint, nSequence=0)],
- [CTxOut(initial_nValue - fee*n - 1*COIN, CScript([1]))])
- self.proxy.sendrawtransaction(dbl_tx, True)
-
- for tx in tree_txs:
- with self.assertRaises(IndexError):
- self.proxy.getrawtransaction(tx.GetHash())
-
- # Try again, but with more total transactions than the "max txs
- # double-spent at once" anti-DoS limit.
- for n in (MAX_REPLACEMENT_LIMIT, MAX_REPLACEMENT_LIMIT*2):
- fee = 0.0001*COIN
- tx0_outpoint = self.make_txout(initial_nValue)
- tree_txs = list(branch(tx0_outpoint, initial_nValue, n, fee=fee))
- self.assertEqual(len(tree_txs), n)
-
- dbl_tx = CTransaction([CTxIn(tx0_outpoint, nSequence=0)],
- [CTxOut(initial_nValue - fee*n, CScript([1]))])
- try:
- self.proxy.sendrawtransaction(dbl_tx, True)
- except bitcoin.rpc.JSONRPCException as exp:
- self.assertEqual(exp.error['code'], -26)
- else:
- self.fail()
-
- for tx in tree_txs:
- self.proxy.getrawtransaction(tx.GetHash())
-
- def test_replacement_feeperkb(self):
- """Replacement requires fee-per-KB to be higher"""
- tx0_outpoint = self.make_txout(1.1*COIN)
-
- tx1a = CTransaction([CTxIn(tx0_outpoint, nSequence=0)],
- [CTxOut(1*COIN, CScript([b'a']))])
- tx1a_txid = self.proxy.sendrawtransaction(tx1a, True)
-
- # Higher fee, but the fee per KB is much lower, so the replacement is
- # rejected.
- tx1b = CTransaction([CTxIn(tx0_outpoint, nSequence=0)],
- [CTxOut(0.001*COIN,
- CScript([b'a'*999000]))])
-
- try:
- tx1b_txid = self.proxy.sendrawtransaction(tx1b, True)
- except bitcoin.rpc.JSONRPCException as exp:
- self.assertEqual(exp.error['code'], -26) # insufficient fee
- else:
- self.fail()
-
- def test_spends_of_conflicting_outputs(self):
- """Replacements that spend conflicting tx outputs are rejected"""
- utxo1 = self.make_txout(1.2*COIN)
- utxo2 = self.make_txout(3.0*COIN)
-
- tx1a = CTransaction([CTxIn(utxo1, nSequence=0)],
- [CTxOut(1.1*COIN, CScript([b'a']))])
- tx1a_txid = self.proxy.sendrawtransaction(tx1a, True)
-
- # Direct spend an output of the transaction we're replacing.
- tx2 = CTransaction([CTxIn(utxo1, nSequence=0), CTxIn(utxo2, nSequence=0),
- CTxIn(COutPoint(tx1a_txid, 0), nSequence=0)],
- tx1a.vout)
-
- try:
- tx2_txid = self.proxy.sendrawtransaction(tx2, True)
- except bitcoin.rpc.JSONRPCException as exp:
- self.assertEqual(exp.error['code'], -26)
- else:
- self.fail()
-
- # Spend tx1a's output to test the indirect case.
- tx1b = CTransaction([CTxIn(COutPoint(tx1a_txid, 0), nSequence=0)],
- [CTxOut(1.0*COIN, CScript([b'a']))])
- tx1b_txid = self.proxy.sendrawtransaction(tx1b, True)
-
- tx2 = CTransaction([CTxIn(utxo1, nSequence=0), CTxIn(utxo2, nSequence=0),
- CTxIn(COutPoint(tx1b_txid, 0))],
- tx1a.vout)
-
- try:
- tx2_txid = self.proxy.sendrawtransaction(tx2, True)
- except bitcoin.rpc.JSONRPCException as exp:
- self.assertEqual(exp.error['code'], -26)
- else:
- self.fail()
-
- def test_new_unconfirmed_inputs(self):
- """Replacements that add new unconfirmed inputs are rejected"""
- confirmed_utxo = self.make_txout(1.1*COIN)
- unconfirmed_utxo = self.make_txout(0.1*COIN, False)
-
- tx1 = CTransaction([CTxIn(confirmed_utxo)],
- [CTxOut(1.0*COIN, CScript([b'a']))])
- tx1_txid = self.proxy.sendrawtransaction(tx1, True)
-
- tx2 = CTransaction([CTxIn(confirmed_utxo), CTxIn(unconfirmed_utxo)],
- tx1.vout)
-
- try:
- tx2_txid = self.proxy.sendrawtransaction(tx2, True)
- except bitcoin.rpc.JSONRPCException as exp:
- self.assertEqual(exp.error['code'], -26)
- else:
- self.fail()
-
- def test_too_many_replacements(self):
- """Replacements that evict too many transactions are rejected"""
- # Try directly replacing more than MAX_REPLACEMENT_LIMIT
- # transactions
-
- # Start by creating a single transaction with many outputs
- initial_nValue = 10*COIN
- utxo = self.make_txout(initial_nValue)
- fee = 0.0001*COIN
- split_value = int((initial_nValue-fee)/(MAX_REPLACEMENT_LIMIT+1))
- actual_fee = initial_nValue - split_value*(MAX_REPLACEMENT_LIMIT+1)
-
- outputs = []
- for i in range(MAX_REPLACEMENT_LIMIT+1):
- outputs.append(CTxOut(split_value, CScript([1])))
-
- splitting_tx = CTransaction([CTxIn(utxo, nSequence=0)], outputs)
- txid = self.proxy.sendrawtransaction(splitting_tx, True)
-
- # Now spend each of those outputs individually
- for i in range(MAX_REPLACEMENT_LIMIT+1):
- tx_i = CTransaction([CTxIn(COutPoint(txid, i), nSequence=0)],
- [CTxOut(split_value-fee, CScript([b'a']))])
- self.proxy.sendrawtransaction(tx_i, True)
-
- # Now create doublespend of the whole lot, should fail
- # Need a big enough fee to cover all spending transactions and have
- # a higher fee rate
- double_spend_value = (split_value-100*fee)*(MAX_REPLACEMENT_LIMIT+1)
- inputs = []
- for i in range(MAX_REPLACEMENT_LIMIT+1):
- inputs.append(CTxIn(COutPoint(txid, i), nSequence=0))
- double_tx = CTransaction(inputs, [CTxOut(double_spend_value, CScript([b'a']))])
-
- try:
- self.proxy.sendrawtransaction(double_tx, True)
- except bitcoin.rpc.JSONRPCException as exp:
- self.assertEqual(exp.error['code'], -26)
- self.assertEqual("too many potential replacements" in exp.error['message'], True)
- else:
- self.fail()
-
- # If we remove an input, it should pass
- double_tx = CTransaction(inputs[0:-1],
- [CTxOut(double_spend_value, CScript([b'a']))])
-
- self.proxy.sendrawtransaction(double_tx, True)
-
-if __name__ == '__main__':
- unittest.main()
diff --git a/src/coins.cpp b/src/coins.cpp
index 723e114708..122bf4e48d 100644
--- a/src/coins.cpp
+++ b/src/coins.cpp
@@ -144,6 +144,11 @@ bool CCoinsViewCache::HaveCoins(const uint256 &txid) const {
return (it != cacheCoins.end() && !it->second.coins.vout.empty());
}
+bool CCoinsViewCache::HaveCoinsInCache(const uint256 &txid) const {
+ CCoinsMap::const_iterator it = cacheCoins.find(txid);
+ return it != cacheCoins.end();
+}
+
uint256 CCoinsViewCache::GetBestBlock() const {
if (hashBlock.IsNull())
hashBlock = base->GetBestBlock();
@@ -206,6 +211,15 @@ bool CCoinsViewCache::Flush() {
return fOk;
}
+void CCoinsViewCache::Uncache(const uint256& hash)
+{
+ CCoinsMap::iterator it = cacheCoins.find(hash);
+ if (it != cacheCoins.end() && it->second.flags == 0) {
+ cachedCoinsUsage -= it->second.coins.DynamicMemoryUsage();
+ cacheCoins.erase(it);
+ }
+}
+
unsigned int CCoinsViewCache::GetCacheSize() const {
return cacheCoins.size();
}
diff --git a/src/coins.h b/src/coins.h
index d174422100..60c1ba8a78 100644
--- a/src/coins.h
+++ b/src/coins.h
@@ -406,6 +406,13 @@ public:
bool BatchWrite(CCoinsMap &mapCoins, const uint256 &hashBlock);
/**
+ * Check if we have the given tx already loaded in this cache.
+ * The semantics are the same as HaveCoins(), but no calls to
+ * the backing CCoinsView are made.
+ */
+ bool HaveCoinsInCache(const uint256 &txid) const;
+
+ /**
* Return a pointer to CCoins in the cache, or NULL if not found. This is
* more efficient than GetCoins. Modifications to other cache entries are
* allowed while accessing the returned pointer.
@@ -437,6 +444,12 @@ public:
*/
bool Flush();
+ /**
+ * Removes the transaction with the given hash from the cache, if it is
+ * not modified.
+ */
+ void Uncache(const uint256 &txid);
+
//! Calculate the size of the cache (in number of transactions)
unsigned int GetCacheSize() const;
diff --git a/src/main.cpp b/src/main.cpp
index e3c77e8505..c41dd58d11 100644
--- a/src/main.cpp
+++ b/src/main.cpp
@@ -789,6 +789,17 @@ bool CheckTransaction(const CTransaction& tx, CValidationState &state)
return true;
}
+void LimitMempoolSize(CTxMemPool& pool, size_t limit, unsigned long age) {
+ int expired = pool.Expire(GetTime() - age);
+ if (expired != 0)
+ LogPrint("mempool", "Expired %i transactions from the memory pool\n", expired);
+
+ std::vector<uint256> vNoSpendsRemaining;
+ pool.TrimToSize(limit, &vNoSpendsRemaining);
+ BOOST_FOREACH(const uint256& removed, vNoSpendsRemaining)
+ pcoinsTip->Uncache(removed);
+}
+
CAmount GetMinRelayFee(const CTransaction& tx, const CTxMemPool& pool, unsigned int nBytes, bool fAllowFree)
{
uint256 hash = tx.GetHash();
@@ -816,7 +827,7 @@ CAmount GetMinRelayFee(const CTransaction& tx, const CTxMemPool& pool, unsigned
}
/** 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)",
state.GetRejectReason(),
@@ -824,8 +835,9 @@ static std::string FormatStateMessage(const CValidationState &state)
state.GetRejectCode());
}
-bool AcceptToMemoryPool(CTxMemPool& pool, CValidationState &state, const CTransaction &tx, bool fLimitFree,
- bool* pfMissingInputs, bool fOverrideMempoolLimit, bool fRejectAbsurdFee)
+bool AcceptToMemoryPoolWorker(CTxMemPool& pool, CValidationState &state, const CTransaction &tx, bool fLimitFree,
+ bool* pfMissingInputs, bool fOverrideMempoolLimit, bool fRejectAbsurdFee,
+ std::vector<uint256>& vHashTxnToUncache)
{
AssertLockHeld(cs_main);
if (pfMissingInputs)
@@ -906,13 +918,19 @@ bool AcceptToMemoryPool(CTxMemPool& pool, CValidationState &state, const CTransa
view.SetBackend(viewMemPool);
// do we already have it?
- if (view.HaveCoins(hash))
+ bool fHadTxInCache = pcoinsTip->HaveCoinsInCache(hash);
+ if (view.HaveCoins(hash)) {
+ if (!fHadTxInCache)
+ vHashTxnToUncache.push_back(hash);
return state.Invalid(false, REJECT_ALREADY_KNOWN, "txn-already-known");
+ }
// do all inputs exist?
// Note that this does not check for the presence of actual outputs (see the next check for that),
// and only helps with filling in pfMissingInputs (to determine missing vs spent).
BOOST_FOREACH(const CTxIn txin, tx.vin) {
+ if (!pcoinsTip->HaveCoinsInCache(txin.prevout.hash))
+ vHashTxnToUncache.push_back(txin.prevout.hash);
if (!view.HaveCoins(txin.prevout.hash)) {
if (pfMissingInputs)
*pfMissingInputs = true;
@@ -964,7 +982,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();
// Don't accept it if it can't get into a block
@@ -1210,12 +1228,8 @@ bool AcceptToMemoryPool(CTxMemPool& pool, CValidationState &state, const CTransa
// trim mempool and check if tx was trimmed
if (!fOverrideMempoolLimit) {
- int expired = pool.Expire(GetTime() - GetArg("-mempoolexpiry", DEFAULT_MEMPOOL_EXPIRY) * 60 * 60);
- if (expired != 0)
- LogPrint("mempool", "Expired %i transactions from the memory pool\n", expired);
-
- pool.TrimToSize(GetArg("-maxmempool", DEFAULT_MAX_MEMPOOL_SIZE) * 1000000);
- if (!pool.exists(tx.GetHash()))
+ LimitMempoolSize(pool, GetArg("-maxmempool", DEFAULT_MAX_MEMPOOL_SIZE) * 1000000, GetArg("-mempoolexpiry", DEFAULT_MEMPOOL_EXPIRY) * 60 * 60);
+ if (!pool.exists(hash))
return state.DoS(0, false, REJECT_INSUFFICIENTFEE, "mempool full");
}
}
@@ -1225,6 +1239,18 @@ bool AcceptToMemoryPool(CTxMemPool& pool, CValidationState &state, const CTransa
return true;
}
+bool AcceptToMemoryPool(CTxMemPool& pool, CValidationState &state, const CTransaction &tx, bool fLimitFree,
+ bool* pfMissingInputs, bool fOverrideMempoolLimit, bool fRejectAbsurdFee)
+{
+ std::vector<uint256> vHashTxToUncache;
+ bool res = AcceptToMemoryPoolWorker(pool, state, tx, fLimitFree, pfMissingInputs, fOverrideMempoolLimit, fRejectAbsurdFee, vHashTxToUncache);
+ if (!res) {
+ BOOST_FOREACH(const uint256& hashTx, vHashTxToUncache)
+ pcoinsTip->Uncache(hashTx);
+ }
+ return res;
+}
+
/** Return transaction in tx, and if it was found inside a block, its hash is placed in hashBlock */
bool GetTransaction(const uint256 &hash, CTransaction &txOut, const Consensus::Params& consensusParams, uint256 &hashBlock, bool fAllowSlow)
{
@@ -2571,7 +2597,7 @@ static bool ActivateBestChainStep(CValidationState& state, const CChainParams& c
if (fBlocksDisconnected) {
mempool.removeForReorg(pcoinsTip, chainActive.Tip()->nHeight + 1, STANDARD_LOCKTIME_VERIFY_FLAGS);
- mempool.TrimToSize(GetArg("-maxmempool", DEFAULT_MAX_MEMPOOL_SIZE) * 1000000);
+ LimitMempoolSize(mempool, GetArg("-maxmempool", DEFAULT_MAX_MEMPOOL_SIZE) * 1000000, GetArg("-mempoolexpiry", DEFAULT_MEMPOOL_EXPIRY) * 60 * 60);
}
mempool.check(pcoinsTip);
@@ -2686,7 +2712,7 @@ bool InvalidateBlock(CValidationState& state, const Consensus::Params& consensus
}
}
- mempool.TrimToSize(GetArg("-maxmempool", DEFAULT_MAX_MEMPOOL_SIZE) * 1000000);
+ LimitMempoolSize(mempool, GetArg("-maxmempool", DEFAULT_MAX_MEMPOOL_SIZE) * 1000000, GetArg("-mempoolexpiry", DEFAULT_MEMPOOL_EXPIRY) * 60 * 60);
// The resulting new best tip may not be in setBlockIndexCandidates anymore, so
// add it again.
@@ -4804,6 +4830,7 @@ bool static ProcessMessage(CNode* pfrom, string strCommand, CDataStream& vRecv,
if (nDoS > 0)
Misbehaving(pfrom->GetId(), nDoS);
}
+ FlushStateToDisk(state, FLUSH_STATE_PERIODIC);
}
diff --git a/src/main.h b/src/main.h
index 16dff28363..19623f4d96 100644
--- a/src/main.h
+++ b/src/main.h
@@ -257,6 +257,8 @@ void PruneAndFlush();
bool AcceptToMemoryPool(CTxMemPool& pool, CValidationState &state, const CTransaction &tx, bool fLimitFree,
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 {
int nMisbehavior;
diff --git a/src/miner.cpp b/src/miner.cpp
index 27a1fbcf80..c6db00d301 100644
--- a/src/miner.cpp
+++ b/src/miner.cpp
@@ -27,6 +27,7 @@
#include <boost/thread.hpp>
#include <boost/tuple/tuple.hpp>
+#include <queue>
using namespace std;
@@ -40,48 +41,18 @@ using namespace std;
// transactions in the memory pool. When we select transactions from the
// 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.
-// 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 nLastBlockSize = 0;
-// We want to sort transactions by priority and fee rate, so:
-typedef boost::tuple<double, CFeeRate, const CTransaction*> TxPriority;
-class TxPriorityCompare
+class ScoreCompare
{
- bool byFee;
-
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)
- {
- 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>();
- }
+ return CompareTxMemPoolEntryByScore()(*b,*a); // Convert to less than
}
};
@@ -141,6 +112,22 @@ CBlockTemplate* CreateNewBlock(const CChainParams& chainparams, const CScript& s
nBlockMinSize = std::min(nBlockMaxSize, nBlockMinSize);
// 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;
{
@@ -149,157 +136,102 @@ CBlockTemplate* CreateNewBlock(const CChainParams& chainparams, const CScript& s
const int nHeight = pindexPrev->nHeight + 1;
pblock->nTime = GetAdjustedTime();
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;
- porphan->feeRate = feeRate;
+ double dPriority = mi->GetPriority(nHeight);
+ CAmount dummy;
+ mempool.ApplyDeltas(mi->GetTx().GetHash(), dPriority, dummy);
+ vecPriority.push_back(TxCoinAgePriority(dPriority, mi));
}
- else
- vecPriority.push_back(TxPriority(dPriority, feeRate, &(mi->GetTx())));
+ std::make_heap(vecPriority.begin(), vecPriority.end(), pricomparer);
}
- // Collect transactions into block
- uint64_t nBlockSize = 1000;
- uint64_t nBlockTx = 0;
- int nBlockSigOps = 100;
- bool fSortedByFee = (nBlockPrioritySize <= 0);
+ CTxMemPool::indexed_transaction_set::nth_index<3>::type::iterator mi = mempool.mapTx.get<3>().begin();
+ CTxMemPool::txiter iter;
- TxPriorityCompare comparer(fSortedByFee);
- std::make_heap(vecPriority.begin(), vecPriority.end(), comparer);
-
- while (!vecPriority.empty())
+ while (mi != mempool.mapTx.get<3>().end() || !clearedTxs.empty())
{
- // Take highest priority transaction off the priority queue:
- double dPriority = vecPriority.front().get<0>();
- CFeeRate feeRate = vecPriority.front().get<1>();
- const CTransaction& tx = *(vecPriority.front().get<2>());
-
- std::pop_heap(vecPriority.begin(), vecPriority.end(), comparer);
- vecPriority.pop_back();
-
- // Size limits
- unsigned int nTxSize = ::GetSerializeSize(tx, SER_NETWORK, PROTOCOL_VERSION);
- if (nBlockSize + nTxSize >= nBlockMaxSize)
- continue;
+ bool priorityTx = false;
+ if (fPriorityBlock && !vecPriority.empty()) { // add a tx from priority queue to fill the blockprioritysize
+ priorityTx = true;
+ iter = vecPriority.front().second;
+ actualPriority = vecPriority.front().first;
+ std::pop_heap(vecPriority.begin(), vecPriority.end(), pricomparer);
+ vecPriority.pop_back();
+ }
+ else if (clearedTxs.empty()) { // add tx with next highest score
+ iter = mempool.mapTx.project<0>(mi);
+ mi++;
+ }
+ else { // try to add a previously postponed child tx
+ iter = clearedTxs.top();
+ clearedTxs.pop();
+ }
- // Legacy limits on sigOps:
- unsigned int nTxSigOps = GetLegacySigOpCount(tx);
- if (nBlockSigOps + nTxSigOps >= MAX_BLOCK_SIGOPS)
- continue;
+ if (inBlock.count(iter))
+ continue; // could have been added to the priorityBlock
- // Skip free transactions if we're past the minimum block size:
- 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;
+ const CTransaction& tx = iter->GetTx();
- // Prioritise by fee once past the priority size or we run out of high-priority
- // transactions:
- if (!fSortedByFee &&
- ((nBlockSize + nTxSize >= nBlockPrioritySize) || !AllowFree(dPriority)))
+ bool fOrphan = false;
+ BOOST_FOREACH(CTxMemPool::txiter parent, mempool.GetMemPoolParents(iter))
{
- fSortedByFee = true;
- comparer = TxPriorityCompare(fSortedByFee);
- std::make_heap(vecPriority.begin(), vecPriority.end(), comparer);
+ if (!inBlock.count(parent)) {
+ fOrphan = true;
+ break;
+ }
}
-
- if (!view.HaveInputs(tx))
+ if (fOrphan) {
+ if (priorityTx)
+ waitPriMap.insert(std::make_pair(iter,actualPriority));
+ else
+ waitSet.insert(iter);
continue;
+ }
- CAmount nTxFees = view.GetValueIn(tx)-tx.GetValueOut();
-
- nTxSigOps += GetP2SHSigOpCount(tx, view);
- if (nBlockSigOps + nTxSigOps >= MAX_BLOCK_SIGOPS)
+ unsigned int nTxSize = iter->GetTxSize();
+ if (fPriorityBlock &&
+ (nBlockSize + nTxSize >= nBlockPrioritySize || !AllowFree(actualPriority))) {
+ 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;
+ }
- // Note that flags: we don't want to set mempool/IsStandard()
- // 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))
+ if (!IsFinalTx(tx, nHeight, nLockTimeCutoff))
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
pblock->vtx.push_back(tx);
pblocktemplate->vTxFees.push_back(nTxFees);
@@ -311,31 +243,37 @@ CBlockTemplate* CreateNewBlock(const CChainParams& chainparams, const CScript& s
if (fPrintPriority)
{
+ double dPriority = iter->GetPriority(nHeight);
+ CAmount dummy;
+ mempool.ApplyDeltas(tx.GetHash(), dPriority, dummy);
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
- if (mapDependers.count(hash))
+ BOOST_FOREACH(CTxMemPool::txiter child, mempool.GetMemPoolChildren(iter))
{
- BOOST_FOREACH(COrphan* porphan, mapDependers[hash])
- {
- if (!porphan->setDependsOn.empty())
- {
- porphan->setDependsOn.erase(hash);
- if (porphan->setDependsOn.empty())
- {
- vecPriority.push_back(TxPriority(porphan->dPriority, porphan->feeRate, porphan->ptx));
- std::push_heap(vecPriority.begin(), vecPriority.end(), comparer);
- }
+ if (fPriorityBlock) {
+ 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);
+ }
+ }
+ else {
+ if (waitSet.count(child)) {
+ clearedTxs.push(child);
+ waitSet.erase(child);
}
}
}
}
-
nLastBlockTx = nBlockTx;
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.
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]);
CValidationState state;
- if (!TestBlockValidity(state, chainparams, *pblock, pindexPrev, false, false))
- throw std::runtime_error("CreateNewBlock(): TestBlockValidity failed");
+ if (!TestBlockValidity(state, chainparams, *pblock, pindexPrev, false, false)) {
+ throw std::runtime_error(strprintf("%s: TestBlockValidity failed: %s", __func__, FormatStateMessage(state)));
+ }
}
return pblocktemplate.release();
diff --git a/src/rpcblockchain.cpp b/src/rpcblockchain.cpp
index c872822759..ee04636ce8 100644
--- a/src/rpcblockchain.cpp
+++ b/src/rpcblockchain.cpp
@@ -190,6 +190,7 @@ UniValue mempoolToJSON(bool fVerbose = false)
UniValue info(UniValue::VOBJ);
info.push_back(Pair("size", (int)e.GetTxSize()));
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("height", (int)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"
" \"size\" : n, (numeric) transaction size in bytes\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"
" \"height\" : n, (numeric) block height when transaction entered pool\n"
" \"startingpriority\" : n, (numeric) priority when transaction entered pool\n"
@@ -795,9 +797,11 @@ UniValue getmempoolinfo(const UniValue& params, bool fHelp)
"\nReturns details on the active state of the TX memory pool.\n"
"\nResult:\n"
"{\n"
- " \"size\": xxxxx (numeric) Current tx count\n"
- " \"bytes\": xxxxx (numeric) Sum of all tx sizes\n"
- " \"usage\": xxxxx (numeric) Total memory usage for the mempool\n"
+ " \"size\": xxxxx, (numeric) Current tx count\n"
+ " \"bytes\": xxxxx, (numeric) Sum of all tx sizes\n"
+ " \"usage\": xxxxx, (numeric) Total memory usage for the mempool\n"
+ " \"maxmempool\": xxxxx, (numeric) Maximum memory usage for the mempool\n"
+ " \"mempoolminfee\": xxxxx (numeric) Minimum fee for tx to be accepted\n"
"}\n"
"\nExamples:\n"
+ HelpExampleCli("getmempoolinfo", "")
diff --git a/src/test/mempool_tests.cpp b/src/test/mempool_tests.cpp
index 896e1237ed..e9f7378f74 100644
--- a/src/test/mempool_tests.cpp
+++ b/src/test/mempool_tests.cpp
@@ -102,12 +102,13 @@ BOOST_AUTO_TEST_CASE(MempoolRemoveTest)
removed.clear();
}
+template<int index>
void CheckSort(CTxMemPool &pool, std::vector<std::string> &sortedOrder)
{
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;
- 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]);
}
}
@@ -163,7 +164,7 @@ BOOST_AUTO_TEST_CASE(MempoolIndexingTest)
sortedOrder[2] = tx1.GetHash().ToString(); // 10000
sortedOrder[3] = tx4.GetHash().ToString(); // 15000
sortedOrder[4] = tx2.GetHash().ToString(); // 20000
- CheckSort(pool, sortedOrder);
+ CheckSort<1>(pool, sortedOrder);
/* low fee but with high fee child */
/* tx6 -> tx7 -> tx8, tx9 -> tx10 */
@@ -175,7 +176,7 @@ BOOST_AUTO_TEST_CASE(MempoolIndexingTest)
BOOST_CHECK_EQUAL(pool.size(), 6);
// Check that at this point, tx6 is sorted low
sortedOrder.insert(sortedOrder.begin(), tx6.GetHash().ToString());
- CheckSort(pool, sortedOrder);
+ CheckSort<1>(pool, sortedOrder);
CTxMemPool::setEntries setAncestors;
setAncestors.insert(pool.mapTx.find(tx6.GetHash()));
@@ -201,7 +202,7 @@ BOOST_AUTO_TEST_CASE(MempoolIndexingTest)
sortedOrder.erase(sortedOrder.begin());
sortedOrder.push_back(tx6.GetHash().ToString());
sortedOrder.push_back(tx7.GetHash().ToString());
- CheckSort(pool, sortedOrder);
+ CheckSort<1>(pool, sortedOrder);
/* low fee child of tx7 */
CMutableTransaction tx8 = CMutableTransaction();
@@ -216,7 +217,7 @@ BOOST_AUTO_TEST_CASE(MempoolIndexingTest)
// Now tx8 should be sorted low, but tx6/tx both high
sortedOrder.insert(sortedOrder.begin(), tx8.GetHash().ToString());
- CheckSort(pool, sortedOrder);
+ CheckSort<1>(pool, sortedOrder);
/* low fee child of tx7 */
CMutableTransaction tx9 = CMutableTransaction();
@@ -231,7 +232,7 @@ BOOST_AUTO_TEST_CASE(MempoolIndexingTest)
// tx9 should be sorted low
BOOST_CHECK_EQUAL(pool.size(), 9);
sortedOrder.insert(sortedOrder.begin(), tx9.GetHash().ToString());
- CheckSort(pool, sortedOrder);
+ CheckSort<1>(pool, 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()+6, tx8.GetHash().ToString());
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
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
std::list<CTransaction> removed;
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)
{
CTxMemPool pool(CFeeRate(1000));
diff --git a/src/test/miner_tests.cpp b/src/test/miner_tests.cpp
index 531cd59d5a..19ddb5b79c 100644
--- a/src/test/miner_tests.cpp
+++ b/src/test/miner_tests.cpp
@@ -120,7 +120,22 @@ BOOST_AUTO_TEST_CASE(CreateNewBlock_validity)
tx.vout[0].nValue -= 1000000;
hash = tx.GetHash();
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;
}
BOOST_CHECK(pblocktemplate = CreateNewBlock(chainparams, scriptPubKey));
@@ -141,18 +156,17 @@ BOOST_AUTO_TEST_CASE(CreateNewBlock_validity)
tx.vout[0].nValue -= 10000000;
hash = tx.GetHash();
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;
}
BOOST_CHECK(pblocktemplate = CreateNewBlock(chainparams, scriptPubKey));
delete pblocktemplate;
mempool.clear();
- // orphan in mempool
+ // orphan in mempool, template creation fails
hash = tx.GetHash();
- mempool.addUnchecked(hash, entry.Time(GetTime()).FromTx(tx));
- BOOST_CHECK(pblocktemplate = CreateNewBlock(chainparams, scriptPubKey));
- delete pblocktemplate;
+ mempool.addUnchecked(hash, entry.Fee(1000000).Time(GetTime()).FromTx(tx));
+ BOOST_CHECK_THROW(CreateNewBlock(chainparams, scriptPubKey), std::runtime_error);
mempool.clear();
// 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.vout[0].nValue = 4900000000LL;
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.resize(2);
tx.vin[1].scriptSig = CScript() << OP_1;
@@ -168,23 +182,23 @@ BOOST_AUTO_TEST_CASE(CreateNewBlock_validity)
tx.vin[1].prevout.n = 0;
tx.vout[0].nValue = 5900000000LL;
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));
delete pblocktemplate;
mempool.clear();
- // coinbase in mempool
+ // coinbase in mempool, template creation fails
tx.vin.resize(1);
tx.vin[0].prevout.SetNull();
tx.vin[0].scriptSig = CScript() << OP_0 << OP_1;
tx.vout[0].nValue = 0;
hash = tx.GetHash();
- mempool.addUnchecked(hash, entry.Time(GetTime()).SpendsCoinbase(false).FromTx(tx));
- BOOST_CHECK(pblocktemplate = CreateNewBlock(chainparams, scriptPubKey));
- delete pblocktemplate;
+ // give it a fee so it'll get mined
+ mempool.addUnchecked(hash, entry.Fee(100000).Time(GetTime()).SpendsCoinbase(false).FromTx(tx));
+ BOOST_CHECK_THROW(CreateNewBlock(chainparams, scriptPubKey), std::runtime_error);
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.n = 0;
tx.vin[0].scriptSig = CScript() << OP_1;
@@ -192,28 +206,26 @@ BOOST_AUTO_TEST_CASE(CreateNewBlock_validity)
script = CScript() << OP_0;
tx.vout[0].scriptPubKey = GetScriptForDestination(CScriptID(script));
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].scriptSig = CScript() << std::vector<unsigned char>(script.begin(), script.end());
tx.vout[0].nValue -= 1000000;
hash = tx.GetHash();
- mempool.addUnchecked(hash, entry.Time(GetTime()).SpendsCoinbase(false).FromTx(tx));
- BOOST_CHECK(pblocktemplate = CreateNewBlock(chainparams, scriptPubKey));
- delete pblocktemplate;
+ mempool.addUnchecked(hash, entry.Fee(1000000).Time(GetTime()).SpendsCoinbase(false).FromTx(tx));
+ BOOST_CHECK_THROW(CreateNewBlock(chainparams, scriptPubKey), std::runtime_error);
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].scriptSig = CScript() << OP_1;
tx.vout[0].nValue = 4900000000LL;
tx.vout[0].scriptPubKey = CScript() << OP_1;
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;
hash = tx.GetHash();
- mempool.addUnchecked(hash, entry.Time(GetTime()).SpendsCoinbase(true).FromTx(tx));
- BOOST_CHECK(pblocktemplate = CreateNewBlock(chainparams, scriptPubKey));
- delete pblocktemplate;
+ mempool.addUnchecked(hash, entry.Fee(100000000L).Time(GetTime()).SpendsCoinbase(true).FromTx(tx));
+ BOOST_CHECK_THROW(CreateNewBlock(chainparams, scriptPubKey), std::runtime_error);
mempool.clear();
// subsidy changing
@@ -237,7 +249,7 @@ BOOST_AUTO_TEST_CASE(CreateNewBlock_validity)
tx.vout[0].scriptPubKey = CScript() << OP_1;
tx.nLockTime = chainActive.Tip()->nHeight+1;
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));
// time locked
@@ -251,7 +263,7 @@ BOOST_AUTO_TEST_CASE(CreateNewBlock_validity)
tx2.vout[0].scriptPubKey = CScript() << OP_1;
tx2.nLockTime = chainActive.Tip()->GetMedianTimePast()+1;
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(pblocktemplate = CreateNewBlock(chainparams, scriptPubKey));
diff --git a/src/test/scheduler_tests.cpp b/src/test/scheduler_tests.cpp
index cb1a427db0..fc07aa72c1 100644
--- a/src/test/scheduler_tests.cpp
+++ b/src/test/scheduler_tests.cpp
@@ -40,6 +40,7 @@ static void MicroSleep(uint64_t n)
#endif
}
+#if 0 /* Disabled for now because there is a race condition issue in this test - see #6540 */
BOOST_AUTO_TEST_CASE(manythreads)
{
seed_insecure_rand(false);
@@ -115,5 +116,6 @@ BOOST_AUTO_TEST_CASE(manythreads)
}
BOOST_CHECK_EQUAL(counterSum, 200);
}
+#endif
BOOST_AUTO_TEST_SUITE_END()
diff --git a/src/test/test_bitcoin.cpp b/src/test/test_bitcoin.cpp
index 9645c7c942..2147dbb065 100644
--- a/src/test/test_bitcoin.cpp
+++ b/src/test/test_bitcoin.cpp
@@ -150,7 +150,7 @@ CTxMemPoolEntry TestMemPoolEntryHelper::FromTx(CMutableTransaction &tx, CTxMemPo
CAmount inChainValue = hasNoDependencies ? txn.GetValueOut() : 0;
return CTxMemPoolEntry(txn, nFee, nTime, dPriority, nHeight,
- hasNoDependencies, inChainValue, spendsCoinbase);
+ hasNoDependencies, inChainValue, spendsCoinbase, sigOpCount);
}
void Shutdown(void* parg)
diff --git a/src/test/test_bitcoin.h b/src/test/test_bitcoin.h
index 343c27673c..273bfdd7f4 100644
--- a/src/test/test_bitcoin.h
+++ b/src/test/test_bitcoin.h
@@ -66,11 +66,12 @@ struct TestMemPoolEntryHelper
unsigned int nHeight;
bool hadNoDependencies;
bool spendsCoinbase;
-
+ unsigned int sigOpCount;
+
TestMemPoolEntryHelper() :
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);
// Change the default value
@@ -80,5 +81,6 @@ struct TestMemPoolEntryHelper
TestMemPoolEntryHelper &Height(unsigned int _height) { nHeight = _height; return *this; }
TestMemPoolEntryHelper &HadNoDependencies(bool _hnd) { hadNoDependencies = _hnd; return *this; }
TestMemPoolEntryHelper &SpendsCoinbase(bool _flag) { spendsCoinbase = _flag; return *this; }
+ TestMemPoolEntryHelper &SigOps(unsigned int _sigops) { sigOpCount = _sigops; return *this; }
};
#endif
diff --git a/src/txmempool.cpp b/src/txmempool.cpp
index 9d25139481..fea5da8029 100644
--- a/src/txmempool.cpp
+++ b/src/txmempool.cpp
@@ -22,10 +22,10 @@ using namespace std;
CTxMemPoolEntry::CTxMemPoolEntry(const CTransaction& _tx, const CAmount& _nFee,
int64_t _nTime, double _entryPriority, unsigned int _entryHeight,
bool poolHasNoInputsOf, CAmount _inChainInputValue,
- bool _spendsCoinbase):
+ bool _spendsCoinbase, unsigned int _sigOps):
tx(_tx), nFee(_nFee), nTime(_nTime), entryPriority(_entryPriority), entryHeight(_entryHeight),
hadNoDependencies(poolHasNoInputsOf), inChainInputValue(_inChainInputValue),
- spendsCoinbase(_spendsCoinbase)
+ spendsCoinbase(_spendsCoinbase), sigOpCount(_sigOps)
{
nTxSize = ::GetSerializeSize(tx, SER_NETWORK, PROTOCOL_VERSION);
nModSize = tx.CalculateModifiedSize(nTxSize);
@@ -36,6 +36,8 @@ CTxMemPoolEntry::CTxMemPoolEntry(const CTransaction& _tx, const CAmount& _nFee,
nFeesWithDescendants = nFee;
CAmount nValueIn = tx.GetValueOut()+nFee;
assert(inChainInputValue <= nValueIn);
+
+ feeDelta = 0;
}
CTxMemPoolEntry::CTxMemPoolEntry(const CTxMemPoolEntry& other)
@@ -53,6 +55,11 @@ CTxMemPoolEntry::GetPriority(unsigned int currentHeight) const
return dResult;
}
+void CTxMemPoolEntry::UpdateFeeDelta(int64_t newFeeDelta)
+{
+ feeDelta = newFeeDelta;
+}
+
// Update the given tx for any in-mempool descendants.
// Assumes that setMemPoolChildren is correct for the given tx and all
// descendants.
@@ -392,6 +399,15 @@ bool CTxMemPool::addUnchecked(const uint256& hash, const CTxMemPoolEntry &entry,
}
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++;
totalTxSize += entry.GetTxSize();
minerPolicyEstimator->processTransaction(entry, fCurrentEstimate);
@@ -769,6 +785,10 @@ void CTxMemPool::PrioritiseTransaction(const uint256 hash, const string strHash,
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(deltas.second));
+ }
}
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 {
LOCK(cs);
- // Estimate the overhead of mapTx to be 9 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;
+ // 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) + 12 * sizeof(void*)) * mapTx.size() + memusage::DynamicUsage(mapNextTx) + memusage::DynamicUsage(mapDeltas) + memusage::DynamicUsage(mapLinks) + cachedInnerUsage;
}
void CTxMemPool::RemoveStaged(setEntries &stage) {
@@ -924,7 +944,7 @@ void CTxMemPool::trackPackageRemoved(const CFeeRate& rate) {
}
}
-void CTxMemPool::TrimToSize(size_t sizelimit) {
+void CTxMemPool::TrimToSize(size_t sizelimit, std::vector<uint256>* pvNoSpendsRemaining) {
LOCK(cs);
unsigned nTxnRemoved = 0;
@@ -943,8 +963,26 @@ void CTxMemPool::TrimToSize(size_t sizelimit) {
setEntries stage;
CalculateDescendants(mapTx.project<0>(it), stage);
- RemoveStaged(stage);
nTxnRemoved += stage.size();
+
+ std::vector<CTransaction> txn;
+ if (pvNoSpendsRemaining) {
+ txn.reserve(stage.size());
+ BOOST_FOREACH(txiter it, stage)
+ txn.push_back(it->GetTx());
+ }
+ RemoveStaged(stage);
+ if (pvNoSpendsRemaining) {
+ BOOST_FOREACH(const CTransaction& tx, txn) {
+ BOOST_FOREACH(const CTxIn& txin, tx.vin) {
+ if (exists(txin.prevout.hash))
+ continue;
+ std::map<COutPoint, CInPoint>::iterator it = mapNextTx.lower_bound(COutPoint(txin.prevout.hash, 0));
+ if (it == mapNextTx.end() || it->first.hash != txin.prevout.hash)
+ pvNoSpendsRemaining->push_back(txin.prevout.hash);
+ }
+ }
+ }
}
if (maxFeeRateRemoved > CFeeRate(0))
diff --git a/src/txmempool.h b/src/txmempool.h
index c4ea51557c..9203171868 100644
--- a/src/txmempool.h
+++ b/src/txmempool.h
@@ -68,6 +68,8 @@ private:
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
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
// mempool; if we remove this transaction we must remove all of these
@@ -81,7 +83,8 @@ private:
public:
CTxMemPoolEntry(const CTransaction& _tx, const CAmount& _nFee,
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);
const CTransaction& GetTx() const { return this->tx; }
@@ -95,10 +98,14 @@ public:
int64_t GetTime() const { return nTime; }
unsigned int GetHeight() const { return entryHeight; }
bool WasClearAtEntry() const { return hadNoDependencies; }
+ unsigned int GetSigOpCount() const { return sigOpCount; }
+ int64_t GetModifiedFee() const { return nFee + feeDelta; }
size_t DynamicMemoryUsage() const { return nUsageSize; }
// Adjusts the descendant state, if this entry is not dirty.
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-
* mempool descendants will be too expensive, which can potentially happen
@@ -136,6 +143,16 @@ struct set_dirty
{ 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
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
{
public:
@@ -220,10 +255,11 @@ public:
*
* 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
* - feerate [we use max(feerate of tx, feerate of tx with all descendants)]
* - 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
* 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::identity<CTxMemPoolEntry>,
CompareTxMemPoolEntryByEntryTime
+ >,
+ // sorted by score (for mining prioritization)
+ boost::multi_index::ordered_unique<
+ boost::multi_index::identity<CTxMemPoolEntry>,
+ CompareTxMemPoolEntryByScore
>
>
> indexed_transaction_set;
@@ -334,6 +375,8 @@ public:
};
typedef std::set<txiter, CompareIteratorByHash> setEntries;
+ const setEntries & GetMemPoolParents(txiter entry) const;
+ const setEntries & GetMemPoolChildren(txiter entry) const;
private:
typedef std::map<txiter, setEntries, CompareIteratorByHash> cacheMap;
@@ -345,8 +388,6 @@ private:
typedef std::map<txiter, TxLinks, CompareIteratorByHash> txlinksMap;
txlinksMap mapLinks;
- const setEntries & GetMemPoolParents(txiter entry) const;
- const setEntries & GetMemPoolChildren(txiter entry) const;
void UpdateParent(txiter entry, txiter parent, bool add);
void UpdateChild(txiter entry, txiter child, bool add);
@@ -442,8 +483,11 @@ public:
*/
CFeeRate GetMinFee(size_t sizelimit) const;
- /** Remove transactions from the mempool until its dynamic size is <= sizelimit. */
- void TrimToSize(size_t sizelimit);
+ /** Remove transactions from the mempool until its dynamic size is <= sizelimit.
+ * pvNoSpendsRemaining, if set, will be populated with the list of transactions
+ * which are not in mempool which no longer have any spends in this mempool.
+ */
+ void TrimToSize(size_t sizelimit, std::vector<uint256>* pvNoSpendsRemaining=NULL);
/** Expire all transaction (and their dependencies) in the mempool older than time. Return the number of removed transactions. */
int Expire(int64_t time);
@@ -546,4 +590,17 @@ public:
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