-
Notifications
You must be signed in to change notification settings - Fork 2.6k
Embed runtime version as a custom section #8688
Conversation
This change emits a custom section from the impl_runtime_apis! proc macro. Each implemented API will result to emitting a link section `runtime_apis`. During linking all sections with this name will be concatenated and placed into the final wasm binary under the same name.
This macro takes an existing `RuntimeVersion` const declaration, parses it and emits the version information in form of a linking section. Ultimately such a linking section will result into a custom wasm section.
Co-authored-by: David <dvdplm@gmail.com>
Just as a status update: I am struggling to find what's the reason of the test failures in the test-linux-stable-int and cargo-check-benches. I can reproduce both locally. The both failures have the same error:
vs.
The difference is only one byte. I haven't figured out what this data stands for yet. I will keep investigating it tomorrow. |
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.
Looks good, but still some stuff that could be improved :D
.filter(|cs| cs.name() == section_name) | ||
.next() | ||
.map(|cs| cs.payload()) |
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.
.filter(|cs| cs.name() == section_name) | |
.next() | |
.map(|cs| cs.payload()) | |
.filter_map(|cs| (cs.name() == section_name).then(|| cs.payload())) |
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 am not entirely sure how would that work. Currently, this code takes an iterator of custom sections, leaves only sections that we are interested in, takes the first one which gives us an option and then we narrow the contents of this section to only its payload. But in the proposed version the result seems to be not an option
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 I can introduce you to: https://doc.rust-lang.org/std/primitive.bool.html#method.then ;)
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.
TIL. This however misses the point, if I understand you correctly. The problem is the expression in the proposed code is typed as Iterator
whereas the function returns an Option
.
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.
filter
+ next
is a find
though.
We can simplify the code to Iterator::find
+ Option::map
. That will reduce the number of combinators by one.
We could use a Iterator::find_map
, but that would use nested bool::then
. But I consider nested combis harder to reason about and I would guess bool::then
also is not among commonly used functions out there.
//! will go into the custom section is parsed from the item declaration. Due to that, the macro is | ||
//! somewhat rigid in terms of the code it accepts. There are the following considerations: | ||
//! | ||
//! - The `spec_name` and `impl_name` must be set by a macro-like expression. The name of the macro |
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.
Why do we enforce this?
With this macro we could switch to only supporting a static str and this macro would expand to the correct code that creates the Cow::Borrowed
?
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.
Funnily, this is similar to my initial approach. I found it confusing that the actual type is RuntimeString
, but the user passes an &str
expression. That's why I ditched it.
This way where we try to recognize the a macro call is very rigid, but at least it seems less magicky.
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.
What is magicky in taking a &str?
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 Rust types are not converted under the hood typically. There are certain exceptions like Deref
coercions, but in overwhelming majority of cases you have to be explicit if you want conversion to happen. You cannot even use a string literal to initialize a String
!
Applying this logic to the interface in question, if we were to accept a string literal directly and wrap it into RuntimeString::Borrowed
then it would look like the string type was transmogrified into RuntimeString
.
It may have been OK in other cases, but here we have syntax which is identical to normal Rust. Imagine a reader who is not familiar with the internal workings of the macro. If the macro there, then suddenly rules are different and a string literal can be accepted in place of a RuntimeString
defying the standard rules. One may call such a behavior "magical".
client/executor/src/wasm_runtime.rs
Outdated
let mut version = None; | ||
|
||
// Use the runtime blob to scan if there is any metadata embedded into the wasm binary pertaining | ||
// to runtime version. We do it before consuming the runtime blob for creating the runtime. | ||
if let Some(embedded_version) = read_embedded_version(&blob)? { | ||
version = Some(embedded_version); | ||
} |
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.
let mut version = None; | |
// Use the runtime blob to scan if there is any metadata embedded into the wasm binary pertaining | |
// to runtime version. We do it before consuming the runtime blob for creating the runtime. | |
if let Some(embedded_version) = read_embedded_version(&blob)? { | |
version = Some(embedded_version); | |
} | |
// Use the runtime blob to scan if there is any metadata embedded into the wasm binary pertaining | |
// to runtime version. We do it before consuming the runtime blob for creating the runtime. | |
let mut version = read_embedded_version(&blob)?; |
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.
Do you insist? I do realize that it is possible and is more succinct. However, IMO, the verison is there emphasizes that embedded version can be None
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 will not insist, but I find it weird :D
In the end the type system is emphasizing that this can be None
:P
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.
Ok found a compromise:
let mut version: Option<_> = read_embedded_version(&blob)?;
/// deserializing it. | ||
/// | ||
/// See [`sp_maybe_compressed_blob`] for details about decompression. | ||
pub fn uncompress_if_needed(wasm_code: &[u8]) -> Result<Self, WasmError> { |
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 new
should do this? IMHO the name indicates that this is done on an already existing reference, while actually we create an instance with this method
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.
You mean because it's uncompress
but returns Self
? I don't think it is unheard of, even though that new_*
, from_*
or with_*
are the most common. For example, there is TcpStream::connect
which returns Self
.
I also had an idea about putting it in new
, but in the end I decided to go with uncompress
to indicate at the call sites , mainly in sc-executor, that the code may be compressed at that point. If compression were hidden in new
, it would be even harder to find out.
I do agree that it is a small thing and perhaps is not that useful, so I can tuck this logic under new
if you wish so.
The burn-in looks good. We are interested to get this in rather quickly to be able to upgrade polkadot validation functions with version checks. |
bot merge |
Trying merge. |
Bot will approve on the behalf of @pepyakin, since they are a team lead, in an attempt to reach the minimum approval count |
* emit a custom section from impl_runtime_apis! This change emits a custom section from the impl_runtime_apis! proc macro. Each implemented API will result to emitting a link section `runtime_apis`. During linking all sections with this name will be concatenated and placed into the final wasm binary under the same name. * Introduce `runtime_version` proc macro This macro takes an existing `RuntimeVersion` const declaration, parses it and emits the version information in form of a linking section. Ultimately such a linking section will result into a custom wasm section. * Parse custom wasm section for runtime version * Apply suggestions from code review Co-authored-by: David <dvdplm@gmail.com> * Fix sc-executor integration tests * Nits Co-authored-by: Bastian Köcher <bkchr@users.noreply.github.com> * Refactor apis section deserialization * Fix version decoding * Reuse uncompressed value for CallInWasm * Log on decompression error * Simplify if * Reexport proc-macro from sp_version * Merge ReadRuntimeVersionExt * Export `read_embedded_version` * Fix test * Simplify searching for custom section Co-authored-by: David <dvdplm@gmail.com> Co-authored-by: Bastian Köcher <bkchr@users.noreply.github.com>
I got curious, why |
No real reason, just not yet done. |
Filled an issue ↑ |
This changeset is a step towards #7327
This change consists of three commits (could be reviewed individually)
runtime_apis
from theimpl_runtime_apis!
macroruntime_version
proc-macro that parses an existingRuntimeVersion
declaration and emits a wasm custom sectionruntime_version
RuntimeVersion
struct before calling into the runtime.The client side part is implemented with a built in extension, which is similar to runtime workers machinery. Now
preregister_builtin_ext
serves as a common point for registering such builtin extensions.To avoid superfluous deserialization the realm of
RuntimeBlob
is extended a bit and now it's required forcreate_wasm_runtime_with_code
.As a follow up work I can highlight that we still do support
Core_version
runtime API equivalently. I think we should deprecate it and encourage using the new custom wasm section based solution.