-
Notifications
You must be signed in to change notification settings - Fork 38.3k
mining: add getMemoryLoad() and track template non-mempool memory footprint #33922
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
base: master
Are you sure you want to change the base?
Conversation
|
The following sections might be updated with supplementary metadata relevant to reviewers and maintainers. Code Coverage & BenchmarksFor details see: https://corecheck.dev/bitcoin/bitcoin/pulls/33922. ReviewsSee the guideline for information on the review process.
If your review is incorrectly listed, please copy-paste ConflictsReviewers, this pull request conflicts with the following ones:
If you consider this pull request important, please also help to review the conflicting pull requests. Ideally, start with the one that should be merged first. |
|
I haven't benchmarked this yet on mainnet, so I'm not sure if checking every (unique) transaction for mempool presence is unacceptably expensive. If people prefer, I could also add a way for the |
21ad8c1 to
f22413f
Compare
|
🚧 At least one of the CI tasks failed. HintsTry to run the tests locally, according to the documentation. However, a CI failure may still
Leave a comment here, if you need help tracking down a confusing failure. |
|
|
||
| TxTemplateMap& tx_refs{*Assert(m_tx_template_refs)}; | ||
| // Don't track the dummy coinbase, because it can be modified in-place | ||
| // by submitSolution() |
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.
f22413f to
3b77529
Compare
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.
Concept ACK
I think it would be better if we have internal memory management for the mining interface IPC, since we hold on to the block templates.
I would suggest the following approach:
- Add memory budget for the mining interface.
- Introduce a tracking list of recently built block templates and total memory usage.
- Add templates to the list and increment the memory usage after every
createnewblockorwaitnextreturn. - Whenever the memory budget is exhausted, we should release templates in FIFO order.
I think since we create a new template after a time interval elapses even if fees increase and that interval is usually enough for the client to receive and distribute the template to miners, this mechanism should be safe as the miners have long switch to most recent template when the budget elapsed because of the time interval being used in between returns of waitnext.
Mining interface clients should also handle their own memory internally.
Currently, I don’t see much use for the exposed getMemoryLoad method. In my opinion, we should not rely on the IPC client to manage our memory.
It seems counter intuitive, but from a memory management perspective IPC clients are treated no different than our own code. And if we started FIFO deleting templates that are used by our own code, we'd crash. So I think FIFO deletion should be a last resort (not implemented here). There's another reason why we should give clients an opportunity to gracefully release templates in whatever order they prefer. Maybe there's 100 downstream ASIC's, one of which is very slow at loading templates, so it's only given a new template when the tip changes, not when there's a fee change. In that scenario you have a specific template that the client wants to "defend" at all cost. In practice I'm hoping none of this matters and we can pick and recommend defaults that make it unlikely to get close to a memory limit, other than during some weird token launch. |
IMHO I think we should separate that, and treat clients differently from our own code, because they are different codebases and separate applications with their own memory.
I see your point but I don’t think that’s a realistic scenario, and I think we shouldn’t design software to be one-size-fits-all.
Delegating template eviction responsibility to the client can put us in a situation where they handle it poorly and cause us to OOM (but I guess your argument is that we rather take that chance than being in a situation where we make miners potentially lose on rewards). |
Note that it's already the clients responsibility, that's inherent to how multiprocess works. In the scenario where they handle it poorly, we can use FIFO deletion. All
We currently don't track whether any given
Afaik that means revalidating the block from scratch, removing one advantage the |
3b77529 to
24592b7
Compare
|
I restructured the implementation and commits a bit. The It's also less code churn because I don't have to touch the It also made it easier to move This in turn let me split out a separate commit that introduces the actual I added some comments to point out that we don't hold a |
24592b7 to
03dcfae
Compare
|
One caveat is that Expanded the PR description. |
03dcfae to
ac1e97a
Compare
Prepare template destruction handling for a later commit that checks memory management: - add destroy_template helper which awaits the result and avoids calling destroy() if we never received a template - reverse order and prevent template override. This ensures template and template2 (which don't have transactions) are destroyed last. Additionally, expand the test to demonstrate how setting feeThreshold to MAX_MONEY ignores new mempool transactions. This extra transaction is needed in a later commit (to add coverage for reference counting).
The getblocktemplate RPC uses a static BlockTemplate, which goes out of scope only after the node completed its shutdown sequence. This becomes a problem when a later commit implements a destructor that uses m_node.
IPC clients can hold on to block templates indefinately, which has the same impact as when the node holds a shared pointer to the CBlockTemplate. Because each template in turn tracks CTransactionRefs, transactions that are removed from the mempool will have not have their memory cleared. This commit adds bookkeeping to the block template constructor and destructor that will let us track the resulting memory footprint.
Calculate the non-mempool memory footprint for template transaction references. Add bench logging to collect data on whether caching or simplified heuristics are needed, such as not checking for mempool presence.
Allow IPC clients to inspect the amount of memory consumed by non-mempool transactions in blocks. Returns a MemoryLoad struct which can later be expand to e.g. include a limit. Expand the interface_ipc.py test to demonstrate the behavior and to illustrate how clients can call destroy() to reduce memory pressure.
ac1e97a to
e8f8f7f
Compare
|
Concept ACK e8f8f7f. All the changes here seem good and mostly straightforward. The I am a little concerned about the idea of proactively deleting block templates in FIFO order on behalf of clients, since it seems like this could increase complexity server-side, and client-side if clients have to deal with templates disappearing without being notified. Just not returning new templates after a certain amount of memory has been used would like a simpler approach. re: #33922 (comment)
Would be good if this said templates are also released if the python references are destroyed or go out of scope. (This stood out because I tested this yesterday in #33940 (comment).) |
It is, but refusing to make new templates doesn't stop the footprint of existing templates from growing. The worst case extra memory footprint for existing templates is the full size of the mempool. This is rather unlikely though, it would only happen if between two blocks the entire mempool was gradually RBF'd in such a way that each transaction was at the top of the mempool briefly, and thus made it into a template.
Added a sentence to the PR description. |
ryanofsky
left a comment
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.
Code review e8f8f7f. This looks good except for a thread safety issue I think you can address by adding a mutex.
re: #33922 (comment)
Would be good if this said templates are also released
Added a sentence to the PR description.
Sorry, I should have made a more specific suggestion. The problem is is that this sentence is not accurate: "templates are not released until the client disconnects or calls the destroy() method." Templates will be released if the client drops references to them, even if it never disconnects or calls destroy. I would just change it to "templates are not released until the client drops references to them, or calls the template destroy method, or disconnects"
| static CBlockIndex* pindexPrev; | ||
| static int64_t time_start; | ||
| static std::unique_ptr<BlockTemplate> block_template; | ||
| std::unique_ptr<BlockTemplate>& block_template{node.gbt_template}; |
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.
In commit "rpc: move static block_template to node context" (a5eee29)
I think it would actually be nice to move all these static variables to a struct or class like @ismaelsadeeq's BlockTemplateCache from #33421. But this could be a followup, and doesn't need to complicate this PR.
| //! Cache latest getblocktemplate result for BIP 22 long polling | ||
| //! Track how many templates (which we hold on to on behalf of connected IPC | ||
| //! clients) are referencing each transaction. | ||
| TxTemplateMap template_tx_refs; |
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.
In commit "mining: track non-mempool memory usage" (7c4d03d)
This map can updated from multiple threads, so it needs a mutex to be used safely. I think I'd suggest combining template_tx_refs and gbt_template variables and a mutex into single struct called something like BlockTemplateState and adding a unique_ptr to that struct as a member here. The struct could be replaced with a cache class in #33421.
| assert_equal(template7.to_dict(), {}) | ||
|
|
||
| self.log.debug("Memory load should be zero because there was no mempool churn") | ||
| with self.nodes[0].assert_debug_log(["Calculate template transaction reference memory footprint"]): |
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.
In commit "ipc: add getMemoryLoad()" (e8f8f7f)
Seems ok to assert this log message is logged, but I'm wondering if there was a particular reason for doing this. Was the idea to pair the LOG_TIME_MILLIS_WITH_CATEGORY and assert_debug_log calls together?


Implements a way to track the memory footprint of all non-mempool transactions that are still being referenced by block templates, see discussion in #33899. It does not impose a limit.
IPC clients can query this footprint (total, across all clients) using the
getMemoryLoad()IPC method. Its client-side usage is demonstrated here:Additionally, the functional test in
interface_ipc.pyis expanded to demonstrate how template memory management works: templates are not released until the client disconnects or calls thedestroy()method. The latter happens automatically for clients using libmultiprocess, as sv2-tp does. In the Python tests it also happens when references are destroyed or go out of scope.The PR starts with preparation refactor commits:
interface_ipc.pysodestroy()calls happen in an order that's useful to later demonstrate memory managementstd::unique_ptr<BlockTemplate> block_templatefrom astaticdefined inrpc/mining.cpptoNodeContext. This prevents a crash when we switch to a non-trivial destructor later (which usesm_node).Then the main commits:
template_tx_refstoNodeContextto track how many templates contain any given transaction. This map is updated by theBlockTemplateconstructor and destructor.GetTemplateMemoryUsage()which loops over this map and sums up the memory footprint for transactions outside the mempoolgetMemoryLoad()and add test coverage