-
Notifications
You must be signed in to change notification settings - Fork 37.7k
Mining: Select transactions using feerate-with-ancestors #7600
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Conversation
Does this algorithm claim to select the optimum descendant subtree for a given independent transaction? |
This algorithm is by no means guaranteed to produce maximum fee blocks for a given mempool, if that's what you're trying to ask. The algorithm this PR is trying to implement is straightforward:
The implementation is complicated because we can't actually remove things from the mempool. To deal with that, I added a new multi_index that Anyway, getting back to the algorithm -- I think this algorithm is superior to the existing one (which iterates based on each tx's individual feerate, not looking at ancestors) for optimizing block fees, and it has the property that (what I expect to be) typical uses of CPFP should generally be captured -- namely those situations where a single child is paying a large fee for its parents. However if there are multiple medium fee children that are chained below some low-fee parent, then the algorithm might easily fill the block without those transactions because they may not be considered until the block is full. For now though I suspect this algorithm is adequate for the current usage patterns, and perhaps we can come up with ways to monitor what's in the mempool to look for patterns of usage that would motivate further improvements to |
Would it be possible to have a CTxMempool::check that accurately checks the ancestor-based statistics, rather than just lower bounding them to 1 level up? |
e2865a2
to
ef35d94
Compare
Updated now that #7594 has been merged. |
Is this still WIP? |
@sdaftuar Github tasklists are maybe better for this kind of thing, and they show up as tasks even on ticket lists. https://github.com/blog/1375-task-lists-in-gfm-issues-pulls-comments |
Rebased on the latest #7598 |
fc8b425
to
48e0620
Compare
Rebased again and built off the latest version of #7598. |
Very nice!
Yes you would definitely expect bigger difference as this becomes available. As entities will be able to underpay transactions without risking getting dropped from the mempool. (for entities who continuously create transactions) One of the dangers of the blocksize limit is that fees would go up, but have almost no reason/force to get it down. Things like this and RBF could really help with that. 👍 Is this algorithm also used to determine which transactions to drop? |
@seweso The algorithm for eviction of transactions from the mempool is unchanged. In a sense, the transaction eviction algorithm for the mempool is already assuming we're doing something like what this PR implements for transaction selection, in that a transaction is less likely to be evicted from the mempool if its feerate with descendants is high. But more fundamentally, mempool eviction is just a different problem than transaction selection: when selecting transactions for a block, a candidate transaction's ancestors must be included (so it's natural to sort by feerate-with-ancestors), but when considering a transaction for mempool eviction, a candidate transaction's descendants must also be removed as well (so we use |
Needs rebase |
48e0620
to
157d24e
Compare
Rebased after #7598 has been merged. This PR is ready for review! |
Concept ACK |
iit++; | ||
} | ||
} | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Nit: may as well have ++iit in the for loop because it always occurs.
for (CTxMemPool::setEntries::iterator iit = testSet.begin(); iit != testSet.end(); ++iit) {
// Only test txs not already in the block
if (inBlock.count(*iit))
testSet.erase(iit);
}
Edit: ignore, erasing an iterator invalidates iit.
utAck |
assert (potentialBlockSigOps < MAX_BLOCK_SIGOPS); | ||
} | ||
return true; | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Nit: since block size and sigops have already been checked, it shouldn't be necessary to re-check and assert here.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I guess I left these asserts in to make the logic in addPackageTxs easier to review, in that if you weren't sure whether the mapModifiedTx calculations were correct, this assertion ensures that it doesn't underestimate. But I suppose this would be caught in TestBlockValidity anyway, so I'll remove.
Pushed commits addressing @mrbandrews comments. re: @JeremyRubin's nits, I'm leaving I'll do another round of testing locally, similar to what I did before when I opened the pull, with the current version of the code and report back. |
I re-tested my code by simulating on data from 2016-02-23 -to 2016-02-29, comparing the default mining code in master against the mining code introduced here, by calling CreateNewBlock every 100 transactions. I looked at the fees produced in the last call to CNB before each block found by the network, and the code in this PR produced higher fee blocks in each instance (894 data points). I was also benchmarking the runtime of CNB over all invocations, and this code was negligibly faster than the old code (summing over all invocations). |
ACK. Beyond review, I tested this on several mining nodes for about a week (in addition to prior testing I did months back); I also took nodes running this and a tight getblocktemplate loop through a number of reorg tests with debugging turned up. Finally, rather than using sdaftuar record and replay framework, I scraped all the recent transactions out of recent blocks, reorged the chain back and put all the still-valid transactions into the mempool and confirmed it gave a valid gbt result with higher total fees than the before the patch, I tried this at a dozen different points. |
ACK. |
{ | ||
// Start by adding all descendants of previously added txs to mapModifiedTx | ||
// and modifiying them for their already included ancestors | ||
UpdatePackagesForAdded(inBlock); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
If I understand it correctly, this means you can't call addPackageTxs() twice (or have another add*Tx() that also calls UpdatePackagesForAdded(inBlock);
. That could be solved by keeping a set of things in inBlock
that have already been counted for package updates, but I guess it's sufficient to just document that this must not be called twice.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Calling it twice doesn't really make sense, as addPackageTx's would fill up your block the first time. I think if you wanted to implement custom transaction selection, you'd implement it in a function that is called before you'd call addPackageTx's to fill the rest of the block (like how priority tx's are done).
I can add a comment though to make this clearer.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Oh I forgot to mention: this function no longer changes global state, after I addressed @mrbandrews comments in a later commit and moved mapModifiedTx to be a local variable in addPackageTxs. So this can be reused, though it's pretty tied to the package selection.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
ACK, fixed by the later commit.
ACK f3c6551 (with tree 66fb31f4aaaf3c49ef03c93b3b8155b531359e05). Update: ACK b428fb2656b3cdf3021faf508eb78e6831e5a276 (with tree 2e6a8674a4e7179aea0041931d68076a442d0132). Can you squash the fixups? |
Includes changes by Pieter Wuille
b428fb2
to
29fac19
Compare
Thanks, squashed. |
ReACK 29fac19 (tree 2e6a8674a4e7179aea0041931d68076a442d0132). |
29fac19 Add unit tests for ancestor feerate mining (Suhas Daftuar) c82a4e9 Use ancestor-feerate based transaction selection for mining (Suhas Daftuar)
…cestors 29fac19 Add unit tests for ancestor feerate mining (Suhas Daftuar) c82a4e9 Use ancestor-feerate based transaction selection for mining (Suhas Daftuar)
…cestors 29fac19 Add unit tests for ancestor feerate mining (Suhas Daftuar) c82a4e9 Use ancestor-feerate based transaction selection for mining (Suhas Daftuar)
…priority removal f5b2e54 Fix comparisons for integer expressions of different signedness (random-zebra) 784d8b4 [Doc] Add Mempool priority changes to release notes (random-zebra) 6163d4b Remove -blockminsize option (Suhas Daftuar) b304fb9 Allow setting minrelaytxfee to 0 (Alex Morcos) 006136e Make boost::multi_index comparators const (Suhas Daftuar) 5858fb5 [Tests] Remove priority check for sapling txes (random-zebra) e267d02 [Cleanup] Remove coin age priority completely (random-zebra) 4298938 [RPC] Remove priorityDelta from prioritisetransaction (random-zebra) cd1535a [rpc] Remove priority information from mempool RPC calls (Alex Morcos) 3b4d207 [Refactor][RPC] entryToJSON/EntryDescriptionString out of mempoolToJSON (random-zebra) 142415e Fix edge case with stale fee estimates (Alex Morcos) 9f47b0f Add clarifying comments to fee estimation (Alex Morcos) 57b7922 Add extra logging to processBlock in fee estimation. (Alex Morcos) 59486ef Add IsCurrentForFeeEstimatation (Alex Morcos) d23ebfd Pass pointers to existing CTxMemPoolEntries to fee estimation (Alex Morcos) be5982e Always update fee estimates on new blocks. (Alex Morcos) aa97809 rename bool to validFeeEstimate (Alex Morcos) 82aba64 Remove member variable hadNoDependencies from CTxMemPoolEntry (random-zebra) c3758e1 Don't track transactions at all during IBD. (Alex Morcos) 9ca59ae [rpc] rawtx: Prepare fLimitFree to make it an option (MarcoFalke) f682e75 [wallet] Set fLimitFree = true (MarcoFalke) 0221d5a [QA] Fix random failure in wallet_abandonconflict.py (random-zebra) 33c9aa1 [Trivial] Pass hash by const ref to CBlockPolicyEstimator::removeTx (random-zebra) 7be33e0 Remove extraneous LogPrint from fee estimation (Alex Morcos) 7b6e8a3 [test] Remove priority from tests (Alex Morcos) c2ecf27 No longer allow "free" transactions (Alex Morcos) 5c577c5 [Cleanup] Remove feature_nulldummy.py unused functional test (random-zebra) 3097847 [cleanup] Remove estimatePriority and estimateSmartPriority (random-zebra) f283e8a [debug] Change -printpriority option (Alex Morcos) dcddcaf [Cleanup] Remove unused (always false) fAllowFree arg in GetMinRelayFee (random-zebra) 03d2ee9 [mining] Remove -blockprioritysize. (Alex Morcos) cfaeb97 [BUG] Don't add priority txes (random-zebra) 01882da Add unit tests for ancestor feerate mining (Suhas Daftuar) 49b1a9b [Policy] Limit total SHIELD size and check spork 20 in addPackageTxs (random-zebra) cc473ed Use ancestor-feerate based transaction selection for mining (random-zebra) dfaf4e7 [Wallet] Remove sendfree (random-zebra) Pull request description: This PR implements CPFP transaction selection mechanism, which is the simplest "fee bumping technique" (we might want to consider implementing RBF too later on). The algorithm in `CreateNewBlock` now selects transactions based on their feerate, inclusive of unconfirmed ancestor transactions. This means that a low-fee transaction can become more likely to be selected if a high-fee transaction that spends its outputs is relayed: the fee burned in the "child" transaction, actually pays for the parent tx too (hence the name). This PR also removes completely the (already deprecated) notion of coinage-based priority and "free transactions area". Changes backported / adapted from: - bitcoin#7600 [Suhas Daftuar] - bitcoin#8287 [MarcoFalke] - bitcoin#8295 [Suhas Daftuar] - bitcoin#9138 [Alex Morcos] - bitcoin#9602 [Alex Morcos] - bitcoin#11847 [Suhas Daftuar] ACKs for top commit: Fuzzbawls: utACK f5b2e54 furszy: re-ACK f5b2e54 after rebase and last commit. Tree-SHA512: aef64628008699c81735660fcbe51789b7dc9d2a670d0c695399a2821f01f5d72e46d72b5f57d7b28c0e0028b60b4d6294ee101b8038ea46d237c7b8729a79da
This implements "Child-pays-for-parent" transaction selection in
CreateNewBlock
, where we sort the mempool using (modified) feerate with ancestors and consider transactions in that order during block creation. As transactions are selected, a separate map is used to track the new feerate-with-ancestors value to use for in-mempool descendants that haven't yet been selected.This is almost strictly better (ie, results in higher-fee blocks) than the existing mining algorithm, with some small variance due to edge cases around which transactions might get selected as the new block's size approaches the maximum allowed. Moreover the performance impact on
CreateNewBlock
is minimal -- I observed that the total run time of the function actually slightly improved, apparently due toTestBlockValidity
being on average slightly faster (a somewhat surprising result).The actual fee improvements observed were fairly low in the time period I looked at (October 2015), roughly a 1% fee improvement overall. I think that is driven first and foremost by blocks not really being full for a large bulk of the time period, so on a large number of data points the results were identical. Additionally, my guess is that user behavior may not have adapted to this type of transaction selection being widely deployed, and so in the future perhaps we might expect to see larger differences. I did note a handful of instances where the implementation here notably outperformed the existing algorithm, in some instances more than 20% better, but those were the exception and not the rule.
For comparison, I found no examples where the old algorithm produced a block with 0.5% or more in fees than the new algorithm (and few examples where the old algorithm's block had more fees at all, around 1% of samples).
I've labeled this PR as a work-in-progress for now, as it builds off two other open PR's (#7594 and #7598). Once those are reviewed and merged, I'll rebase this and remove the WIP label. I opened this now to help motivate the work in those two PRs.
One open question I have is whether it's worth keeping around the old transaction selection algorithm. The refactor in #7598 that this builds on makes it easy to leave it around, and I don't think it would be hard to add command line arguments for policy configuration that would enable using the older algorithm. But I don't know if that's of interest to anyone, so for now I figure we can let people mull it over and decide what to do in a future PR.
A related question to that is whether to get rid of the existing mempool "score" index. The ancestor tracking pull adds a new ancestor feerate index without removing the old feerate index, but if we decide to get rid of the mining algorithm that uses that index, then perhaps we could also eliminate this mempool index as well.
To do: