Skip to content

Commit

Permalink
Fix mempool limiting for PrioritiseTransaction
Browse files Browse the repository at this point in the history
Redo the feerate index to be based on mining score, rather than fee.

Update mempool_packages.py to test prioritisetransaction's effect on
package scores.

Coming from btc@eb306664e786ae43d539fde66f0fbe2a3e89d910
  • Loading branch information
furszy committed Jan 18, 2021
1 parent 1fa40ac commit 8325bb4
Show file tree
Hide file tree
Showing 4 changed files with 76 additions and 44 deletions.
4 changes: 2 additions & 2 deletions src/rpc/blockchain.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -399,7 +399,7 @@ UniValue mempoolToJSON(bool fVerbose = false)
info.pushKV("currentpriority", e.GetPriority(chainActive.Height()));
info.pushKV("descendantcount", e.GetCountWithDescendants());
info.pushKV("descendantsize", e.GetSizeWithDescendants());
info.pushKV("descendantfees", e.GetFeesWithDescendants());
info.pushKV("descendantfees", e.GetModFeesWithDescendants());
const CTransaction& tx = e.GetTx();
std::set<std::string> setDepends;
for (const CTxIn& txin : tx.vin) {
Expand Down Expand Up @@ -456,7 +456,7 @@ UniValue getrawmempool(const JSONRPCRequest& request)
" \"currentpriority\" : n, (numeric) transaction priority now\n"
" \"descendantcount\" : n, (numeric) number of in-mempool descendant transactions (including this one)\n"
" \"descendantsize\" : n, (numeric) size of in-mempool descendants (including this one)\n"
" \"descendantfees\" : n, (numeric) fees of in-mempool descendants (including this one)\n"
" \"descendantfees\" : n, (numeric) modified fees (see above) of in-mempool descendants (including this one)\n"
" \"depends\" : [ (array) unconfirmed transactions used as inputs for this transaction\n"
" \"transactionid\", (string) parent transaction id\n"
" ... ]\n"
Expand Down
49 changes: 28 additions & 21 deletions src/txmempool.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ CTxMemPoolEntry::CTxMemPoolEntry(const CTransactionRef& _tx, const CAmount& _nFe

nCountWithDescendants = 1;
nSizeWithDescendants = nTxSize;
nFeesWithDescendants = nFee;
nModFeesWithDescendants = nFee;
CAmount nValueIn = _tx->GetValueOut()+nFee;
assert(inChainInputValue <= nValueIn);

Expand All @@ -57,6 +57,7 @@ CTxMemPoolEntry::GetPriority(unsigned int currentHeight) const

void CTxMemPoolEntry::UpdateFeeDelta(int64_t newFeeDelta)
{
nModFeesWithDescendants += newFeeDelta - feeDelta;
feeDelta = newFeeDelta;
}

Expand Down Expand Up @@ -95,7 +96,7 @@ void CTxMemPool::UpdateForDescendants(txiter updateIt, cacheMap &cachedDescendan
for (const txiter& cit : setAllDescendants) {
if (!setExclude.count(cit->GetTx().GetHash())) {
modifySize += cit->GetTxSize();
modifyFee += cit->GetFee();
modifyFee += cit->GetModifiedFee();
modifyCount++;
cachedDescendants[updateIt].insert(cit);
}
Expand Down Expand Up @@ -221,7 +222,7 @@ void CTxMemPool::UpdateAncestorsOf(bool add, txiter it, setEntries &setAncestors
}
const int64_t updateCount = (add ? 1 : -1);
const int64_t updateSize = updateCount * it->GetTxSize();
const CAmount updateFee = updateCount * it->GetFee();
const CAmount updateFee = updateCount * it->GetModifiedFee();
for (const txiter& ancestorIt : setAncestors) {
mapTx.modify(ancestorIt, update_descendant_state(updateSize, updateFee, updateCount));
}
Expand Down Expand Up @@ -281,8 +282,7 @@ void CTxMemPoolEntry::UpdateState(int64_t modifySize, CAmount modifyFee, int64_t
{
nSizeWithDescendants += modifySize;
assert(int64_t(nSizeWithDescendants) > 0);
nFeesWithDescendants += modifyFee;
assert(nFeesWithDescendants >= 0);
nModFeesWithDescendants += modifyFee;
nCountWithDescendants += modifyCount;
assert(int64_t(nCountWithDescendants) > 0);
}
Expand Down Expand Up @@ -334,6 +334,17 @@ bool CTxMemPool::addUnchecked(const uint256& hash, const CTxMemPoolEntry &entry,
indexed_transaction_set::iterator newit = mapTx.insert(entry).first;
mapLinks.insert(make_pair(newit, TxLinks()));

// Update transaction for any feeDelta created by PrioritiseTransaction
// TODO: refactor so that the fee delta is calculated before inserting
// into mapTx.
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));
}
}

// Update cachedInnerUsage to include contained transaction's usage.
// (When we update the entry for in-mempool parents, memory usage will be
// further updated.)
Expand Down Expand Up @@ -363,15 +374,6 @@ 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));
}
}

