Implement runtime version checks in set_code (#4548)

* Implement runtime version checks in `set_code`

Check that the new runtime code given to `set_code` fullfills some
requirements:

- `spec_name` matches
- `spec_version` does not decreases
- `impl_version` does not decreases
- Either `spec_version` and `impl_version` increase

* Make tests almost work

* Some fixes after master merge

* Fix tests

* Add missed file

* Make depedency check happy?

* Remove leftover `sc-executor`

* AHHHHH

* Reset debug stuff

* Remove some 'static

* More 'static

* Some docs

* Update `Cargo.lock`
This commit is contained in:
Bastian Köcher
2020-01-16 13:58:37 +01:00
committed by Gavin Wood
parent 437772be9e
commit afc3318f21
38 changed files with 584 additions and 279 deletions
+28 -1
View File
@@ -80,7 +80,7 @@ sp_externalities::decl_extension! {
}
/// Code execution engine.
pub trait CodeExecutor: Sized + Send + Sync {
pub trait CodeExecutor: Sized + Send + Sync + CallInWasm + Clone + 'static {
/// Externalities error type.
type Error: Display + Debug + Send + 'static;
@@ -99,3 +99,30 @@ pub trait CodeExecutor: Sized + Send + Sync {
native_call: Option<NC>,
) -> (Result<crate::NativeOrEncoded<R>, Self::Error>, bool);
}
/// Something that can call a method in a WASM blob.
pub trait CallInWasm: Send + Sync {
/// Call the given `method` in the given `wasm_blob` using `call_data` (SCALE encoded arguments)
/// to decode the arguments for the method.
///
/// Returns the SCALE encoded return value of the method.
fn call_in_wasm(
&self,
wasm_blob: &[u8],
method: &str,
call_data: &[u8],
ext: &mut dyn Externalities,
) -> Result<Vec<u8>, String>;
}
sp_externalities::decl_extension! {
/// The call-in-wasm extension to register/retrieve from the externalities.
pub struct CallInWasmExt(Box<dyn CallInWasm>);
}
impl CallInWasmExt {
/// Creates a new instance of `Self`.
pub fn new<T: CallInWasm + 'static>(inner: T) -> Self {
Self(Box::new(inner))
}
}
@@ -8,4 +8,4 @@ edition = "2018"
[dependencies]
sp-storage = { version = "2.0.0", path = "../storage" }
sp-std = { version = "2.0.0", path = "../std" }
environmental = { version = "1.0.2" }
environmental = { version = "1.1.1" }
+21 -2
View File
@@ -35,7 +35,7 @@ use sp_std::ops::Deref;
#[cfg(feature = "std")]
use sp_core::{
crypto::Pair,
traits::KeystoreExt,
traits::{KeystoreExt, CallInWasmExt},
offchain::{OffchainExt, TransactionPoolExt},
hexdisplay::HexDisplay,
storage::{ChildStorageKey, ChildInfo},
@@ -49,7 +49,7 @@ use sp_core::{
};
#[cfg(feature = "std")]
use ::sp_trie::{TrieConfiguration, trie_types::Layout};
use sp_trie::{TrieConfiguration, trie_types::Layout};
use sp_runtime_interface::{runtime_interface, Pointer};
@@ -351,6 +351,25 @@ pub trait Misc {
fn print_hex(data: &[u8]) {
log::debug!(target: "runtime", "{}", HexDisplay::from(&data));
}
/// Extract the runtime version of the given wasm blob by calling `Core_version`.
///
/// Returns the SCALE encoded runtime version and `None` if the call failed.
///
/// # Performance
///
/// Calling this function is very expensive and should only be done very occasionally.
/// For getting the runtime version, it requires instantiating the wasm blob and calling a
/// function in this blob.
fn runtime_version(&mut self, wasm: &[u8]) -> Option<Vec<u8>> {
// Create some dummy externalities, `Core_version` should not write data anyway.
let mut ext = sp_state_machine::BasicExternalities::default();
self.extension::<CallInWasmExt>()
.expect("No `CallInWasmExt` associated for the current context!")
.call_in_wasm(wasm, "Core_version", &[], &mut ext)
.ok()
}
}
/// Interfaces for working with crypto related types from within the runtime.
@@ -10,7 +10,6 @@ sp-std = { version = "2.0.0", default-features = false, path = "../std" }
sp-runtime-interface-proc-macro = { version = "2.0.0", path = "proc-macro" }
sp-externalities = { version = "0.8.0", optional = true, path = "../externalities" }
codec = { package = "parity-scale-codec", version = "1.0.6", default-features = false }
environmental = { version = "1.0.2", optional = true }
static_assertions = "1.0.0"
primitive-types = { version = "0.6.1", default-features = false }
@@ -29,7 +28,6 @@ std = [
"sp-std/std",
"codec/std",
"sp-externalities",
"environmental",
"primitive-types/std",
]
@@ -30,7 +30,6 @@ fn call_wasm_method<HF: HostFunctionsT>(method: &str) -> TestExternalities {
let mut ext_ext = ext.ext();
sc_executor::call_in_wasm::<
_,
(
HF,
sp_io::SubstrateHostFunctions,
+3 -21
View File
@@ -53,6 +53,9 @@ pub mod testing;
pub mod traits;
pub mod transaction_validity;
pub mod random_number_generator;
mod runtime_string;
pub use crate::runtime_string::*;
/// Re-export these since they're only "kind of" generic.
pub use generic::{DigestItem, Digest};
@@ -92,27 +95,6 @@ impl TypeId for ModuleId {
const TYPE_ID: [u8; 4] = *b"modl";
}
/// A String that is a `&'static str` on `no_std` and a `Cow<'static, str>` on `std`.
#[cfg(feature = "std")]
pub type RuntimeString = std::borrow::Cow<'static, str>;
/// A String that is a `&'static str` on `no_std` and a `Cow<'static, str>` on `std`.
#[cfg(not(feature = "std"))]
pub type RuntimeString = &'static str;
/// Create a const [`RuntimeString`].
#[cfg(feature = "std")]
#[macro_export]
macro_rules! create_runtime_str {
( $y:expr ) => {{ std::borrow::Cow::Borrowed($y) }}
}
/// Create a const [`RuntimeString`].
#[cfg(not(feature = "std"))]
#[macro_export]
macro_rules! create_runtime_str {
( $y:expr ) => {{ $y }}
}
#[cfg(feature = "std")]
pub use serde::{Serialize, Deserialize, de::DeserializeOwned};
use crate::traits::IdentifyAccount;
@@ -0,0 +1,117 @@
// Copyright 2020 Parity Technologies (UK) Ltd.
// This file is part of Substrate.
// Substrate is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
// Substrate is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
// You should have received a copy of the GNU General Public License
// along with Substrate. If not, see <http://www.gnu.org/licenses/>.
use codec::{Encode, Decode};
use sp_core::RuntimeDebug;
use sp_std::vec::Vec;
/// A string that wraps a `&'static str` in the runtime and `String`/`Vec<u8>` on decode.
#[derive(Eq, RuntimeDebug, Clone)]
pub enum RuntimeString {
/// The borrowed mode that wraps a `&'static str`.
Borrowed(&'static str),
/// The owned mode that wraps a `String`.
#[cfg(feature = "std")]
Owned(String),
/// The owned mode that wraps a `Vec<u8>`.
#[cfg(not(feature = "std"))]
Owned(Vec<u8>),
}
impl From<&'static str> for RuntimeString {
fn from(data: &'static str) -> Self {
Self::Borrowed(data)
}
}
#[cfg(feature = "std")]
impl From<RuntimeString> for String {
fn from(string: RuntimeString) -> Self {
match string {
RuntimeString::Borrowed(data) => data.to_owned(),
RuntimeString::Owned(data) => data,
}
}
}
impl Default for RuntimeString {
fn default() -> Self {
Self::Borrowed(Default::default())
}
}
impl PartialEq for RuntimeString {
fn eq(&self, other: &Self) -> bool {
self.as_ref() == other.as_ref()
}
}
impl AsRef<[u8]> for RuntimeString {
fn as_ref(&self) -> &[u8] {
match self {
Self::Borrowed(val) => val.as_ref(),
Self::Owned(val) => val.as_ref(),
}
}
}
impl Encode for RuntimeString {
fn encode(&self) -> Vec<u8> {
match self {
Self::Borrowed(val) => val.encode(),
Self::Owned(val) => val.encode(),
}
}
}
impl Decode for RuntimeString {
fn decode<I: codec::Input>(value: &mut I) -> Result<Self, codec::Error> {
Decode::decode(value).map(Self::Owned)
}
}
#[cfg(feature = "std")]
impl std::fmt::Display for RuntimeString {
fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
match self {
Self::Borrowed(val) => write!(f, "{}", val),
Self::Owned(val) => write!(f, "{}", val),
}
}
}
#[cfg(feature = "std")]
impl serde::Serialize for RuntimeString {
fn serialize<S: serde::Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
match self {
Self::Borrowed(val) => val.serialize(serializer),
Self::Owned(val) => val.serialize(serializer),
}
}
}
#[cfg(feature = "std")]
impl<'de> serde::Deserialize<'de> for RuntimeString {
fn deserialize<D: serde::Deserializer<'de>>(de: D) -> Result<Self, D::Error> {
String::deserialize(de).map(Self::Owned)
}
}
/// Create a const [`RuntimeString`].
#[macro_export]
macro_rules! create_runtime_str {
( $y:expr ) => {{ $crate::RuntimeString::Borrowed($y) }}
}
+22 -7
View File
@@ -24,7 +24,7 @@ use hash_db::Hasher;
use codec::{Decode, Encode, Codec};
use sp_core::{
storage::{well_known_keys, ChildInfo}, NativeOrEncoded, NeverNativeValue,
traits::CodeExecutor, hexdisplay::HexDisplay
traits::{CodeExecutor, CallInWasmExt}, hexdisplay::HexDisplay
};
use overlayed_changes::OverlayedChangeSet;
use sp_externalities::Extensions;
@@ -191,7 +191,7 @@ pub struct StateMachine<'a, B, H, N, T, Exec>
impl<'a, B, H, N, T, Exec> StateMachine<'a, B, H, N, T, Exec> where
H: Hasher,
H::Out: Ord + 'static + codec::Codec,
Exec: CodeExecutor,
Exec: CodeExecutor + Clone + 'static,
B: Backend<H>,
T: ChangesTrieStorage<H, N>,
N: crate::changes_trie::BlockNumber,
@@ -204,8 +204,10 @@ impl<'a, B, H, N, T, Exec> StateMachine<'a, B, H, N, T, Exec> where
exec: &'a Exec,
method: &'a str,
call_data: &'a [u8],
extensions: Extensions,
mut extensions: Extensions,
) -> Self {
extensions.register(CallInWasmExt::new(exec.clone()));
Self {
backend,
exec,
@@ -451,7 +453,7 @@ where
B: Backend<H>,
H: Hasher,
H::Out: Ord + 'static + codec::Codec,
Exec: CodeExecutor,
Exec: CodeExecutor + Clone + 'static,
{
let trie_backend = backend.as_trie_backend()
.ok_or_else(|| Box::new(ExecutionError::UnableToGenerateProof) as Box<dyn Error>)?;
@@ -478,7 +480,7 @@ where
S: trie_backend_essence::TrieBackendStorage<H>,
H: Hasher,
H::Out: Ord + 'static + codec::Codec,
Exec: CodeExecutor,
Exec: CodeExecutor + 'static + Clone,
{
let proving_backend = proving_backend::ProvingBackend::new(trie_backend);
let mut sm = StateMachine::<_, H, _, InMemoryChangesTrieStorage<H, u64>, Exec>::new(
@@ -504,7 +506,7 @@ pub fn execution_proof_check<H, Exec>(
) -> Result<Vec<u8>, Box<dyn Error>>
where
H: Hasher,
Exec: CodeExecutor,
Exec: CodeExecutor + Clone + 'static,
H::Out: Ord + 'static + codec::Codec,
{
let trie_backend = create_proof_check_backend::<H>(root.into(), proof)?;
@@ -522,7 +524,7 @@ pub fn execution_proof_check_on_trie_backend<H, Exec>(
where
H: Hasher,
H::Out: Ord + 'static + codec::Codec,
Exec: CodeExecutor,
Exec: CodeExecutor + Clone + 'static,
{
let mut sm = StateMachine::<_, H, _, InMemoryChangesTrieStorage<H, u64>, Exec>::new(
trie_backend, None, overlay, exec, method, call_data, Extensions::default(),
@@ -741,6 +743,7 @@ mod tests {
};
use sp_core::{Blake2Hasher, map, traits::Externalities, storage::ChildStorageKey};
#[derive(Clone)]
struct DummyCodeExecutor {
change_changes_trie_config: bool,
native_available: bool,
@@ -797,6 +800,18 @@ mod tests {
}
}
impl sp_core::traits::CallInWasm for DummyCodeExecutor {
fn call_in_wasm(
&self,
_: &[u8],
_: &str,
_: &[u8],
_: &mut dyn Externalities,
) -> std::result::Result<Vec<u8>, String> {
unimplemented!("Not required in tests.")
}
}
#[test]
fn execute_works() {
let backend = trie_backend::tests::test_trie();
+1 -1
View File
@@ -7,7 +7,7 @@ edition = "2018"
[dependencies]
impl-serde = { version = "0.2.3", optional = true }
serde = { version = "1.0.101", optional = true, features = ["derive"] }
codec = { package = "parity-scale-codec", version = "1.0.5", default-features = false, features = ["derive"] }
codec = { package = "parity-scale-codec", version = "1.1.2", default-features = false, features = ["derive"] }
sp-std = { version = "2.0.0", default-features = false, path = "../std" }
sp-runtime = { version = "2.0.0", default-features = false, path = "../runtime" }
+8 -20
View File
@@ -25,11 +25,11 @@ use std::fmt;
#[cfg(feature = "std")]
use std::collections::HashSet;
use codec::Encode;
#[cfg(feature = "std")]
use codec::Decode;
use codec::{Encode, Decode};
use sp_runtime::RuntimeString;
pub use sp_runtime::create_runtime_str;
#[doc(hidden)]
pub use sp_std;
#[cfg(feature = "std")]
use sp_runtime::{traits::Block as BlockT, generic::BlockId};
@@ -37,25 +37,13 @@ use sp_runtime::{traits::Block as BlockT, generic::BlockId};
/// The identity of a particular API interface that the runtime might provide.
pub type ApiId = [u8; 8];
/// A vector of pairs of `ApiId` and a `u32` for version. For `"std"` builds, this
/// is a `Cow`.
#[cfg(feature = "std")]
pub type ApisVec = std::borrow::Cow<'static, [(ApiId, u32)]>;
/// A vector of pairs of `ApiId` and a `u32` for version. For `"no-std"` builds, this
/// is just a reference.
#[cfg(not(feature = "std"))]
pub type ApisVec = &'static [(ApiId, u32)];
/// A vector of pairs of `ApiId` and a `u32` for version.
pub type ApisVec = sp_std::borrow::Cow<'static, [(ApiId, u32)]>;
/// Create a vector of Api declarations.
#[macro_export]
#[cfg(feature = "std")]
macro_rules! create_apis_vec {
( $y:expr ) => { std::borrow::Cow::Borrowed(& $y) }
}
#[macro_export]
#[cfg(not(feature = "std"))]
macro_rules! create_apis_vec {
( $y:expr ) => { & $y }
( $y:expr ) => { $crate::sp_std::borrow::Cow::Borrowed(& $y) }
}
/// Runtime version.
@@ -63,8 +51,8 @@ macro_rules! create_apis_vec {
/// 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, Default, sp_runtime::RuntimeDebug)]
#[cfg_attr(feature = "std", derive(Serialize, Deserialize, Decode))]
#[derive(Clone, PartialEq, Eq, Encode, Decode, Default, sp_runtime::RuntimeDebug)]
#[cfg_attr(feature = "std", derive(Serialize, Deserialize))]
#[cfg_attr(feature = "std", serde(rename_all = "camelCase"))]
pub struct RuntimeVersion {
/// Identifies the different Substrate runtimes. There'll be at least polkadot and node.