Skip to content
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

Update RuntimeVerison type and use system_version to derive extrinsics root StateVersion instead of V0 #4257

Merged
merged 26 commits into from
Sep 10, 2024

Conversation

vedhavyas
Copy link
Contributor

This PR

  • Renames RuntimeVersion::state_version to system_version
  • Uses Runtime::system_version to derive extrinsics root StateVersion instead of default StateVersion::V0

This PR should not be breaking any existing chains so long as they use same RuntimeVersion::state_version for Runtime::system_version

Using RuntimeVersion::system_version = 2 will make the extrinsics root to use StateVersion::V1 instead of V0

RFC for this change - polkadot-fellows/RFCs#42

@vedhavyas
Copy link
Contributor Author

@bkchr I had looked through the failed tests and not sure if the failures are related to changes in this PR. Can you confirm? If not where should I look at to fix these tests ?

@vedhavyas
Copy link
Contributor Author

Hey @koute @bkchr would appreciate some thoughts on the failing issues.
Also, any chance you can do a first pass/review this PR ?

@vedhavyas
Copy link
Contributor Author

Hey @bkchr @koute
Would be really great to this PR in. I will fix the conflicts in sometime but please have a look so that this is landed

@vedhavyas
Copy link
Contributor Author

@bkchr @koute bumping this again

@vedhavyas
Copy link
Contributor Author

@bkchr @koute bumping this again

@vedhavyas
Copy link
Contributor Author

@bkchr @koute bumping this again :)

@bkchr bkchr added the T1-FRAME This PR/Issue is related to core FRAME, the framework. label Jul 24, 2024
@bkchr
Copy link
Member

bkchr commented Jul 24, 2024

@vedhavyas sorry for the delay. Can you please fix the CI issues and merge master?

@vedhavyas
Copy link
Contributor Author

@bkchr yes will do. Thank you!

prdoc/pr_4257.prdoc Outdated Show resolved Hide resolved
Comment on lines 127 to 128
} else if field_name == "state_version" {
parse_once(&mut self.state_version, field_value, Self::parse_num_literal_u8)?;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Would be nice to still accept the old state_version for backwards compatibility and emit a deprecation message prompting the users to change to system_version.

Also, since this is a procedural macro it could be nice to allow people to specify the versions in a "nice" way instead of just having a hardcoded magic number, for example:

    // instead of "system_version: 0":
    state_version: StateVersion::V0,
    extrinsics_root_state_version: StateVersion::V0,

    // instead of "system_version: 1":
    state_version: StateVersion::V1,
    extrinsics_root_state_version: StateVersion::V0,

    // instead of "system_version: 2":
    state_version: StateVersion::V1,
    extrinsics_root_state_version: StateVersion::V1,

This would be nicer from a users' point of view, but I suppose it makes the macro even more "magic", so *shrug*. I'll leave it up to whatever @bkchr prefers.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is similar to my initial proposal but decided to not go that route since this adds another extra field to Runtimeversion. More here - polkadot-fellows/RFCs#42 (comment)

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm not talking about adding/changing the physical fields - the RFC's already accepted so that train already left the station. I'm just talking about providing potential syntax sugar in the macro which would turn the state_version + extrinsics_root_state_version into system_version.

A less magic alternative would be something like a const fn system_version(state_version: StateVersion, extrinsics_root_state_version: StateVersion) -> u8 or SystemVersion { state_version: ..., extrinsics_root_state_version: ... }.into() helper or something like that.

(But, again, I don't have a particularly strong opinion here which way to go here. But at very least I'd like to not force additional churn on people by breaking backwards compatibility.)

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

state_version + extrinsics_root_state_version into system_version.

Not sure we should do this.

The general idea of still supporting state_version sounds like a good proposal. So, it prints some warning and internally converts it to system_version.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

However, I would not separate the fields.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@vedhavyas and could you do this?

@vedhavyas
Copy link
Contributor Author

@bkchr seems like CI is stuck. Is there an way to restart it ?

@koute
Copy link
Contributor

koute commented Jul 26, 2024

seems like CI is stuck. Is there an way to restart it ?

Done.

@vedhavyas
Copy link
Contributor Author

@koute any idea why some tests failed. Looking at the CI logs of each failed test does not give much info. Any pointers pls ?

@bkchr
Copy link
Member

bkchr commented Jul 28, 2024

I think the zombienet tests are just flaky. @vedhavyas please give us merge rights to your branch, then we can also merge master for example

@nazar-pc
Copy link
Contributor

Had to introduce custom deserialization as well in order to allow "duplicated" field and make serialization/deserialization symmetrical

Comment on lines 550 to 558
Field::TransactionVersion => {
if transaction_version.is_some() {
return Err(<A::Error as serde::de::Error>::duplicate_field(
"transactionVersion",
));
}
transaction_version = Some(map.next_value()?);
},
Field::SystemVersion => {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If both exists, they should be the same value

transaction_version = Some(map.next_value()?);
},
Field::SystemVersion => {
system_version = Some(map.next_value()?);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Also you are missing here the check if this is already some.

formatter.write_str("field identifier")
}

fn visit_u64<E>(self, value: u64) -> Result<Self::Value, E>
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do we need this?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I didn't write it, I used macro expansion as the basis and this is what it spit out, I just made it a bit more readable

}
}

fn visit_bytes<E>(self, value: &[u8]) -> Result<Self::Value, E>
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

And this?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Same here, this is from Deserialize derive

@paritytech-cicd-pr
Copy link

The CI pipeline was cancelled due to failure one of the required jobs.
Job name: test-linux-stable-int
Logs: https://gitlab.parity.io/parity/mirrors/polkadot-sdk/-/jobs/7190974

if system_version != new_value {
return Err(<A::Error as serde::de::Error>::custom(
alloc::format!(
r#"Duplicated "stateVersion" and "systemVersion" \
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You don't really know this. It could also be that there are more than one stateVersion.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You're right, but it is one of those two fields. And I decided to make this assumption in an error message since it is the most likely. Open for suggestions.

// Manual implementation in order to sprinkle `stateVersion` at the end for migration purposes
// after the field was renamed from `state_version` to `system_version`
#[cfg(feature = "serde")]
impl serde::Serialize for RuntimeVersion {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can we move all this code into a separate file? 400 lines is a lot.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Would serde module work? I generally try to keep impls next to the definition, but agree that in this case it is quite lengthy.

// Manual implementation in order to sprinkle `stateVersion` at the end for migration purposes
// after the field was renamed from `state_version` to `system_version`
#[cfg(feature = "serde")]
impl serde::Serialize for RuntimeVersion {
Copy link
Contributor

@koute koute Sep 3, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hmm.... I don't think we need this serde boilerplate? Something like this should work:

use serde::{Serialize, Deserialize};

#[derive(Clone)]
pub struct RuntimeVersion {
    // ...
    pub system_version: u8
}

#[derive(Clone, Serialize, Deserialize)]
struct RuntimeVersionSerde {
    // ...
    system_version: Option<u8>,
    state_version: Option<u8>,
}

impl From<RuntimeVersion> for RuntimeVersionSerde {
    fn from(value: RuntimeVersion) -> Self {
        Self {
            // ...
            system_version: Some(value.system_version),
            state_version: Some(value.system_version),
        }
    }
}

impl From<RuntimeVersionSerde> for RuntimeVersion {
    fn from(value: RuntimeVersionSerde) -> Self {
        Self {
            // ..
            system_version: value.system_version.or(value.state_version)
                .unwrap() // TODO: Properly handle if both/none are there.
        }
    }
}

impl serde::Serialize for RuntimeVersion {
    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
    where
        S: serde::Serializer,
    {
        RuntimeVersionSerde::from(self.clone()).serialize(serializer)
    }
}

impl<'de> serde::Deserialize<'de> for RuntimeVersion {
    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
    where
        D: serde::Deserializer<'de>,
    {
        Ok(RuntimeVersionSerde::deserialize(deserializer)?.into())
    }
}

(The From impls are for illustrative purposes; it's probably better to just inline the impls into the Serialize/Deserialize.)

Basically - use an auxiliary struct to do the serialization/deserialization, and then just convert between the real RuntimeVersion and the auxiliary one. You still need some boilerplate to copy the fields, but it should be much less than the serde junk and be much easier to maintain.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This looks appealing, but Option and missing field are not 100% the same for serde. How big of an issue is this really?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah, just let's take the current code.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This looks appealing, but Option and missing field are not 100% the same for serde. How big of an issue is this really?

@nazar-pc What do you mean are not the same? What exactly is the problem? You can implement strictly the same behavior as you've currently implemented, just with a lot less code (and more elegant) if you use a wrapper like I've suggested.

I'm fine with taking the existing code if it's truly necessary, but if not I'd rather have a simplified version that won't be pain to maintain and won't require you to read a ton of serde boilerplate to understand what it does.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Serde is not used just for JSON, so while Some(u8) and u8 encode identically in JSON, I do not know if it is actually the case for all formats that serde supports, so the change is not strictly equivalent in serialization case.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Same thing with deserialization: you can deserialize a number into both Option<u8> and u8 from JSON, but I do not think it is guaranteed to be symmetrical that way with other formats. If we do not care then fine, but I was trying to make it actually equivalent and didn't find a clean way to do so without manual implementation.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Well, as far as I know the only format we ever explicitly supported was JSON (we just used serde because that's the "standard" way to support JSON), and if we care about formats other than JSON then ideally we should have tests for serialization/deserialization of those formats, otherwise considering how flexible serde is we essentially always risk breaking some unknown code somewhere. And we're already partially breaking compatibility anyway by having it serialize an extra field!

So, to reiterate my policy here: if there's a good reason to keep things complex, sure, go for it, but if it's something that's "just in case" and we don't have any idea at all whether it's necessary, then I just prefer to keep things as simple as possible.

Anyway, honestly, taking a step back: the simplest thing would have been to leave the name state_version as state_version and just introduce the possibility of it being 2, and then all of this could have been avoided! We wouldn't have to worry whether this breaks anything, we wouldn't need custom serialization/deserialization, we wouldn't need deprecation warnings, consumers of RuntimeVersion that don't expect the extra field wouldn't break (if there are any then they will break now!), we wouldn't have to remove the state_version from the serialized RuntimeVersion in the future potentially breaking consumers yet again (or, alternatively, we wouldn't leave those two fields in there forever - do you have a plan for when to remove the state_version?), and this PR would have been already merged long ago. The only advantage of renaming it is, as far as I can see, a slightly nicer name, and a big list of pain and disadvantages to go with it.

The RFC was already accepted by the fellowship, so I won't block this, but my opinion is that I would highly prefer to just leave the old name as it was, delete all of the new extra code and call it a day.

Anyway, okay, sorry, rant over. If Big Boss @bkchr's fine with this then I'm also fine; I don't like it, but I don't see any hard blockers. :P

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Although, now that I think about it again, isn't emitting both state_version and system_version non-compliant with the RFC? The RFC stated that only system_version should be there. For polkadot-sdk internally we're fine to do whatever we want, but on external community/Polkadot interfaces (like the serialized RuntimeVersion struct) what is in the Polkadot spec and in fellowship RFCs takes precedence over what we want, so shouldn't we only emit system_version or update the RFC?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

For SCALE the encoding is as documented in the RFC. For others consuming the type via JSON (which is self descriptive and it is observable that the field name changed), they can continue to use their old code without it breaking.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

(I'm also not 100% happy with the 400 lines of useless code, but there is no real better solution...)

@nazar-pc
Copy link
Contributor

nazar-pc commented Sep 5, 2024

If there are no blockers here, I'd really appreciate finally merging this and be done with it 🙏

@bkchr
Copy link
Member

bkchr commented Sep 6, 2024

@koute needs to approve :P

@bkchr bkchr enabled auto-merge September 8, 2024 20:53
@bkchr
Copy link
Member

bkchr commented Sep 8, 2024

@nazar-pc we need a master merge again 🙈

auto-merge was automatically disabled September 9, 2024 11:15

Head branch was pushed to by a user without write access

@bkchr bkchr added this pull request to the merge queue Sep 10, 2024
Merged via the queue into paritytech:master with commit 9930d21 Sep 10, 2024
200 of 203 checks passed
@nazar-pc nazar-pc deleted the system_version branch September 10, 2024 07:58
@liuchengxu
Copy link
Contributor

There is a leftover in chain sync :(

let got = HashingFor::<Block>::ordered_trie_root(
body.iter().map(Encode::encode).collect(),
sp_runtime::StateVersion::V0,
);

cc @nazar-pc

bkchr added a commit that referenced this pull request Sep 12, 2024
With the introduction of `system_version` in #4257 the
extrinsic root may also use the `V1` layout. At this point in the sync code it would require some special
handling to find out the `system_version`. So, this pull request is removing it. The extrinsics root is checked
when executing the block later, so that at least no invalid block gets imported.
@bkchr
Copy link
Member

bkchr commented Sep 12, 2024

#5686

nazar-pc pushed a commit to autonomys/polkadot-sdk that referenced this pull request Sep 27, 2024
…nsics root `StateVersion` instead of `V0` (paritytech#4257)

This PR
- Renames `RuntimeVersion::state_version` to `system_version`
- Uses `Runtime::system_version` to derive extrinsics root
`StateVersion` instead of default `StateVersion::V0`

This PR should not be breaking any existing chains so long as they use
same `RuntimeVersion::state_version` for `Runtime::system_version`

Using `RuntimeVersion::system_version = 2` will make the extrinsics root
to use `StateVersion::V1` instead of `V0`

RFC for this change - polkadot-fellows/RFCs#42

---------

Co-authored-by: Bastian Köcher <[email protected]>
Co-authored-by: Koute <[email protected]>
Co-authored-by: Nazar Mokrynskyi <[email protected]>

(cherry picked from commit 9930d21)
nazar-pc pushed a commit to autonomys/polkadot-sdk that referenced this pull request Sep 27, 2024
With the introduction of `system_version` in paritytech#4257 the
extrinsic root may also use the `V1` layout. At this point in the sync code it would require some special
handling to find out the `system_version`. So, this pull request is removing it. The extrinsics root is checked
when executing the block later, so that at least no invalid block gets imported.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
T1-FRAME This PR/Issue is related to core FRAME, the framework.
Projects
None yet
Development

Successfully merging this pull request may close these issues.

6 participants