// Save spent nullifiers
if (tx.IsShieldedTx()) {
for (const SpendDescription& sd : tx.sapData->vShieldedSpend) {
Expand Down Expand Up @@ -669,22 +671,19 @@ void CTxMemPool::check(const CCoinsViewCache* pcoins) const
CTxMemPool::setEntries setChildrenCheck;
std::map<COutPoint, CInPoint>::const_iterator iter = mapNextTx.lower_bound(COutPoint(tx.GetHash(), 0));
int64_t childSizes = 0;
CAmount childFees = 0;
CAmount childModFee = 0;
for (; iter != mapNextTx.end() && iter->first.hash == tx.GetHash(); ++iter) {
txiter childit = mapTx.find(iter->second.ptx->GetHash());
assert(childit != mapTx.end()); // mapNextTx points to in-mempool transactions
if (setChildrenCheck.insert(childit).second) {
childSizes += childit->GetTxSize();
childFees += childit->GetFee();
childModFee += childit->GetModifiedFee();
}
}
assert(setChildrenCheck == GetMemPoolChildren(it));
// Also check to make sure size/fees is greater than sum with immediate children.
// Also check to make sure size is greater than sum with immediate children.
// just a sanity check, not definitive that this calc is correct...
// also check that the size is less than the size of the entire mempool.
assert(it->GetSizeWithDescendants() >= childSizes + it->GetTxSize());
assert(it->GetFeesWithDescendants() >= childFees + it->GetFee());
assert(it->GetFeesWithDescendants() >= 0);
}

if (fDependsWait)
Expand Down Expand Up @@ -831,6 +830,14 @@ void CTxMemPool::PrioritiseTransaction(const uint256 hash, const std::string str
txiter it = mapTx.find(hash);
if (it != mapTx.end()) {
mapTx.modify(it, update_fee_delta(deltas.second));
// Now update all ancestors' modified fees with descendants
setEntries setAncestors;
uint64_t nNoLimit = std::numeric_limits<uint64_t>::max();
std::string dummy;
CalculateMemPoolAncestors(*it, setAncestors, nNoLimit, nNoLimit, nNoLimit, nNoLimit, dummy, false);
for (const txiter& ancestorIt : setAncestors) {
mapTx.modify(ancestorIt, update_descendant_state(0, nFeeDelta, 0));
}
}
}
LogPrintf("PrioritiseTransaction: %s priority += %f, fee += %d\n", strHash, dPriorityDelta, FormatMoney(nFeeDelta));
Expand Down Expand Up @@ -1030,7 +1037,7 @@ void CTxMemPool::TrimToSize(size_t sizelimit, std::vector<COutPoint>* pvNoSpends
// "minimum reasonable fee rate" (ie some value under which we consider txn
// to have 0 fee). This way, we don't allow txn to enter mempool with feerate
// equal to txn which were removed with no block in between.
CFeeRate removed(it->GetFeesWithDescendants(), it->GetSizeWithDescendants());
CFeeRate removed(it->GetModFeesWithDescendants(), it->GetSizeWithDescendants());
removed += minReasonableRelayFee;
trackPackageRemoved(removed);
maxFeeRateRemoved = std::max(maxFeeRateRemoved, removed);
Expand Down
43 changes: 22 additions & 21 deletions src/txmempool.h
Original file line number Diff line number Diff line change
Expand Up @@ -49,12 +49,12 @@ class CTxMemPool;
* ("descendant" transactions).
*
* When a new entry is added to the mempool, we update the descendant state
* (nCountWithDescendants, nSizeWithDescendants, and nFeesWithDescendants) for
* (nCountWithDescendants, nSizeWithDescendants, and nModFeesWithDescendants) for
* all ancestors of the newly added transaction.
*
* If updating the descendant state is skipped, we can mark the entry as
* "dirty", and set nSizeWithDescendants/nFeesWithDescendants to equal nTxSize/
* nTxFee. (This can potentially happen during a reorg, where we limit the
* "dirty", and set nSizeWithDescendants/nModFeesWithDescendants to equal nTxSize/
* nFee+feeDelta. (This can potentially happen during a reorg, where we limit the
* amount of work we're willing to do to avoid consuming too much CPU.)
*
*/
Expand All @@ -81,11 +81,11 @@ class CTxMemPoolEntry
// Information about descendants of this transaction that are in the
// mempool; if we remove this transaction we must remove all of these
// descendants as well. if nCountWithDescendants is 0, treat this entry as
// dirty, and nSizeWithDescendants and nFeesWithDescendants will not be
// dirty, and nSizeWithDescendants and nModFeesWithDescendants will not be
// correct.
uint64_t nCountWithDescendants; //! number of descendant transactions
uint64_t nSizeWithDescendants; //! ... and size
CAmount nFeesWithDescendants; //! ... and total fees (all including us)
CAmount nModFeesWithDescendants; //! ... and total fees (all including us)

public:
CTxMemPoolEntry(const CTransactionRef& _tx, const CAmount& _nFee,
Expand Down Expand Up @@ -114,12 +114,13 @@ class CTxMemPoolEntry

// 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
// Updates the fee delta used for mining priority score, and the
// modified fees with descendants.
void UpdateFeeDelta(int64_t feeDelta);

uint64_t GetCountWithDescendants() const { return nCountWithDescendants; }
uint64_t GetSizeWithDescendants() const { return nSizeWithDescendants; }
CAmount GetFeesWithDescendants() const { return nFeesWithDescendants; }
CAmount GetModFeesWithDescendants() const { return nModFeesWithDescendants; }

bool GetSpendsCoinbaseOrCoinstake() const { return spendsCoinbaseOrCoinstake; }
};
Expand Down Expand Up @@ -160,39 +161,39 @@ struct mempoolentry_txid
}
};

/** \class CompareTxMemPoolEntryByFee
/** \class CompareTxMemPoolEntryByDescendantScore
*
* Sort an entry by max(feerate of entry's tx, feerate with all descendants).
* Sort an entry by max(score/size of entry's tx, score/size with all descendants).
*/
class CompareTxMemPoolEntryByFee
class CompareTxMemPoolEntryByDescendantScore
{
public:
bool operator()(const CTxMemPoolEntry& a, const CTxMemPoolEntry& b) const
{
bool fUseADescendants = UseDescendantFeeRate(a);
bool fUseBDescendants = UseDescendantFeeRate(b);
bool fUseADescendants = UseDescendantScore(a);
bool fUseBDescendants = UseDescendantScore(b);

double aFees = fUseADescendants ? a.GetFeesWithDescendants() : a.GetFee();
double aModFee = fUseADescendants ? a.GetModFeesWithDescendants() : a.GetModifiedFee();
double aSize = fUseADescendants ? a.GetSizeWithDescendants() : a.GetTxSize();

double bFees = fUseBDescendants ? b.GetFeesWithDescendants() : b.GetFee();
double bModFee = fUseBDescendants ? b.GetModFeesWithDescendants() : b.GetModifiedFee();
double bSize = fUseBDescendants ? b.GetSizeWithDescendants() : b.GetTxSize();

// Avoid division by rewriting (a/b > c/d) as (a*d > c*b).
double f1 = aFees * bSize;
double f2 = aSize * bFees;
double f1 = aModFee * bSize;
double f2 = aSize * bModFee;

if (f1 == f2) {
return a.GetTime() >= b.GetTime();
}
return f1 < f2;
}

// Calculate which feerate to use for an entry (avoiding division).
bool UseDescendantFeeRate(const CTxMemPoolEntry &a) const
// Calculate which score to use for an entry (avoiding division).
bool UseDescendantScore(const CTxMemPoolEntry &a) const
{
double f1 = (double)a.GetFee() * a.GetSizeWithDescendants();
double f2 = (double)a.GetFeesWithDescendants() * a.GetTxSize();
double f1 = (double)a.GetModifiedFee() * a.GetSizeWithDescendants();
double f2 = (double)a.GetModFeesWithDescendants() * a.GetTxSize();
return f2 > f1;
}
};
Expand Down Expand Up @@ -367,7 +368,7 @@ class CTxMemPool
boost::multi_index::ordered_non_unique<
boost::multi_index::tag<descendant_score>,
boost::multi_index::identity<CTxMemPoolEntry>,
CompareTxMemPoolEntryByFee
CompareTxMemPoolEntryByDescendantScore
>,
// sorted by entry time
boost::multi_index::ordered_non_unique<
Expand Down
24 changes: 24 additions & 0 deletions test/functional/mempool_packages.py
Original file line number Diff line number Diff line change
Expand Up @@ -69,17 +69,41 @@ def run_test(self):
for x in reversed(chain):
assert_equal(mempool[x]['descendantcount'], descendant_count)
descendant_fees += mempool[x]['fee']
assert_equal(mempool[x]['modifiedfee'], mempool[x]['fee'])
assert_equal(mempool[x]['descendantfees'], SATOSHIS*descendant_fees)
descendant_size += mempool[x]['size']
assert_equal(mempool[x]['descendantsize'], descendant_size)
descendant_count += 1

# Check that descendant modified fees includes fee deltas from
# prioritisetransaction
self.nodes[0].prioritisetransaction(chain[-1], 0, 1000)
mempool = self.nodes[0].getrawmempool(True)

descendant_fees = 0
for x in reversed(chain):
descendant_fees += mempool[x]['fee']
assert_equal(mempool[x]['descendantfees'], SATOSHIS*descendant_fees+1000)

# Adding one more transaction on to the chain should fail.
try:
self.chain_transaction(self.nodes[0],txid, vout, value, fee, 1)
except JSONRPCException as e:
self.log.info("too-long-ancestor-chain successfully rejected")

# Check that prioritising a tx before it's added to the mempool works
self.nodes[0].generate(1)
self.nodes[0].prioritisetransaction(chain[-1], 0, 2000)
self.nodes[0].invalidateblock(self.nodes[0].getbestblockhash())
mempool = self.nodes[0].getrawmempool(True)

descendant_fees = 0
for x in reversed(chain):
descendant_fees += mempool[x]['fee']
if (x == chain[-1]):
assert_equal(mempool[x]['modifiedfee'], mempool[x]['fee']+satoshi_round(0.00002))
assert_equal(mempool[x]['descendantfees'], SATOSHIS*descendant_fees+2000)

# TODO: check that node1's mempool is as expected

# TODO: test ancestor size limits
Expand Down

0 comments on commit 8325bb4

Please sign in to comment.