mirror of
https://github.com/dashpay/dash.git
synced 2024-12-26 20:42:59 +01:00
Add a score index to the mempool.
The score index is meant to represent the order of priority for being included in a block for miners. Initially this is set to the transactions modified (by any feeDelta) fee rate. Index improvements and unit tests by sdaftuar.
This commit is contained in:
parent
c49d5bc9e6
commit
f3fe83673e
@ -190,6 +190,7 @@ UniValue mempoolToJSON(bool fVerbose = false)
|
|||||||
UniValue info(UniValue::VOBJ);
|
UniValue info(UniValue::VOBJ);
|
||||||
info.push_back(Pair("size", (int)e.GetTxSize()));
|
info.push_back(Pair("size", (int)e.GetTxSize()));
|
||||||
info.push_back(Pair("fee", ValueFromAmount(e.GetFee())));
|
info.push_back(Pair("fee", ValueFromAmount(e.GetFee())));
|
||||||
|
info.push_back(Pair("modifiedfee", ValueFromAmount(e.GetModifiedFee())));
|
||||||
info.push_back(Pair("time", e.GetTime()));
|
info.push_back(Pair("time", e.GetTime()));
|
||||||
info.push_back(Pair("height", (int)e.GetHeight()));
|
info.push_back(Pair("height", (int)e.GetHeight()));
|
||||||
info.push_back(Pair("startingpriority", e.GetPriority(e.GetHeight())));
|
info.push_back(Pair("startingpriority", e.GetPriority(e.GetHeight())));
|
||||||
@ -247,6 +248,7 @@ UniValue getrawmempool(const UniValue& params, bool fHelp)
|
|||||||
" \"transactionid\" : { (json object)\n"
|
" \"transactionid\" : { (json object)\n"
|
||||||
" \"size\" : n, (numeric) transaction size in bytes\n"
|
" \"size\" : n, (numeric) transaction size in bytes\n"
|
||||||
" \"fee\" : n, (numeric) transaction fee in " + CURRENCY_UNIT + "\n"
|
" \"fee\" : n, (numeric) transaction fee in " + CURRENCY_UNIT + "\n"
|
||||||
|
" \"modifiedfee\" : n, (numeric) transaction fee with fee deltas used for mining priority\n"
|
||||||
" \"time\" : n, (numeric) local time transaction entered pool in seconds since 1 Jan 1970 GMT\n"
|
" \"time\" : n, (numeric) local time transaction entered pool in seconds since 1 Jan 1970 GMT\n"
|
||||||
" \"height\" : n, (numeric) block height when transaction entered pool\n"
|
" \"height\" : n, (numeric) block height when transaction entered pool\n"
|
||||||
" \"startingpriority\" : n, (numeric) priority when transaction entered pool\n"
|
" \"startingpriority\" : n, (numeric) priority when transaction entered pool\n"
|
||||||
|
@ -102,12 +102,13 @@ BOOST_AUTO_TEST_CASE(MempoolRemoveTest)
|
|||||||
removed.clear();
|
removed.clear();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
template<int index>
|
||||||
void CheckSort(CTxMemPool &pool, std::vector<std::string> &sortedOrder)
|
void CheckSort(CTxMemPool &pool, std::vector<std::string> &sortedOrder)
|
||||||
{
|
{
|
||||||
BOOST_CHECK_EQUAL(pool.size(), sortedOrder.size());
|
BOOST_CHECK_EQUAL(pool.size(), sortedOrder.size());
|
||||||
CTxMemPool::indexed_transaction_set::nth_index<1>::type::iterator it = pool.mapTx.get<1>().begin();
|
typename CTxMemPool::indexed_transaction_set::nth_index<index>::type::iterator it = pool.mapTx.get<index>().begin();
|
||||||
int count=0;
|
int count=0;
|
||||||
for (; it != pool.mapTx.get<1>().end(); ++it, ++count) {
|
for (; it != pool.mapTx.get<index>().end(); ++it, ++count) {
|
||||||
BOOST_CHECK_EQUAL(it->GetTx().GetHash().ToString(), sortedOrder[count]);
|
BOOST_CHECK_EQUAL(it->GetTx().GetHash().ToString(), sortedOrder[count]);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -163,7 +164,7 @@ BOOST_AUTO_TEST_CASE(MempoolIndexingTest)
|
|||||||
sortedOrder[2] = tx1.GetHash().ToString(); // 10000
|
sortedOrder[2] = tx1.GetHash().ToString(); // 10000
|
||||||
sortedOrder[3] = tx4.GetHash().ToString(); // 15000
|
sortedOrder[3] = tx4.GetHash().ToString(); // 15000
|
||||||
sortedOrder[4] = tx2.GetHash().ToString(); // 20000
|
sortedOrder[4] = tx2.GetHash().ToString(); // 20000
|
||||||
CheckSort(pool, sortedOrder);
|
CheckSort<1>(pool, sortedOrder);
|
||||||
|
|
||||||
/* low fee but with high fee child */
|
/* low fee but with high fee child */
|
||||||
/* tx6 -> tx7 -> tx8, tx9 -> tx10 */
|
/* tx6 -> tx7 -> tx8, tx9 -> tx10 */
|
||||||
@ -175,7 +176,7 @@ BOOST_AUTO_TEST_CASE(MempoolIndexingTest)
|
|||||||
BOOST_CHECK_EQUAL(pool.size(), 6);
|
BOOST_CHECK_EQUAL(pool.size(), 6);
|
||||||
// Check that at this point, tx6 is sorted low
|
// Check that at this point, tx6 is sorted low
|
||||||
sortedOrder.insert(sortedOrder.begin(), tx6.GetHash().ToString());
|
sortedOrder.insert(sortedOrder.begin(), tx6.GetHash().ToString());
|
||||||
CheckSort(pool, sortedOrder);
|
CheckSort<1>(pool, sortedOrder);
|
||||||
|
|
||||||
CTxMemPool::setEntries setAncestors;
|
CTxMemPool::setEntries setAncestors;
|
||||||
setAncestors.insert(pool.mapTx.find(tx6.GetHash()));
|
setAncestors.insert(pool.mapTx.find(tx6.GetHash()));
|
||||||
@ -201,7 +202,7 @@ BOOST_AUTO_TEST_CASE(MempoolIndexingTest)
|
|||||||
sortedOrder.erase(sortedOrder.begin());
|
sortedOrder.erase(sortedOrder.begin());
|
||||||
sortedOrder.push_back(tx6.GetHash().ToString());
|
sortedOrder.push_back(tx6.GetHash().ToString());
|
||||||
sortedOrder.push_back(tx7.GetHash().ToString());
|
sortedOrder.push_back(tx7.GetHash().ToString());
|
||||||
CheckSort(pool, sortedOrder);
|
CheckSort<1>(pool, sortedOrder);
|
||||||
|
|
||||||
/* low fee child of tx7 */
|
/* low fee child of tx7 */
|
||||||
CMutableTransaction tx8 = CMutableTransaction();
|
CMutableTransaction tx8 = CMutableTransaction();
|
||||||
@ -216,7 +217,7 @@ BOOST_AUTO_TEST_CASE(MempoolIndexingTest)
|
|||||||
|
|
||||||
// Now tx8 should be sorted low, but tx6/tx both high
|
// Now tx8 should be sorted low, but tx6/tx both high
|
||||||
sortedOrder.insert(sortedOrder.begin(), tx8.GetHash().ToString());
|
sortedOrder.insert(sortedOrder.begin(), tx8.GetHash().ToString());
|
||||||
CheckSort(pool, sortedOrder);
|
CheckSort<1>(pool, sortedOrder);
|
||||||
|
|
||||||
/* low fee child of tx7 */
|
/* low fee child of tx7 */
|
||||||
CMutableTransaction tx9 = CMutableTransaction();
|
CMutableTransaction tx9 = CMutableTransaction();
|
||||||
@ -231,7 +232,7 @@ BOOST_AUTO_TEST_CASE(MempoolIndexingTest)
|
|||||||
// tx9 should be sorted low
|
// tx9 should be sorted low
|
||||||
BOOST_CHECK_EQUAL(pool.size(), 9);
|
BOOST_CHECK_EQUAL(pool.size(), 9);
|
||||||
sortedOrder.insert(sortedOrder.begin(), tx9.GetHash().ToString());
|
sortedOrder.insert(sortedOrder.begin(), tx9.GetHash().ToString());
|
||||||
CheckSort(pool, sortedOrder);
|
CheckSort<1>(pool, sortedOrder);
|
||||||
|
|
||||||
std::vector<std::string> snapshotOrder = sortedOrder;
|
std::vector<std::string> snapshotOrder = sortedOrder;
|
||||||
|
|
||||||
@ -273,7 +274,7 @@ BOOST_AUTO_TEST_CASE(MempoolIndexingTest)
|
|||||||
sortedOrder.insert(sortedOrder.begin()+5, tx9.GetHash().ToString());
|
sortedOrder.insert(sortedOrder.begin()+5, tx9.GetHash().ToString());
|
||||||
sortedOrder.insert(sortedOrder.begin()+6, tx8.GetHash().ToString());
|
sortedOrder.insert(sortedOrder.begin()+6, tx8.GetHash().ToString());
|
||||||
sortedOrder.insert(sortedOrder.begin()+7, tx10.GetHash().ToString()); // tx10 is just before tx6
|
sortedOrder.insert(sortedOrder.begin()+7, tx10.GetHash().ToString()); // tx10 is just before tx6
|
||||||
CheckSort(pool, sortedOrder);
|
CheckSort<1>(pool, sortedOrder);
|
||||||
|
|
||||||
// there should be 10 transactions in the mempool
|
// there should be 10 transactions in the mempool
|
||||||
BOOST_CHECK_EQUAL(pool.size(), 10);
|
BOOST_CHECK_EQUAL(pool.size(), 10);
|
||||||
@ -281,9 +282,42 @@ BOOST_AUTO_TEST_CASE(MempoolIndexingTest)
|
|||||||
// Now try removing tx10 and verify the sort order returns to normal
|
// Now try removing tx10 and verify the sort order returns to normal
|
||||||
std::list<CTransaction> removed;
|
std::list<CTransaction> removed;
|
||||||
pool.remove(pool.mapTx.find(tx10.GetHash())->GetTx(), removed, true);
|
pool.remove(pool.mapTx.find(tx10.GetHash())->GetTx(), removed, true);
|
||||||
CheckSort(pool, snapshotOrder);
|
CheckSort<1>(pool, snapshotOrder);
|
||||||
|
|
||||||
|
pool.remove(pool.mapTx.find(tx9.GetHash())->GetTx(), removed, true);
|
||||||
|
pool.remove(pool.mapTx.find(tx8.GetHash())->GetTx(), removed, true);
|
||||||
|
/* Now check the sort on the mining score index.
|
||||||
|
* Final order should be:
|
||||||
|
*
|
||||||
|
* tx7 (2M)
|
||||||
|
* tx2 (20k)
|
||||||
|
* tx4 (15000)
|
||||||
|
* tx1/tx5 (10000)
|
||||||
|
* tx3/6 (0)
|
||||||
|
* (Ties resolved by hash)
|
||||||
|
*/
|
||||||
|
sortedOrder.clear();
|
||||||
|
sortedOrder.push_back(tx7.GetHash().ToString());
|
||||||
|
sortedOrder.push_back(tx2.GetHash().ToString());
|
||||||
|
sortedOrder.push_back(tx4.GetHash().ToString());
|
||||||
|
if (tx1.GetHash() < tx5.GetHash()) {
|
||||||
|
sortedOrder.push_back(tx5.GetHash().ToString());
|
||||||
|
sortedOrder.push_back(tx1.GetHash().ToString());
|
||||||
|
} else {
|
||||||
|
sortedOrder.push_back(tx1.GetHash().ToString());
|
||||||
|
sortedOrder.push_back(tx5.GetHash().ToString());
|
||||||
|
}
|
||||||
|
if (tx3.GetHash() < tx6.GetHash()) {
|
||||||
|
sortedOrder.push_back(tx6.GetHash().ToString());
|
||||||
|
sortedOrder.push_back(tx3.GetHash().ToString());
|
||||||
|
} else {
|
||||||
|
sortedOrder.push_back(tx3.GetHash().ToString());
|
||||||
|
sortedOrder.push_back(tx6.GetHash().ToString());
|
||||||
|
}
|
||||||
|
CheckSort<3>(pool, sortedOrder);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
BOOST_AUTO_TEST_CASE(MempoolSizeLimitTest)
|
BOOST_AUTO_TEST_CASE(MempoolSizeLimitTest)
|
||||||
{
|
{
|
||||||
CTxMemPool pool(CFeeRate(1000));
|
CTxMemPool pool(CFeeRate(1000));
|
||||||
|
@ -36,6 +36,8 @@ CTxMemPoolEntry::CTxMemPoolEntry(const CTransaction& _tx, const CAmount& _nFee,
|
|||||||
nFeesWithDescendants = nFee;
|
nFeesWithDescendants = nFee;
|
||||||
CAmount nValueIn = tx.GetValueOut()+nFee;
|
CAmount nValueIn = tx.GetValueOut()+nFee;
|
||||||
assert(inChainInputValue <= nValueIn);
|
assert(inChainInputValue <= nValueIn);
|
||||||
|
|
||||||
|
feeDelta = 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
CTxMemPoolEntry::CTxMemPoolEntry(const CTxMemPoolEntry& other)
|
CTxMemPoolEntry::CTxMemPoolEntry(const CTxMemPoolEntry& other)
|
||||||
@ -53,6 +55,11 @@ CTxMemPoolEntry::GetPriority(unsigned int currentHeight) const
|
|||||||
return dResult;
|
return dResult;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void CTxMemPoolEntry::UpdateFeeDelta(int64_t newFeeDelta)
|
||||||
|
{
|
||||||
|
feeDelta = newFeeDelta;
|
||||||
|
}
|
||||||
|
|
||||||
// Update the given tx for any in-mempool descendants.
|
// Update the given tx for any in-mempool descendants.
|
||||||
// Assumes that setMemPoolChildren is correct for the given tx and all
|
// Assumes that setMemPoolChildren is correct for the given tx and all
|
||||||
// descendants.
|
// descendants.
|
||||||
@ -392,6 +399,15 @@ bool CTxMemPool::addUnchecked(const uint256& hash, const CTxMemPoolEntry &entry,
|
|||||||
}
|
}
|
||||||
UpdateAncestorsOf(true, newit, setAncestors);
|
UpdateAncestorsOf(true, newit, setAncestors);
|
||||||
|
|
||||||
|
// Update transaction's score for any feeDelta created by PrioritiseTransaction
|
||||||
|
std::map<uint256, std::pair<double, CAmount> >::const_iterator pos = mapDeltas.find(hash);
|
||||||
|
if (pos != mapDeltas.end()) {
|
||||||
|
const std::pair<double, CAmount> &deltas = pos->second;
|
||||||
|
if (deltas.second) {
|
||||||
|
mapTx.modify(newit, update_fee_delta(deltas.second));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
nTransactionsUpdated++;
|
nTransactionsUpdated++;
|
||||||
totalTxSize += entry.GetTxSize();
|
totalTxSize += entry.GetTxSize();
|
||||||
minerPolicyEstimator->processTransaction(entry, fCurrentEstimate);
|
minerPolicyEstimator->processTransaction(entry, fCurrentEstimate);
|
||||||
@ -769,6 +785,10 @@ void CTxMemPool::PrioritiseTransaction(const uint256 hash, const string strHash,
|
|||||||
std::pair<double, CAmount> &deltas = mapDeltas[hash];
|
std::pair<double, CAmount> &deltas = mapDeltas[hash];
|
||||||
deltas.first += dPriorityDelta;
|
deltas.first += dPriorityDelta;
|
||||||
deltas.second += nFeeDelta;
|
deltas.second += nFeeDelta;
|
||||||
|
txiter it = mapTx.find(hash);
|
||||||
|
if (it != mapTx.end()) {
|
||||||
|
mapTx.modify(it, update_fee_delta(deltas.second));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
LogPrintf("PrioritiseTransaction: %s priority += %f, fee += %d\n", strHash, dPriorityDelta, FormatMoney(nFeeDelta));
|
LogPrintf("PrioritiseTransaction: %s priority += %f, fee += %d\n", strHash, dPriorityDelta, FormatMoney(nFeeDelta));
|
||||||
}
|
}
|
||||||
@ -818,8 +838,8 @@ bool CCoinsViewMemPool::HaveCoins(const uint256 &txid) const {
|
|||||||
|
|
||||||
size_t CTxMemPool::DynamicMemoryUsage() const {
|
size_t CTxMemPool::DynamicMemoryUsage() const {
|
||||||
LOCK(cs);
|
LOCK(cs);
|
||||||
// Estimate the overhead of mapTx to be 9 pointers + an allocation, as no exact formula for boost::multi_index_contained is implemented.
|
// Estimate the overhead of mapTx to be 12 pointers + an allocation, as no exact formula for boost::multi_index_contained is implemented.
|
||||||
return memusage::MallocUsage(sizeof(CTxMemPoolEntry) + 9 * sizeof(void*)) * mapTx.size() + memusage::DynamicUsage(mapNextTx) + memusage::DynamicUsage(mapDeltas) + memusage::DynamicUsage(mapLinks) + cachedInnerUsage;
|
return memusage::MallocUsage(sizeof(CTxMemPoolEntry) + 12 * sizeof(void*)) * mapTx.size() + memusage::DynamicUsage(mapNextTx) + memusage::DynamicUsage(mapDeltas) + memusage::DynamicUsage(mapLinks) + cachedInnerUsage;
|
||||||
}
|
}
|
||||||
|
|
||||||
void CTxMemPool::RemoveStaged(setEntries &stage) {
|
void CTxMemPool::RemoveStaged(setEntries &stage) {
|
||||||
|
@ -69,6 +69,7 @@ private:
|
|||||||
CAmount inChainInputValue; //! Sum of all txin values that are already in blockchain
|
CAmount inChainInputValue; //! Sum of all txin values that are already in blockchain
|
||||||
bool spendsCoinbase; //! keep track of transactions that spend a coinbase
|
bool spendsCoinbase; //! keep track of transactions that spend a coinbase
|
||||||
unsigned int sigOpCount; //! Legacy sig ops plus P2SH sig op count
|
unsigned int sigOpCount; //! Legacy sig ops plus P2SH sig op count
|
||||||
|
int64_t feeDelta; //! Used for determining the priority of the transaction for mining in a block
|
||||||
|
|
||||||
// Information about descendants of this transaction that are in the
|
// Information about descendants of this transaction that are in the
|
||||||
// mempool; if we remove this transaction we must remove all of these
|
// mempool; if we remove this transaction we must remove all of these
|
||||||
@ -98,10 +99,13 @@ public:
|
|||||||
unsigned int GetHeight() const { return entryHeight; }
|
unsigned int GetHeight() const { return entryHeight; }
|
||||||
bool WasClearAtEntry() const { return hadNoDependencies; }
|
bool WasClearAtEntry() const { return hadNoDependencies; }
|
||||||
unsigned int GetSigOpCount() const { return sigOpCount; }
|
unsigned int GetSigOpCount() const { return sigOpCount; }
|
||||||
|
int64_t GetModifiedFee() const { return nFee + feeDelta; }
|
||||||
size_t DynamicMemoryUsage() const { return nUsageSize; }
|
size_t DynamicMemoryUsage() const { return nUsageSize; }
|
||||||
|
|
||||||
// Adjusts the descendant state, if this entry is not dirty.
|
// Adjusts the descendant state, if this entry is not dirty.
|
||||||
void UpdateState(int64_t modifySize, CAmount modifyFee, int64_t modifyCount);
|
void UpdateState(int64_t modifySize, CAmount modifyFee, int64_t modifyCount);
|
||||||
|
// Updates the fee delta used for mining priority score
|
||||||
|
void UpdateFeeDelta(int64_t feeDelta);
|
||||||
|
|
||||||
/** We can set the entry to be dirty if doing the full calculation of in-
|
/** We can set the entry to be dirty if doing the full calculation of in-
|
||||||
* mempool descendants will be too expensive, which can potentially happen
|
* mempool descendants will be too expensive, which can potentially happen
|
||||||
@ -139,6 +143,16 @@ struct set_dirty
|
|||||||
{ e.SetDirty(); }
|
{ e.SetDirty(); }
|
||||||
};
|
};
|
||||||
|
|
||||||
|
struct update_fee_delta
|
||||||
|
{
|
||||||
|
update_fee_delta(int64_t _feeDelta) : feeDelta(_feeDelta) { }
|
||||||
|
|
||||||
|
void operator() (CTxMemPoolEntry &e) { e.UpdateFeeDelta(feeDelta); }
|
||||||
|
|
||||||
|
private:
|
||||||
|
int64_t feeDelta;
|
||||||
|
};
|
||||||
|
|
||||||
// extracts a TxMemPoolEntry's transaction hash
|
// extracts a TxMemPoolEntry's transaction hash
|
||||||
struct mempoolentry_txid
|
struct mempoolentry_txid
|
||||||
{
|
{
|
||||||
@ -186,6 +200,24 @@ public:
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/** \class CompareTxMemPoolEntryByScore
|
||||||
|
*
|
||||||
|
* Sort by score of entry ((fee+delta)/size) in descending order
|
||||||
|
*/
|
||||||
|
class CompareTxMemPoolEntryByScore
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
bool operator()(const CTxMemPoolEntry& a, const CTxMemPoolEntry& b)
|
||||||
|
{
|
||||||
|
double f1 = (double)a.GetModifiedFee() * b.GetTxSize();
|
||||||
|
double f2 = (double)b.GetModifiedFee() * a.GetTxSize();
|
||||||
|
if (f1 == f2) {
|
||||||
|
return b.GetTx().GetHash() < a.GetTx().GetHash();
|
||||||
|
}
|
||||||
|
return f1 > f2;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
class CompareTxMemPoolEntryByEntryTime
|
class CompareTxMemPoolEntryByEntryTime
|
||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
@ -223,10 +255,11 @@ public:
|
|||||||
*
|
*
|
||||||
* CTxMemPool::mapTx, and CTxMemPoolEntry bookkeeping:
|
* CTxMemPool::mapTx, and CTxMemPoolEntry bookkeeping:
|
||||||
*
|
*
|
||||||
* mapTx is a boost::multi_index that sorts the mempool on 3 criteria:
|
* mapTx is a boost::multi_index that sorts the mempool on 4 criteria:
|
||||||
* - transaction hash
|
* - transaction hash
|
||||||
* - feerate [we use max(feerate of tx, feerate of tx with all descendants)]
|
* - feerate [we use max(feerate of tx, feerate of tx with all descendants)]
|
||||||
* - time in mempool
|
* - time in mempool
|
||||||
|
* - mining score (feerate modified by any fee deltas from PrioritiseTransaction)
|
||||||
*
|
*
|
||||||
* Note: the term "descendant" refers to in-mempool transactions that depend on
|
* Note: the term "descendant" refers to in-mempool transactions that depend on
|
||||||
* this one, while "ancestor" refers to in-mempool transactions that a given
|
* this one, while "ancestor" refers to in-mempool transactions that a given
|
||||||
@ -323,6 +356,11 @@ public:
|
|||||||
boost::multi_index::ordered_non_unique<
|
boost::multi_index::ordered_non_unique<
|
||||||
boost::multi_index::identity<CTxMemPoolEntry>,
|
boost::multi_index::identity<CTxMemPoolEntry>,
|
||||||
CompareTxMemPoolEntryByEntryTime
|
CompareTxMemPoolEntryByEntryTime
|
||||||
|
>,
|
||||||
|
// sorted by score (for mining prioritization)
|
||||||
|
boost::multi_index::ordered_unique<
|
||||||
|
boost::multi_index::identity<CTxMemPoolEntry>,
|
||||||
|
CompareTxMemPoolEntryByScore
|
||||||
>
|
>
|
||||||
>
|
>
|
||||||
> indexed_transaction_set;
|
> indexed_transaction_set;
|
||||||
|
Loading…
Reference in New Issue
Block a user