Track nested memory used by xcm::DoubleEncoded: approach #1 - #10747
Track nested memory used by xcm::DoubleEncoded: approach #1#10747serban300 wants to merge 3 commits into
xcm::DoubleEncoded: approach #1#10747Conversation
2bf2683 to
351c175
Compare
b4ba120 to
59bccbe
Compare
59bccbe to
ea4fbeb
Compare
xcm::DoubleEncodedxcm::DoubleEncoded
bkchr
left a comment
There was a problem hiding this comment.
I find the current approach hard to follow and it also doesn't forward the left over limit from decoding the extrinsic.
E.g. when executing pallet_xcm::execute it should only leave the rest from the budget for Transact and not give it the full budget again. Also the budgeting right now maybe shares too much. E.g. when I want to load two XCM messages from the storage, they will both use the same limit. This is probably not wanted. We should provide some better "tools" around the bounded limit.
struct DecodeWithLimit {
limit: usize,
decoded: T,
}
impl Decode using DecodeWithLimitThingy {}
fn DecodeWithLimit {
pub use(use: Impl(&mut T) -> R) -> R {
mem::run_with(self.limit, || use(&mut self.decoded))
}
}
Maybe something like this? Then we could use this to load XCM messages from storage?
| //! | ||
| //! There are also cases where there can be multiple nested double-encoded layers. | ||
|
|
||
| use crate::generic::DEFAULT_CALL_SIZE_LIMIT; |
There was a problem hiding this comment.
If we want that to be generic, we should pass the limit as an input value and not use this constant.
| "Dispatch failed" | ||
| ); | ||
| Transact { origin_kind, call, .. } => { | ||
| sp_runtime::nested_mem::using_limiter_once(|| { |
There was a problem hiding this comment.
The problem here is that we will run here without knowing how much the outer extrinsic already used for decoding.
There was a problem hiding this comment.
We can find out the remaining mem limit by calling sp_runtime::nested_mem::get_current_limit()
| signature_check, | ||
| select, | ||
| ); | ||
| nested_mem::using_limiter_once(|| { |
There was a problem hiding this comment.
The scope is way too broad here. We only need this for extrinsic applying.
There was a problem hiding this comment.
We also need it for on_idle(). Because the XCMs received from other chains are being executed in the on_idle() hook of the pallet-message-queue.
I wasn't sure if we needed it for other hooks as well and I thought that it would be safer to execute the entire block inside using_limiter_once().
Anyway, trying to find a better approach overall.
serban300
left a comment
There was a problem hiding this comment.
E.g. when executing pallet_xcm::execute it should only leave the rest from the budget for Transact and not give it the full budget again.
You mean that when we execute and extrinsic, we should subtract the mem used for decoding the extrinsic from the budget first, and then use only the rest for Transact ? The problem is that this can lead to inconsistencies. For example:
- dry running an XCM can succeed, because it's not ran as part of an extrinsic, but if we run it via
pallet_xcm::execute()it can fail, because of the extra extrinsic size. - an XCM can succeed when running it in
on_idleand fail if we run it viapallet_xcm::execute() - an XCM can succeed in an extrinsic and fail in a different one
Also the budgeting right now maybe shares too much. E.g. when I want to load two XCM messages from the storage, they will both use the same limit. This is probably not wanted.
When simply loading 2 XCMs from the storage, the nested mem budget is not used. Only when we execute them and we decode the Transact instructions we end up using the nested mem budget. But:
- normally the first XCM should free the memory after it is executed, because
try_using_decoded()consumes the object. So normally we don't need to use a lower limit for the second XCM. There is a problem when usingensure_decoded(), because this doesn't consume the object. But if the entire block is executed insidenested_mem::using_limiter_once, this should also be covered. - I think it would be problematic if the successful execution of an XCM would depend on the memory used by the other XCMs that were executed before it. It would lead to inconsistencies depending on the order in which the XCMs are run
I find the current approach hard to follow and it also doesn't forward the left over limit from decoding the extrinsic.
I agree this approach with global variables is hard to follow and has some limitations. Maybe we can use a storage value instead. Maybe this could make it more readable and more flexible. I need to experiment a bit. Will try to change the approach.
| signature_check, | ||
| select, | ||
| ); | ||
| nested_mem::using_limiter_once(|| { |
There was a problem hiding this comment.
We also need it for on_idle(). Because the XCMs received from other chains are being executed in the on_idle() hook of the pallet-message-queue.
I wasn't sure if we needed it for other hooks as well and I thought that it would be safer to execute the entire block inside using_limiter_once().
Anyway, trying to find a better approach overall.
| "Dispatch failed" | ||
| ); | ||
| Transact { origin_kind, call, .. } => { | ||
| sp_runtime::nested_mem::using_limiter_once(|| { |
There was a problem hiding this comment.
We can find out the remaining mem limit by calling sp_runtime::nested_mem::get_current_limit()
|
Another option would be to try to recursively decode the entire XCM, together with the double encoded calls. When we do this as part of an extrinsic, it will be limited by the extrinsic heap limit. As part of the XCMP/UMP/DMP queue, we can use a custom limit. Will experiment with this as well. |
xcm::DoubleEncodedxcm::DoubleEncoded
xcm::DoubleEncodedxcm::DoubleEncoded: approach #1
|
Closing in favor of #11147 |
…tech#11147) Resolves paritytech#8675 Follow-up for paritytech#10747 (approach # 1) Different approach for tracking the nested memory used by `xcm::DoubleEncoded` The idea is to decode the entire `xcm::DoubleEncoded<Call>` when possible. This can be done when the `Call` is the `RuntimeCall` of our local Runtime. When the `Call` is `()` it means that it's a remote `RuntimeCall`, and it will be decoded on the destination chain. This covers the XCMP/UMP/DMP pathways and the `pallet_xcm::execute()` extrinsic or any other extrinsic that accepts XCMs. For extrinsics we have the heap memory limit used for decoding the extrinsic (16 MB) and for XCMP/UMP/DMP we call `decode_with_constraints()` which uses a hardcoded 10MB limit now. We can adjust it or make it configurable. I hope I'm not missing any use cases. Known issues (to be fixed in future PRs): - dry-running: The methods defined in `RecordXcm` return `VersionedXcm<()>` directly. We should add the `RuntimeCall` as a generic type param (`RecordXcm<Call>`) and modify all the methods to return `VersionedXcm<Call>` - `DoubleEncoded::transmute_encoded()` is used in some mocks. Not sure if it's needed outside of mocks. Maybe we can remove it with some cleanup. --------- Co-authored-by: cmd[bot] <41898282+github-actions[bot]@users.noreply.github.com>
Resolves #8675
The PR introduces a global variable that limits the amount of heap memory available for decoding structures within nested contexts.