-
Notifications
You must be signed in to change notification settings - Fork 746
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
revive: Limit the amount of static memory a contract can use #5726
Conversation
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.
LGTM
substrate/frame/revive/src/limits.rs
Outdated
pub fn enforce<T: Config>(blob: Vec<u8>) -> Result<CodeVec, DispatchError> { | ||
fn round_page(n: u64) -> u64 { | ||
debug_assert!( | ||
PAGE_SIZE != 0 && (PAGE_SIZE & (PAGE_SIZE - 1)) == 0, |
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.
FYI, you have the next_multiple_of
method on primitives you can use instead of this helper.
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.
Ahh yes. This function panics on overflow, though. Hence, I forced the inputs to be u32 and do the rounding in u64. When we switch to 64bit we probably just error out when the section sizes don't fit into u32.
substrate/frame/revive/src/limits.rs
Outdated
// plus the overhead of instructions in memory which is derived from the code | ||
// size itself and the number of instruction | ||
let memory_size = (blob.len() as u64) | ||
.saturating_add(round_page(program.ro_data_size() as u64)) |
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.
- For all of the
u32
->u64
casts you could useu64::from
, which I think is nicer as it will give you an error if this cast ever becomes truncating (in PolkaVM I have a clippy lint set up for this). - Alternatively, instead of the
saturating_*
spam you could usecore::num::Saturating
wrapper plus a local trait to make it nicer, but this might be a complete overkill.
trait Cast {
fn cast(self) -> Saturating<u64>;
}
impl Cast for u32 {
fn cast(self) -> Saturating<u64> { return Saturating(u64::from(self)) }
}
impl Cast for usize {
fn cast(self) -> Saturating<u64> { return Saturating(self as u64) }
}
let memory_size = blob.len().cast()
+ program.ro_data_size().cast()
- program.ro_data.len().cast()
+ ...
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.
- Yes to the first point. I replaced by
into
where possible. The code then breaks when we upgrade to 64bit and we need to re-evaluate. - Looking at the saturating_* spam gives me a piece of mind. Seeing raw arithmetic is kinda triggering :)
@@ -56,7 +57,7 @@ use sp_runtime::DispatchError; | |||
#[codec(mel_bound())] | |||
#[scale_info(skip_type_params(T))] | |||
pub struct WasmBlob<T: Config> { |
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.
Shouldn't the WasmBlob
be renamed? (: (Not necessarily in this PR of course.)
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.
Yes there is a lot of renaming to do. I am pushing this of because this will be a nasty PR.
@@ -58,3 +61,85 @@ pub const STORAGE_KEY_BYTES: u32 = 128; | |||
/// | |||
/// The buffer will always be disabled for on-chain execution. | |||
pub const DEBUG_BUFFER_BYTES: u32 = 2 * 1024 * 1024; | |||
|
|||
/// The page size in which PolkaVM should allocate memory chunks. | |||
pub const PAGE_SIZE: u32 = 4 * 1024; |
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.
maybe just use VM_MIN_PAGE_SIZE from polkavm-common?
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.
That constant's not for public consumption. (: (In general everything is 'polkavm-common' is private)
Besides, what matters is the page size that's configured, not what the minimum supported is, so this way is how it is actually intended to be used.
substrate/frame/revive/src/lib.rs
Outdated
.saturating_sub(MAX_STACK_SIZE) | ||
.saturating_div(17 * 4); | ||
.saturating_sub(STATIC_MEMORY_BYTES) | ||
.saturating_div(4); |
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.
.saturating_div(4); | |
.saturating_div(EXTRA_OVERHEAD_PER_CODE_BYTE); |
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.
This is to account for memory allocator overhead (see the docs above). But it's a good point: All those numbers should have names.
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.
Replaced by constants
This will make sure that when uploading new code that the declared static memory fits within a defined limit. We apply different limits to code and data. Reason is that code will consume much more memory per byte once decoded during lazy execution.
This PR:
Config
to we maintain tight control over it.STATIC_MEMORY_BYTES
knob that limits the maximum decoded size.integrity_check
.