-
Notifications
You must be signed in to change notification settings - Fork 2.6k
Staging Runtime API implementation #11577
Comments
What is the stable staging api? The one that is "actually" stable but not yet enabled by the runtime? In general I find this a good idea, what I did not yet understood. How many "different" versions do you need? Currently we have one version for a runtime api. I think we should keep this "main version" for when we need to iterate on the stable api, aka stuff that doesn't need interventions by the council. Then we would like to have one "staging version", which would be different to the "main version" as long as it wasn't yet enacted by the council? Meaning both can also be the same when the staging one is enacted. And then you also want to have versions around new functions that you added to the runtime api that are only used in test nets? Do we need there one version per "unstable staging api function" or would it be enough to have one version for the "unstable staging api"? So, that in the end we have in maximum 3 different versions per runtime api? "main version", "stable staging version" and "unstable staging version"? Most of this stuff could already be done today, relative easily on the runtime side. We could add some kind of check There are multiple things to consider:
For me the main points that need to be cleared are around how many different versions we need per runtime api. I hope that the 3 above I outlined are enough. Then the rest should be relative simple. |
Sorry about that. It was a typo. It should read as STABLE API.
Probably my typo lead to this question, but let's clear this. We need two versions of the runtime - a stable and a staging one. Both APIs can 'grow' linearly. I don't expect to have forks. I think this is similar to the current situation. We've got a stable api v2. At some point it will become v3. The staging one will be the same but the versions will go faster. You do a change - bump the version. Another change - another bump and so on.
I am very unfamiliar with the council part but I think we don't want to change this behaviour. The council might decide that a specific network can have the staging API enabled. Or it might be just hardcoded for Versi and that's all. @rphmeier could you please share your thoughts about this paragraph? |
The most basic requirements of this feature at the Substrate level:
In Polkadot, we will then create a special "vstaging" version of the parachain-host runtime API which will eventually become v3. We'll upgrade through normal runtime upgrades. Rococo/Westend will implement v3 before Kusama, which will implement v3 before Polkadot. Before any of that, we'd run 'vstaging' on Versi intensively. ^ This is the first thing that should be implemented and is probably not more than a day or two of work. We discussed other stretch features which are not as necessary but will make things easier in the long run. These should not be implemented right away. Experimental API bundles: Grouping new functions together, so they are 'bundled' and only enabled together as an experimental staging API. Runtime API version could be treated as a bitfield where bits indicate which experimental feature bundles are enabled. |
We could do the following:
With staging enabled:
This would return as runtime api version Without staging enabled:
This would return as runtime api version WDYT? |
This suits our needs for the moment just fine. |
However, shortly after that we will want the ability to choose the runtime API version we implement in a runtime beyond just |
So something like this? |
Yes, that'd definitely work. We don't need the 'staging' nomenclature in Substrate itself because we can make that distinction at a higher level. |
btw I think @tdimitrov is happy to help as long as he has some guidance on the required code changes. @tdimitrov have you worked on proc-macros before? |
And I'm happy if I don't need to do it :D I can help as needed with instructions :) |
Yes I will help with the implementation. I've got basic knowledge about proc-macros but I'll ramp up. The task doesn't seem too complicated (I hope) :) One question:
What about the case when another staging function is added to the staging API? We will want to differentiate between the two versions, won't we? |
The latest "iteration" didn't included this staging notion anymore. So, you can just ignore it. We want to go the way with the manual versioning as highlighted in my second last post above. |
Yep, that'll be for later. For reference, we will make modifications for something like #11577 (comment) and then make the 'staging' distinction only at the higher level with Polkadot for the moment. |
Related to issue paritytech#11577 Add support for multiple versions of a Runtime API. The purpose is to have one main version of the API, which is considered stable and multiple unstable (aka staging) ones. How it works =========== Some methods of the API trait can be tagged with `#[api_version(N)]` attribute where N is version number bigger than the main one. Let's call them **staging methods** for brevity. The implementor of the API decides which version to implement. Example (from paritytech#11577 (comment)): ``` decl_runtime_apis! { #{api_version(10)] trait Test { fn something() -> Vec<u8>; #[api_version(11)] fn new_cool_function() -> u32; } } ``` ``` impl_runtime_apis! { #[api_version(11)] impl Test for Runtime { fn something() -> Vec<u8> { vec![1, 2, 3] } fn new_cool_function() -> u32 { 10 } } } ``` Version safety checks (currently not implemented) ================================================= By default in the API trait all staging methods has got default implementation calling `unimplemented!()`. This is a problem because if the developer wants to implement version 11 in the example above and forgets to add `fn new_cool_function()` in `impl_runtime_apis!` the runtime will crash when the function is executed. Ideally a compilation error should be generated in such cases. TODOs ===== Things not working well at the moment: [ ] Version safety check [ ] Integration tests of `primitives/api` are messed up a bit. More specifically `primitives/api/test/tests/decl_and_impl.rs` [ ] Integration test covering the new functionality. [ ] Some duplicated code
@rphmeier @bkchr may I have some initial feedback on this: #11779 ? It needs more work, but I want to check if I'm in the right direction. The biggest issue so far is how to implement safety check for an unimplemented method. E.g. in @bkchr 's example above - if
|
Let me restate the problem I spoke about in the last comment again:
it will generate a trait similar to:
The important part here is that the staging function has got a default implementation.
We state that we implement version 11 but we don't provide implementation for What we can do?With
Note that one more trait is generated - Then
This way if a method mandatory for V11 is missed - the code won't compile. The error will be a bit weird, but at least the problem will be caught compile time. |
Related to issue paritytech#11577 Add support for multiple versions of a Runtime API. The purpose is to have one main version of the API, which is considered stable and multiple unstable (aka staging) ones. How it works =========== Some methods of the API trait can be tagged with `#[api_version(N)]` attribute where N is version number bigger than the main one. Let's call them **staging methods** for brevity. The implementor of the API decides which version to implement. Example (from paritytech#11577 (comment)): ``` decl_runtime_apis! { #{api_version(10)] trait Test { fn something() -> Vec<u8>; #[api_version(11)] fn new_cool_function() -> u32; } } ``` ``` impl_runtime_apis! { #[api_version(11)] impl Test for Runtime { fn something() -> Vec<u8> { vec![1, 2, 3] } fn new_cool_function() -> u32 { 10 } } } ``` Version safety checks (currently not implemented) ================================================= By default in the API trait all staging methods has got default implementation calling `unimplemented!()`. This is a problem because if the developer wants to implement version 11 in the example above and forgets to add `fn new_cool_function()` in `impl_runtime_apis!` the runtime will crash when the function is executed. Ideally a compilation error should be generated in such cases. TODOs ===== Things not working well at the moment: [ ] Version safety check [ ] Integration tests of `primitives/api` are messed up a bit. More specifically `primitives/api/test/tests/decl_and_impl.rs` [ ] Integration test covering the new functionality. [ ] Some duplicated code
I did a quick and dirty implementation of the above idea in the PR. Then I've added the modified substrate version in Polkadot. You'll see a method with First I tried to add a method with a version older than the stable one. Note that the api is V2, while
Compilation error:
Then I declared I am implementing version 3 and didn't add a required method:
The compilation error is:
They look almost like real to me :) What do you think? This is the branch I used for testing: https://github.com/tdimitrov/polkadot/tree/substrate-local-test |
Could you elaborate on why this is big trouble?
I think this isn't substantially different from a user implementing the runtime API as an The only issue is if not implementing the new runtime APIs does not issue a warning or error to the user, which is a developer experience issue. Less than ideal, and if so, it should be documented as a major caveat. But acceptable if there's no simple way to introduce that warning or error. But the example you showed of implementing In short, nice work! |
I see it as a big trouble because the developer can implement a runtime version without providing all required method implementations. And when any such method is called - runtime will crash. In my eyes this was an apocalyptic outcome but you explained this is not the case :) I think a found a nice 'hack' which yields compilation error if a required method implementation is missing. The idea came after I wrote the comment above. I spoke with @bkchr about my idea. He has got some reservations about it so let's wait for his feedback too. Until then I'll refactor my changes a bit. I wanted to see how it will work out and I cut a lot of corners with the implementation. |
I've created a polkadot branch based on this PR. It does two things:
I've built a single binary from this branch and ran it with zombienet with polkadot and versi chainspec. The versioning works good. In versi the new method was executed. In polkadot the runtime version check here and here kicked in:
|
Related to issue paritytech#11577 Add support for multiple versions of a Runtime API. The purpose is to have one main version of the API, which is considered stable and multiple unstable (aka staging) ones. How it works =========== Some methods of the API trait can be tagged with `#[api_version(N)]` attribute where N is version number bigger than the main one. Let's call them **staging methods** for brevity. The implementor of the API decides which version to implement. Example (from paritytech#11577 (comment)): ``` decl_runtime_apis! { #{api_version(10)] trait Test { fn something() -> Vec<u8>; #[api_version(11)] fn new_cool_function() -> u32; } } ``` ``` impl_runtime_apis! { #[api_version(11)] impl Test for Runtime { fn something() -> Vec<u8> { vec![1, 2, 3] } fn new_cool_function() -> u32 { 10 } } } ``` Version safety checks (currently not implemented) ================================================= By default in the API trait all staging methods has got default implementation calling `unimplemented!()`. This is a problem because if the developer wants to implement version 11 in the example above and forgets to add `fn new_cool_function()` in `impl_runtime_apis!` the runtime will crash when the function is executed. Ideally a compilation error should be generated in such cases. TODOs ===== Things not working well at the moment: [ ] Version safety check [ ] Integration tests of `primitives/api` are messed up a bit. More specifically `primitives/api/test/tests/decl_and_impl.rs` [ ] Integration test covering the new functionality. [ ] Some duplicated code
Related to issue paritytech#11577 Add support for multiple versions of a Runtime API. The purpose is to have one main version of the API, which is considered stable and multiple unstable (aka staging) ones. How it works =========== Some methods of the API trait can be tagged with `#[api_version(N)]` attribute where N is version number bigger than the main one. Let's call them **staging methods** for brevity. The implementor of the API decides which version to implement. Example (from paritytech#11577 (comment)): ``` decl_runtime_apis! { #{api_version(10)] trait Test { fn something() -> Vec<u8>; #[api_version(11)] fn new_cool_function() -> u32; } } ``` ``` impl_runtime_apis! { #[api_version(11)] impl Test for Runtime { fn something() -> Vec<u8> { vec![1, 2, 3] } fn new_cool_function() -> u32 { 10 } } } ``` Version safety checks (currently not implemented) ================================================= By default in the API trait all staging methods has got default implementation calling `unimplemented!()`. This is a problem because if the developer wants to implement version 11 in the example above and forgets to add `fn new_cool_function()` in `impl_runtime_apis!` the runtime will crash when the function is executed. Ideally a compilation error should be generated in such cases. TODOs ===== Things not working well at the moment: [ ] Version safety check [ ] Integration tests of `primitives/api` are messed up a bit. More specifically `primitives/api/test/tests/decl_and_impl.rs` [ ] Integration test covering the new functionality. [ ] Some duplicated code
Related to issue paritytech#11577 Add support for multiple versions of a Runtime API. The purpose is to have one main version of the API, which is considered stable and multiple unstable (aka staging) ones. How it works =========== Some methods of the API trait can be tagged with `#[api_version(N)]` attribute where N is version number bigger than the main one. Let's call them **staging methods** for brevity. The implementor of the API decides which version to implement. Example (from paritytech#11577 (comment)): ``` decl_runtime_apis! { #{api_version(10)] trait Test { fn something() -> Vec<u8>; #[api_version(11)] fn new_cool_function() -> u32; } } ``` ``` impl_runtime_apis! { #[api_version(11)] impl Test for Runtime { fn something() -> Vec<u8> { vec![1, 2, 3] } fn new_cool_function() -> u32 { 10 } } } ``` Version safety checks (currently not implemented) ================================================= By default in the API trait all staging methods has got default implementation calling `unimplemented!()`. This is a problem because if the developer wants to implement version 11 in the example above and forgets to add `fn new_cool_function()` in `impl_runtime_apis!` the runtime will crash when the function is executed. Ideally a compilation error should be generated in such cases. TODOs ===== Things not working well at the moment: [ ] Version safety check [ ] Integration tests of `primitives/api` are messed up a bit. More specifically `primitives/api/test/tests/decl_and_impl.rs` [ ] Integration test covering the new functionality. [ ] Some duplicated code
* Runtime API versioning Related to issue #11577 Add support for multiple versions of a Runtime API. The purpose is to have one main version of the API, which is considered stable and multiple unstable (aka staging) ones. How it works =========== Some methods of the API trait can be tagged with `#[api_version(N)]` attribute where N is version number bigger than the main one. Let's call them **staging methods** for brevity. The implementor of the API decides which version to implement. Example (from #11577 (comment)): ``` decl_runtime_apis! { #{api_version(10)] trait Test { fn something() -> Vec<u8>; #[api_version(11)] fn new_cool_function() -> u32; } } ``` ``` impl_runtime_apis! { #[api_version(11)] impl Test for Runtime { fn something() -> Vec<u8> { vec![1, 2, 3] } fn new_cool_function() -> u32 { 10 } } } ``` Version safety checks (currently not implemented) ================================================= By default in the API trait all staging methods has got default implementation calling `unimplemented!()`. This is a problem because if the developer wants to implement version 11 in the example above and forgets to add `fn new_cool_function()` in `impl_runtime_apis!` the runtime will crash when the function is executed. Ideally a compilation error should be generated in such cases. TODOs ===== Things not working well at the moment: [ ] Version safety check [ ] Integration tests of `primitives/api` are messed up a bit. More specifically `primitives/api/test/tests/decl_and_impl.rs` [ ] Integration test covering the new functionality. [ ] Some duplicated code * Update primitives/api/proc-macro/src/impl_runtime_apis.rs Code review feedback and formatting Co-authored-by: asynchronous rob <rphmeier@gmail.com> * Code review feedback Applying suggestions from @bkchr * fmt * Apply suggestions from code review Co-authored-by: Bastian Köcher <bkchr@users.noreply.github.com> * Code review feedback * dummy trait -> versioned trait * Implement only versioned traits (not compiling) * Remove native API calls (still not compiling) * fmt * Fix compilation * Comments * Remove unused code * Remove native runtime tests * Remove unused code * Fix UI tests * Code review feedback * Code review feedback * attribute_names -> common * Rework `append_api_version` * Code review feedback * Apply suggestions from code review Co-authored-by: Bastian Köcher <bkchr@users.noreply.github.com> * Code review feedback * Code review feedback * Code review feedback * Use type alias for the default trait - doesn't compile * Fixes * Better error for `method_api_ver < trait_api_version` * fmt * Rework how we call runtime functions * Update UI tests * Fix warnings * Fix doctests * Apply suggestions from code review Co-authored-by: Bastian Köcher <bkchr@users.noreply.github.com> * Apply suggestions from code review Co-authored-by: Bastian Köcher <bkchr@users.noreply.github.com> * Fix formatting and small compilation errors * Update primitives/api/proc-macro/src/impl_runtime_apis.rs Co-authored-by: Bastian Köcher <bkchr@users.noreply.github.com> Co-authored-by: asynchronous rob <rphmeier@gmail.com> Co-authored-by: Bastian Köcher <bkchr@users.noreply.github.com> Co-authored-by: Bastian Köcher <info@kchr.de>
Fixed with #11779 |
* Runtime API versioning Related to issue paritytech#11577 Add support for multiple versions of a Runtime API. The purpose is to have one main version of the API, which is considered stable and multiple unstable (aka staging) ones. How it works =========== Some methods of the API trait can be tagged with `#[api_version(N)]` attribute where N is version number bigger than the main one. Let's call them **staging methods** for brevity. The implementor of the API decides which version to implement. Example (from paritytech#11577 (comment)): ``` decl_runtime_apis! { #{api_version(10)] trait Test { fn something() -> Vec<u8>; #[api_version(11)] fn new_cool_function() -> u32; } } ``` ``` impl_runtime_apis! { #[api_version(11)] impl Test for Runtime { fn something() -> Vec<u8> { vec![1, 2, 3] } fn new_cool_function() -> u32 { 10 } } } ``` Version safety checks (currently not implemented) ================================================= By default in the API trait all staging methods has got default implementation calling `unimplemented!()`. This is a problem because if the developer wants to implement version 11 in the example above and forgets to add `fn new_cool_function()` in `impl_runtime_apis!` the runtime will crash when the function is executed. Ideally a compilation error should be generated in such cases. TODOs ===== Things not working well at the moment: [ ] Version safety check [ ] Integration tests of `primitives/api` are messed up a bit. More specifically `primitives/api/test/tests/decl_and_impl.rs` [ ] Integration test covering the new functionality. [ ] Some duplicated code * Update primitives/api/proc-macro/src/impl_runtime_apis.rs Code review feedback and formatting Co-authored-by: asynchronous rob <rphmeier@gmail.com> * Code review feedback Applying suggestions from @bkchr * fmt * Apply suggestions from code review Co-authored-by: Bastian Köcher <bkchr@users.noreply.github.com> * Code review feedback * dummy trait -> versioned trait * Implement only versioned traits (not compiling) * Remove native API calls (still not compiling) * fmt * Fix compilation * Comments * Remove unused code * Remove native runtime tests * Remove unused code * Fix UI tests * Code review feedback * Code review feedback * attribute_names -> common * Rework `append_api_version` * Code review feedback * Apply suggestions from code review Co-authored-by: Bastian Köcher <bkchr@users.noreply.github.com> * Code review feedback * Code review feedback * Code review feedback * Use type alias for the default trait - doesn't compile * Fixes * Better error for `method_api_ver < trait_api_version` * fmt * Rework how we call runtime functions * Update UI tests * Fix warnings * Fix doctests * Apply suggestions from code review Co-authored-by: Bastian Köcher <bkchr@users.noreply.github.com> * Apply suggestions from code review Co-authored-by: Bastian Köcher <bkchr@users.noreply.github.com> * Fix formatting and small compilation errors * Update primitives/api/proc-macro/src/impl_runtime_apis.rs Co-authored-by: Bastian Köcher <bkchr@users.noreply.github.com> Co-authored-by: asynchronous rob <rphmeier@gmail.com> Co-authored-by: Bastian Köcher <bkchr@users.noreply.github.com> Co-authored-by: Bastian Köcher <info@kchr.de>
This is a follow up from #11338, paritytech/polkadot#5048 and some offline discussions.
What we need
I write this from the point of view of a substrate user in polkadot. This problem might not be relevant for other projects.
At the moment we use the primitives provided by substrate to build versioned runtime API. We have got different runtimes (for polkadot, kusama, rococo, etc) but they implement the same runtime API interface. For specific runtimes (rococo to be concrete) we want to introduce breaking changes like:
Additionally we need versioning independent from the one of the stable
stagingAPI. We expect the development around it to be dynamic so we should be able to create new versions easily.A quote from @eskimor from the linked discussion:
Another one regarding the versioning:
And a final one from @rphmeier :
Things we want to avoid (if possible)
The text was updated successfully, but these errors were encountered: