问题
In the main lib.rs
of substrate's runtime, (and also in the template node) there are several version attributes which can be changed - I'm guessing for tracking various build versions - but it's not clear how we are to use these in our own projects.
1) What are they for? What are the expectations for incrementing these in our own projects?
2) Are any one of these or combination intended to indicate incompatibility with a prior version of our runtimes, for example an incrementation of this indicates that the newer version is not compatible for storage, consensus or some other aspect that might be expected to cause a fork in the network?
pub const VERSION: RuntimeVersion = RuntimeVersion {
spec_name: create_runtime_str!("node"),
impl_name: create_runtime_str!("substrate-node"),
authoring_version: 10,
spec_version: 99,
impl_version: 104,
apis: RUNTIME_API_VERSIONS,
};
回答1:
Runtime versioning is an important part of the "forkless runtime upgrade" feature of Substrate based blockchains.
From core/sr-version at the time of this post:
/// Runtime version.
/// This should not be thought of as classic Semver (major/minor/tiny).
/// This triplet have different semantics and mis-interpretation could cause problems.
/// In particular: bug fixes should result in an increment of `spec_version` and possibly `authoring_version`,
/// absolutely not `impl_version` since they change the semantics of the runtime.
#[derive(Clone, PartialEq, Eq, Encode)]
#[cfg_attr(feature = "std", derive(Debug, Serialize, Decode))]
#[cfg_attr(feature = "std", serde(rename_all = "camelCase"))]
pub struct RuntimeVersion {
/// Identifies the different Substrate runtimes. There'll be at least polkadot and node.
/// A different on-chain spec_name to that of the native runtime would normally result
/// in node not attempting to sync or author blocks.
pub spec_name: RuntimeString,
/// Name of the implementation of the spec. This is of little consequence for the node
/// and serves only to differentiate code of different implementation teams. For this
/// codebase, it will be parity-polkadot. If there were a non-Rust implementation of the
/// Polkadot runtime (e.g. C++), then it would identify itself with an accordingly different
/// `impl_name`.
pub impl_name: RuntimeString,
/// `authoring_version` is the version of the authorship interface. An authoring node
/// will not attempt to author blocks unless this is equal to its native runtime.
pub authoring_version: u32,
/// Version of the runtime specification. A full-node will not attempt to use its native
/// runtime in substitute for the on-chain Wasm runtime unless all of `spec_name`,
/// `spec_version` and `authoring_version` are the same between Wasm and native.
pub spec_version: u32,
/// Version of the implementation of the specification. Nodes are free to ignore this; it
/// serves only as an indication that the code is different; as long as the other two versions
/// are the same then while the actual code may be different, it is nonetheless required to
/// do the same thing.
/// Non-consensus-breaking optimizations are about the only changes that could be made which
/// would result in only the `impl_version` changing.
pub impl_version: u32,
/// List of supported API "features" along with their versions.
#[cfg_attr(feature = "std", serde(serialize_with = "apis_serialize::serialize"))]
pub apis: ApisVec,
}
spec_version
is used to signify whether consensus-critical logic has changed, while impl_version
is used to signify changes which will not affect consensus in the network. For example, if the behavior of a function changes in the runtime, you must increment the spec_version
to note that this version of the runtime will not come to consensus with another version of the runtime. Whereas, if there was only an optimization made to a function, but the resulting output is the same, then only the impl_version
needs to be bumped.
Using the spec_version
, a node is able to determine whether the native version of the runtime (the native executable actually running the node) matches the Wasm version of the runtime (which is stored on-chain and the network has come to consensus with).
In the case where the native spec_name
, authoring_version
, and spec_version
of the runtime matches the versions of the Wasm runtime, the native runtime is used instead of the Wasm runtime since it is faster to execute. In the case where the spec_version
does not match exactly, the node will fall back to use the Wasm version of the runtime, ensuring that the node stays in consensus with the rest of the network.
If you want to follow the code path where this happens, you can start in core/sr-version.
impl RuntimeVersion {
/// Check if this version matches other version for calling into runtime.
pub fn can_call_with(&self, other: &RuntimeVersion) -> bool {
self.spec_version == other.spec_version &&
self.spec_name == other.spec_name &&
self.authoring_version == other.authoring_version
}
...
}
Then if you go into core/executor/native_executor.rs, you will see the can_call_with
function is used to determine if the native runtime can be used.
Edit: It is important to note that block construction execution engine always defaults to Wasm, while the importing execution engine tries to use native if possible, using the logic above.
来源:https://stackoverflow.com/questions/56873179/substrate-has-runtime-versioning-what-is-the-purpose-and-use-case-for-these